diff --git a/.cargo/config.toml b/.cargo/config.toml index f5aa362..4d35cfd 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,8 +2,9 @@ target = "x86_64-unknown-uefi" [unstable] -build-std = ["core", "compiler_builtins", "alloc"] +build-std = ["core", "alloc"] build-std-features = ["compiler-builtins-mem"] +panic-abort-tests = true [target.x86_64-unknown-uefi] runner = "scripts/qemu-runner.sh" diff --git a/.cargo/config_link.toml b/.cargo/config_link.toml index cc59626..be8ed48 100644 --- a/.cargo/config_link.toml +++ b/.cargo/config_link.toml @@ -1,2 +1,2 @@ [target.x86_64-unknown-uefi] -rustflags = ["-C", "link-arg=-lswiftcore"] +rustflags = ["-C", "link-arg=-lmochios"] diff --git "a/.github/ISSUE_TEMPLATE/\343\203\220\343\202\260\343\201\256\345\240\261\345\221\212.md" "b/.github/ISSUE_TEMPLATE/\343\203\220\343\202\260\343\201\256\345\240\261\345\221\212.md" new file mode 100644 index 0000000..5e1d337 --- /dev/null +++ "b/.github/ISSUE_TEMPLATE/\343\203\220\343\202\260\343\201\256\345\240\261\345\221\212.md" @@ -0,0 +1,17 @@ +--- +name: バグの報告 +about: Create a report to help us improve +title: 'Bug report: ' +labels: bug +assignees: tas0dev + +--- + +## 概要 +報告するバグの内容を簡潔に書いてください。 + +## 動作 +そのバグはどのようにしたら発生したか、詳細に書いてください。 + +## 発生箇所 +そのバグが発生するソースコード(わかる場合でOKです!) diff --git "a/.github/ISSUE_TEMPLATE/\346\251\237\350\203\275\343\201\256\346\217\220\346\241\210.md" "b/.github/ISSUE_TEMPLATE/\346\251\237\350\203\275\343\201\256\346\217\220\346\241\210.md" new file mode 100644 index 0000000..68e57ac --- /dev/null +++ "b/.github/ISSUE_TEMPLATE/\346\251\237\350\203\275\343\201\256\346\217\220\346\241\210.md" @@ -0,0 +1,19 @@ +--- +name: 機能の提案 +about: Suggest an idea for this project +title: 'Feature request: ' +labels: enhancement +assignees: tas0dev + +--- + +問題が何であるかについて、明確かつ簡潔に説明してください。 例 私はいつも...のときに苛立ちます。 + +## その解決策 +起こってほしいことの明確かつ簡潔な説明を書いてください。 + +## 代替案 +その機能について、代替できる方法はありますか? + +## ノート +ここに機能リクエストに関するその他のコンテキストやスクリーンショットを追加してください。 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..acfa1b3 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,21 @@ +## 概要 / Summary + + + +## 関連タスク / Related Tasks + + + +## スクリーンショット / Screenshots + + + +## 備考 / Notes + + \ No newline at end of file diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..b55cbbd --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,92 @@ +# mochiOS Copilot インストラクション + +mochiOS は Rust(no_std、nightly)で書かれたx86-64のハイブリッドアーキテクチャカーネルです。カーネルはハイブリッドカーネル設計を参考にしており、最小限のカーネルコアが単一の `core.service` を起動し、そこからディスク・ファイルシステム・シェルの各サービスを IPC 経由でユーザー空間に立ち上げます。 +絶対にクラッシュしないことを目標にしています。 + +## ビルドと実行 + +```bash +# 初回セットアップ +git submodule update --init --recursive +cd src/lib && ./configure && cd ../.. + +# ビルド(UEFI ディスクイメージを生成) +cargo build + +# ビルドして QEMU で実行 +cargo run + +# リリースビルド +cargo build --release +``` + +ビルドは `build.rs` が `builders/` 配下のモジュールを呼び出して制御します。カーネル ELF・newlib(libc)・サービス/ユーティリティバイナリのコンパイル、`ramfs/` を `initfs.ext2` へ、`fs/` を `rootfs.ext2` へパック、最終的な UEFI ESP イメージの組み立て、という流れです。 +実行はユーザーが行います。あなたは実装だけ行うようにしてください。 + +### ブートフロー + +``` +UEFI ファームウェア + └─ src/boot/loader.rs # カーネル ELF + initfs/rootfs イメージをロード、BootInfo を構築 + └─ src/core/entry.rs # カーネルエントリ(sysv64 ABI) + └─ src/core/kernel.rs # 初期化: メモリ → GDT/TSS → IDT/PIC → ヒープ → core.service 起動 + └─ core.service → disk.service → fs.service → shell.service(IPC 管理) +``` + +### ソースレイアウト + +| パス | 役割 | +|-------------------|--------------------------------------------------| +| `src/boot/` | UEFI ブートローダー — ELF ロード、メモリマップ、BootInfo | +| `src/core/` | カーネル — 割り込み、メモリ、スケジューラ、システムコール、ELF ローダー | +| `src/services/` | ユーザー空間サービス(core, disk, fs, shell)— 各々独立したクレート | +| `src/user/` | `swiftlib` — ユーザー空間バイナリ向けシステムコールラッパー、IPC、アロケータ | +| `src/utils/` | ユーザー空間ユーティリティ(ls, echo, cat, pwd) | +| `src/posix/` | POSIX 互換シム | +| `src/lib/` | Newlib(git サブモジュール) | +| `builders/` | `build.rs` から呼び出されるビルドスクリプトモジュール | +| `ramfs/` | initfs ソース(サービス ELF + 最小限のライブラリ、ビルド時に ext2 へパック) | +| `fs/` | ルートファイルシステムソース(ビルド時に ext2 へパック) | + +### カーネルサブシステム(`src/core/`) + +- `mem/` — GDT、TSS、ページング、ヒープアロケータ(`linked_list_allocator`) +- `interrupt/` — IDT、PIC、タイマー、システムコールディスパッチ +- `task/` — プロセス・スレッドスケジューラ +- `syscall/` — exec、IPC、fs、I/O、時刻など +- `elf/` — ユーザー空間 ELF ローダー +- `percpu.rs` — CPU ごとの状態管理 +- `panic.rs` — カーネルパニックハンドラ + +### カスタムビルドターゲット + +カーネルは `src/x86_64-mochios.json` 向けにコンパイルされます: +- LLVM トリプル:`x86_64-unknown-linux-musl`(ベアメタル向けカスタマイズ) +- MMX/AVX 無効、コードモデル:small、リロケーション:static +- リンカー:`rust-lld`、パニック戦略:abort、レッドゾーン無効 +- カーネルは `0x200000` にリンク(`src/core/kernel.ld` 参照) + +ブートローダーは `x86_64-unknown-uefi` ターゲットを使用します。 + +## 主な規約 + +### `no_std` の徹底 +カーネルコード(`src/core/`、`src/boot/`)はすべて `#![no_std]` を使用します。カーネルやブートローダーに `std` のインポートや `std` 専用クレートを追加しないでください。 + +### カーネルコードで `unwrap()` 禁止 +カーネルは `#![deny(clippy::unwrap_used)]` を設定しています。`result.rs` で定義された `Result` エイリアス(`Result`)を使用してください。エラーバリアントは `result.rs` の `Kernel`・`Process`・`Memory` カテゴリに定義されています。 + +### サービスは独立したクレート +`src/services//` 配下の各サービスは、独自の `Cargo.toml` と `src/main.rs` を持ちます。サービスは `builders/services.rs` でコンパイルされます(ワークスペースではありません)。サービスのマニフェストは `src/services/index.toml` にあります。 + +### IPC がサービス間通信の唯一の手段 +サービス間の通信は OP コードを使ったメッセージパッシングのみで行います。サービスをまたいだ直接の関数呼び出しは存在しません。 + +### アドレスは `u64` +仮想アドレスおよび物理アドレスは、カーネル全体を通じて `u64` で表現します(可能な限り `usize` や生ポインタは使わない)。 + +### Rust ツールチェーン +`rust-toolchain.toml` で定義:nightly チャンネル、コンポーネントは `rust-src` と `llvm-tools`。すべての `#![feature(...)]` 属性が引き続きコンパイルできることを確認せずにチャンネルを変更しないでください。 + +### ソースとドキュメント内の日本語 +コメント、ドキュメントは日本語で記載されています。プロジェクトの README と CODE_OF_CONDUCT も日本語です。これは意図的なものです。 「修正」や翻訳はしないでください。 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1c159c8..b137083 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,35 @@ -/target +# bin +target/ +/fs/ +/ramfs/ +initfs/ +*.bin +*.elf +*.service +*.o +*.a +*.so +*.so.* +*.rlib + +# editor config files +.idea/** +.vscode/** +.zed/** + +# fossil version control *.fossil *.fslckout -*.log \ No newline at end of file + +# caches and logs +rustc-ice-*.txt +*.log +*.lock + +# Pull Request diff and patch files +*.diff +*.patch + +# applications and libs +src/lib/** +src/apps/** diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..afb777b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,18 @@ +[submodule "src/lib"] + path = src/lib + url = https://sourceware.org/git/newlib-cygwin.git +[submodule "src/apps/Kagami"] + path = src/apps/Kagami + url = https://github.com/tas0dev/Kagami +[submodule "src/apps/Binder"] + path = src/apps/Binder + url = https://github.com/tas0dev/Binder +[submodule "src/apps/ViewKit"] + path = src/apps/ViewKit + url = https://github.com/tas0dev/ViewKit +[submodule "src/apps/Dock"] + path = src/apps/Dock + url = https://github.com/tas0dev/dock +[submodule "src/apps/Terminal"] + path = src/apps/Terminal + url = https://github.com/tas0dev/Terminal diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 8a1df83..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "rust-analyzer.cargo.target": "x86_64-unknown-uefi", - "rust-analyzer.cargo.buildScripts.enable": true, - "rust-analyzer.check.allTargets": false, - "rust-analyzer.diagnostics.disabled": [ - "unsafe-code", - "unused-unsafe", - "unsafe-op-in-unsafe-fn" - ] -} diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..9275bc7 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,30 @@ +## mochiOS Code of Conduct + +オペレーティングシステムは、多種多様なハードウェアを抽象化し、安全で過ごしやすい環境をアプリケーションに提供します。したがって、その実装も多様であり、その開発に関わる人々もまた、さまざまな背景や意見、独自性を持っているのは自然なことです。 + +mochiOSでは、OS開発に有用な情報を収集し公開すること、また開発者間での交流を促進することを目標としています。これを達成するには、すべての参加者がお互いを尊重し、建設的であることが重要です。そのための道しるべとして、ここに行動指針を定めます。 + +mochiOSのメンテナーの主催するイベント、およびmochiOSのメンテナーが管理するコミュニティに関わる全ての方には、この行動指針に記載されている内容を遵守することが求められます。 + +mochiOSのメンテナーには、本行動指針に反する行為をした参加者に対し、そのような行為を止めるよう声を上げる責任があります。同時に、参加者それぞれの適切な行動も重要です。 +本行動指針に反してコミュニティの安全をゆるがす行為を継続した参加者に対しては、本コミュニティやその主催するイベントへの参加の禁止等の対処を行うことがあります。 + +### 求められる行動(ぜひやりましょう) + +- 異なる意見、視点、経験を尊重し、お互いの違いを歓迎する +- 建設的な意見を積極的に発信し、知見を広く共有する +- 失敗を恐れず行動し、失敗を受け入れ、失敗から学ぶ +- 常にお互いに敬意をもって対等に接する + +### 不適切とみなされる行動(してはいけません) +- 特定の個人や組織、製品やプロジェクトなどを貶めたり、誹謗中傷すること +- 性、性同一性、性表現、性的指向、障害、身体的外見、身体の大きさ、人種、民族性、国籍、宗教、年齢などに関する攻撃的、不適切、または不必要な言及 +- あらゆる形態の、他者への脅迫・虐め・ハラスメント + +## 相談先 + +[mochiOSメンテナー](./maintainer.md)の誰かに連絡してください。メールでも、TwitterのDMでもOKです。 + +--- + +このCoCは[osdev-jp](https://osdev.jp)の[CoC](https://osdev.jp/code-of-conduct.html)を元に、[tas0dev](https://github.com/tas0dev)が名称の変更などを行なったものであり、mochiOSのコミュニティはosdev-jpと一切関係ありません。 diff --git a/Cargo.lock b/Cargo.lock index a781514..16f386c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,17 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "SwiftCore" -version = "0.1.0" -dependencies = [ - "bitflags", - "log", - "spin", - "uefi", - "x86_64", -] - [[package]] name = "bit_field" version = "0.10.3" @@ -21,9 +10,9 @@ checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "cfg-if" @@ -33,9 +22,30 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "const_fn" -version = "0.4.11" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413d67b29ef1021b4d60f4aa1e925ca031751e213832b4b1d588fae623c05c60" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "libc" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f8a2ca5ac02d09563609681103aada9e1777d54fc57a5acd7a41404f9c93b6e" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "linked_list_allocator" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" +dependencies = [ + "spinning_top", +] [[package]] name = "lock_api" @@ -52,11 +62,32 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "mochiOS" +version = "0.0.1-dev" +dependencies = [ + "linked_list_allocator", + "num_cpus", + "spin", + "uefi", + "x86_64", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "proc-macro2" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -83,9 +114,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.43" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -111,6 +142,15 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spinning_top" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9eb1a2f4c41445a3a0ff9abc5221c5fcd28e1f13cd7c0397706f9ac938ddb0" +dependencies = [ + "lock_api", +] + [[package]] name = "syn" version = "1.0.109" @@ -124,9 +164,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.114" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -166,7 +206,7 @@ checksum = "4f345e42323c05e41e29e409505f5f8e0df7b5743340215d60344dbd79b729f4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -188,9 +228,9 @@ checksum = "0c8352f8c05e47892e7eaf13b34abd76a7f4aeaf817b716e88789381927f199c" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "volatile" diff --git a/Cargo.toml b/Cargo.toml index e67fe3f..50d5834 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,24 +1,39 @@ [package] -name = "SwiftCore" -version = "0.1.0" +name = "mochiOS" +version = "0.0.1-dev" edition = "2021" -authors = ["nekogakure "] +authors = ["tas0dev"] default-run = "boot" +build = "build.rs" [lib] -name = "swiftcore" -path = "src/lib.rs" +name = "mochios" +path = "src/core/lib.rs" +test = false +bench = false [[bin]] name = "boot" path = "src/boot/loader.rs" +required-features = ["uefi-dep"] +test = false +bench = false + +[features] +default = ["uefi-dep"] +uefi-dep = ["dep:uefi"] +kcfi = [] +cet-ibt = [] +cet-shadow-stack = [] [dependencies] -uefi = { version = "0.30", features = ["alloc", "logger"] } -log = "0.4" +uefi = { version = "0.30", features = ["alloc", "logger", "global_allocator"], optional = true } spin = "0.9" x86_64 = "0.15" -bitflags = "2.4" +linked_list_allocator = "0.10.5" + +[build-dependencies] +num_cpus = "1.17.0" [profile.dev] panic = "abort" diff --git a/LICENSE b/LICENSE index 1cd473d..239f55c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,13 +1,201 @@ -Copyright 2025 nekogakure + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - http://www.apache.org/licenses/LICENSE-2.0 + 1. Definitions. -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 tas0dev + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 73bb103..e8294f1 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,66 @@ -# SwiftCore -SwiftCoreはRustで書かれた x86_64 / UEFI 向けのハイブリッドカーネルです。 -SwiftOSに使用されています。 - -## ビルド -```bash -cargo build -``` - -### 実行 -```bash -cargo run -``` - -## ライセンス -[LICENSE](./LICENSE)ファイルを参照してください \ No newline at end of file +
+

mochiOS

+Ask DeepWiki +dependency status +Discord server +
+ +## About +mochiOSはハイブリッドアーキテクチャを採用した、新しいOSです。中学生によって開発/維持されています。 +「絶対クラッシュしないこと」を実現しようとしています。 + +餅という名前にしたのは餅は柔らかくて壊れにくいから(伸びても切れない)。超絶安直なネーミングだぜぇ。 + +## Build +必要なツール: +- git +- qemu-system-x86_64 +- x86_64-elf-gcc +- cargo +- rustup +- make +- e2fsprogs +- texinfo +- build-essentialで入るすべてのツール +- mtools +- libgcc-s1 +- `x86_64-unknown-none`ターゲット +- `x86_64-unknown-uefi`ターゲット +- Nightly toolchain + +> [!TIP] +> x86_64-elf-gccは[homebrew](https://brew.sh/)でインストールすることを推奨します。(Ubuntu標準のaptリポジトリにありません)また、brewをインストール時、`Run there commands in your terminal to add Homebrew to your PATH`と表示されたら、必ず指示に従ってください。 + +また、これらのツールはUbuntuを使用している人は`scripts/autoinstall.sh`を使用すると自動でインストールできます。ただ、brewのインストールは各自行ってください。 + +1. このレポをクローンします。 +2. サブモジュールをインストールします。 + ```bash + git submodule update --init --recursive + ``` +3. libcのconfigureをします。 + ```bash + cd src/lib + ./configure + ``` +4. ビルドします。 + ```bash + cd ../.. + cargo build + ``` +5. 実行します。 + ```bash + cargo run + ``` + +## How to contribute? +ライセンスは[この](./LICENSE)ファイルを参照してください + +## Document +まともなドキュメントはまだないです。 +[DeepWiki](https://deepwiki.com/tas0dev/mochiOS)を読んでください。 + +
+mochimochi-kun +< みんなの貢献待ってるよ! +
\ No newline at end of file diff --git a/arch.md b/arch.md deleted file mode 100644 index 7cc823f..0000000 --- a/arch.md +++ /dev/null @@ -1,58 +0,0 @@ -# SwiftCore アーキテクチャ設計 - -ハイブリッドカーネル - -### 設計 - -- **コア機能**: カーネル空間で高速実行(Ring 0) -- **拡張機能**: モジュール化して柔軟性を確保 -- **デバイスドライバ**: 一部をユーザー空間で動作可能に - -## レイヤー構造 - -```mermaid -graph TB - subgraph UserSpace["ユーザー空間 (Ring 3)"] - App["アプリケーション"] - UserDriver["ユーザーランドドライバ"] - end - - subgraph KernelSpace["カーネル空間 (Ring 0)"] - subgraph ServiceLayer["サービス層"] - DeviceDriver["デバイスドライバ"] - FileSystem["ファイルシステム"] - Network["ネットワークスタック"] - IntHandler["割込みハンドラ(実処理)"] - end - - subgraph KernelCore["カーネルコア"] - Memory["メモリ管理
(VMM/PMM)"] - Process["プロセス/スレッド管理"] - Scheduler["スケジューラ"] - Syscall["システムコール機構"] - IntMgmt["割込み管理
(IDT/ディスパッチ)"] - end - - subgraph HAL["ハードウェア抽象化層 (HAL)"] - CPU["CPU制御"] - IntCtrl["割込みコントローラ
(PIC/APIC)"] - Timer["タイマー"] - end - end - - Hardware["ハードウェア"] - - App --> |システムコール| Syscall - UserDriver --> |システムコール| Syscall - - ServiceLayer <--> KernelCore - KernelCore <--> HAL - HAL <--> Hardware - - style UserSpace fill:#e1f5ff - style KernelSpace fill:#fff3e0 - style ServiceLayer fill:#ffe0b2 - style KernelCore fill:#ffcc80 - style HAL fill:#ffb74d - style Hardware fill:#bdbdbd -``` diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..548d6ec --- /dev/null +++ b/build.rs @@ -0,0 +1,626 @@ +mod builders; + +use std::collections::HashSet; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; + +use builders::{ + build_apps, build_drivers, build_module, build_newlib, build_service, build_user_libs, + build_utils, copy_newlib_libs, create_ext2_image, create_initfs_image, default_modules, + parse_service_index, setup_fs_layout, +}; + +const BUSYBOX_URL: &str = "https://busybox.net/downloads/binaries/1.35.0-x86_64-linux-musl/busybox"; +const BUSYBOX_SHA256: &str = "6e123e7f3202a8c1e9b1f94d8941580a25135382b99e8d3e34fb858bba311348"; + +/// カーネル ELF をビルドして fs/system/kernel.elf にコピーする +fn build_kernel(manifest_dir: &PathBuf, fs_dir: &PathBuf, profile: &str) { + let kernel_crate_dir = manifest_dir.join("src/core"); + let kernel_target_dir = manifest_dir.join("target/kernel"); + + let mut clean = std::process::Command::new("cargo"); + clean.current_dir(&kernel_crate_dir); + clean.arg("clean"); + let _ = clean.status(); + + let mut cmd = std::process::Command::new("cargo"); + cmd.current_dir(&kernel_crate_dir); + cmd.env("MOCHIOS_BUILDING_KERNEL", "1"); + cmd.env("CARGO_TARGET_DIR", &kernel_target_dir); + cmd.args(["build", "-Z", "build-std=core,alloc"]); + if profile == "release" { + cmd.arg("--release"); + } + let status = cmd.status(); + match status { + Ok(s) if s.success() => {} + Ok(s) => { + panic!( + "kernel build failed with exit code {}", + s.code().unwrap_or(-1) + ); + } + Err(e) => { + panic!("failed to run kernel cargo build: {}", e); + } + } + + let kernel_bin = kernel_target_dir + .join("x86_64-unknown-none") + .join(profile) + .join("kernel"); + let system_dir = fs_dir.join("system"); + fs::create_dir_all(&system_dir).expect("failed to create system directory"); + let dest = system_dir.join("kernel.elf"); + if !kernel_bin.exists() { + panic!("kernel binary not found at {}", kernel_bin.display()); + } + fs::copy(&kernel_bin, &dest) + .unwrap_or_else(|e| panic!("failed to copy kernel ELF to {}: {}", dest.display(), e)); + println!("Kernel ELF copied to {}", dest.display()); +} + +fn is_elf_binary(path: &Path) -> Result { + use std::io::Read; + + let mut file = + fs::File::open(path).map_err(|e| format!("Failed to open {}: {}", path.display(), e))?; + let mut magic = [0u8; 4]; + file.read_exact(&mut magic) + .map_err(|e| format!("Failed to read ELF magic from {}: {}", path.display(), e))?; + Ok(magic == [0x7F, b'E', b'L', b'F']) +} + +fn compute_sha256(path: &Path) -> Result { + use std::process::Command; + + let output = Command::new("sha256sum") + .arg(path) + .output() + .map_err(|e| format!("Failed to run sha256sum: {}", e))?; + + if !output.status.success() { + return Err("sha256sum failed".to_string()); + } + + let stdout = String::from_utf8_lossy(&output.stdout); + stdout + .split_whitespace() + .next() + .map(|s| s.to_lowercase()) + .ok_or_else(|| "No hash in sha256sum output".to_string()) +} + +/// BusyBoxをダウンロード +fn ensure_busybox_binary(fs_dir: &Path) -> Result<(), String> { + let binaries_dir = fs_dir.join("bin"); + fs::create_dir_all(&binaries_dir) + .map_err(|e| format!("Failed to create {}: {}", binaries_dir.display(), e))?; + + let dest = binaries_dir.join("busybox.elf"); + let temp = binaries_dir.join("busybox.elf.download"); + + if dest.exists() { + if !is_elf_binary(&dest)? { + println!( + "cargo:warning=Existing BusyBox is not ELF, replacing: {}", + dest.display() + ); + fs::remove_file(&dest) + .map_err(|e| format!("Failed to remove invalid {}: {}", dest.display(), e))?; + } else { + let existing_hash = compute_sha256(&dest)?; + if existing_hash == BUSYBOX_SHA256 { + println!( + "BusyBox already exists and checksum verified at {}", + dest.display() + ); + return Ok(()); + } + println!( + "cargo:warning=Existing BusyBox checksum mismatch (expected {}, got {}), replacing {}", + BUSYBOX_SHA256, + existing_hash, + dest.display() + ); + fs::remove_file(&dest).map_err(|e| { + format!( + "Failed to remove checksum-mismatched {}: {}", + dest.display(), + e + ) + })?; + } + } + + println!("Downloading busybox from {}", BUSYBOX_URL); + + let status = std::process::Command::new("curl") + .args([ + "-L", + "--fail", + "--silent", + "--show-error", + "--max-time", + "30", + "--output", + ]) + .arg(&temp) + .arg(BUSYBOX_URL) + .status(); + + match status { + Ok(s) if s.success() => { + if !is_elf_binary(&temp)? { + let _ = fs::remove_file(&temp); + return Err(format!( + "Downloaded file is not a valid ELF binary: {}", + temp.display() + )); + } + + // SHA256整合性検証(既知の固定値と照合) + let sha256_file = binaries_dir.join("busybox.elf.sha256"); + let actual_hash = compute_sha256(&temp)?; + + if actual_hash != BUSYBOX_SHA256 { + let _ = fs::remove_file(&temp); + return Err(format!( + "BusyBox SHA256 mismatch: expected {}, got {}. \ + 上流バイナリが変更された場合は {} を削除して再ビルドしてください", + BUSYBOX_SHA256, + actual_hash, + sha256_file.display() + )); + } + + let _ = fs::write(&sha256_file, &actual_hash); + + if let Err(rename_err) = fs::rename(&temp, &dest) { + fs::copy(&temp, &dest).map_err(|copy_err| { + format!( + "Failed to place busybox at {} (rename: {}, copy: {})", + dest.display(), + rename_err, + copy_err + ) + })?; + let _ = fs::remove_file(&temp); + } + + println!("Downloaded busybox to {}", dest.display()); + Ok(()) + } + Ok(s) => { + let _ = fs::remove_file(&temp); + if dest.exists() { + let existing_hash = compute_sha256(&dest)?; + if existing_hash != BUSYBOX_SHA256 { + return Err(format!( + "BusyBox download failed (status={}) and existing {} checksum mismatch: expected {}, got {}", + s, + dest.display(), + BUSYBOX_SHA256, + existing_hash + )); + } + println!( + "cargo:warning=BusyBox download failed (status={}), using existing {}", + s, + dest.display() + ); + Ok(()) + } else { + Err(format!( + "BusyBox download failed (status={}) and no fallback file exists at {}", + s, + dest.display() + )) + } + } + Err(e) => { + let _ = fs::remove_file(&temp); + if dest.exists() { + println!( + "cargo:warning=Failed to execute curl ({}), using existing {}", + e, + dest.display() + ); + Ok(()) + } else { + Err(format!( + "Failed to execute curl ({}) and no fallback file exists at {}", + e, + dest.display() + )) + } + } + } +} + +fn prune_stale_service_artifacts( + services: &[builders::services::ServiceEntry], + ramfs_dir: &Path, + fs_dir: &Path, +) -> Result<(), String> { + let initfs_expected: HashSet = services + .iter() + .filter(|s| s.fs_type == "initfs") + .map(|s| format!("{}.service", s.name)) + .collect(); + let ata_expected: HashSet = services + .iter() + .filter(|s| s.fs_type != "initfs") + .map(|s| format!("{}.service", s.name)) + .collect(); + + if let Ok(entries) = fs::read_dir(ramfs_dir) { + for entry in entries.flatten() { + let path = entry.path(); + if !path.is_file() { + continue; + } + let Some(name) = path.file_name().and_then(|n| n.to_str()) else { + continue; + }; + if name.ends_with(".service") && !initfs_expected.contains(name) { + fs::remove_file(&path) + .map_err(|e| format!("Failed to remove stale {}: {}", path.display(), e))?; + } + } + } + + let fs_services_dir = fs_dir.join("services"); + if let Ok(entries) = fs::read_dir(&fs_services_dir) { + for entry in entries.flatten() { + let path = entry.path(); + if !path.is_file() { + continue; + } + let Some(name) = path.file_name().and_then(|n| n.to_str()) else { + continue; + }; + if name.ends_with(".service") && !ata_expected.contains(name) { + fs::remove_file(&path) + .map_err(|e| format!("Failed to remove stale {}: {}", path.display(), e))?; + } + } + } + + Ok(()) +} + +fn prune_stale_module_artifacts( + modules: &[builders::modules::ModuleEntry], + ramfs_dir: &Path, +) -> Result<(), String> { + let expected: HashSet = modules.iter().map(|m| format!("{}.cext", m.name)).collect(); + let modules_dir = ramfs_dir.join("Modules"); + if let Ok(entries) = fs::read_dir(&modules_dir) { + for entry in entries.flatten() { + let path = entry.path(); + if !path.is_file() { + continue; + } + let Some(name) = path.file_name().and_then(|n| n.to_str()) else { + continue; + }; + if name.ends_with(".cext") && !expected.contains(name) { + fs::remove_file(&path) + .map_err(|e| format!("Failed to remove stale {}: {}", path.display(), e))?; + } + } + } + Ok(()) +} + +#[allow(unused)] +fn main() { + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + + // Emit rerun-if-changed for all source directories + fn emit_rerun_for_dir(dir: &Path) { + if let Ok(entries) = std::fs::read_dir(dir) { + for e in entries.flatten() { + let p = e.path(); + if p.is_file() { + if p.extension() + .map(|e| e == "rs" || e == "toml") + .unwrap_or(false) + { + println!("cargo:rerun-if-changed={}", p.display()); + } + } else if p.is_dir() { + if p.file_name() + .map(|n| n != "target" && n != ".git") + .unwrap_or(true) + { + emit_rerun_for_dir(&p); + } + } + } + } + } + + for dir in &[ + "src/user", + "src/services", + "src/modules", + "src/apps", + "src/drivers", + "src/resources", + ] { + let p = manifest_dir.join(dir); + if p.exists() { + emit_rerun_for_dir(&p); + } + } + + println!("cargo:rerun-if-env-changed=PROFILE"); + println!("cargo:rerun-if-env-changed=TARGET"); + println!("cargo:rerun-if-env-changed=CARGO_TARGET_DIR"); + + // カーネルビルドの再帰呼び出しの場合はプレースホルダーだけ作成して終了する + // (initfs は埋め込まず、ブートローダーが実行時にロードして BootInfo で渡す) + if env::var("MOCHIOS_BUILDING_KERNEL").is_ok() { + let _ = fs::write(out_dir.join("initfs.ext2"), b""); + let _ = fs::write(out_dir.join("rootfs.ext2"), b""); + return; + } + + // ramfsとfsディレクトリを作成 + let ramfs_dir = manifest_dir.join("ramfs"); + let fs_dir = manifest_dir.join("fs"); + + for dir in &[&ramfs_dir, &fs_dir] { + if !dir.is_dir() { + fs::create_dir_all(dir) + .unwrap_or_else(|_| panic!("Failed to create directory: {}", dir.display())); + } + } + + // fsの標準ディレクトリレイアウトを作成 + let resources_src = manifest_dir.join("src/resources"); + setup_fs_layout(&fs_dir, &resources_src) + .unwrap_or_else(|e| println!("cargo:warning=setup_fs_layout failed: {}", e)); + + // newlibのインストールディレクトリを取得 + let target = env::var("TARGET").unwrap_or("x86_64-unknown-uefi".to_string()); + let profile = env::var("PROFILE").unwrap_or("debug".to_string()); + let target_dir = PathBuf::from(env::var("CARGO_TARGET_DIR").unwrap_or("target".to_string())); + + // カーネル ELF をビルド + build_kernel(&manifest_dir, &fs_dir, &profile); + + // newlibのビルド + let newlib_src_dir = manifest_dir.join("src/lib"); + if !newlib_src_dir.exists() { + panic!("Newlib source not found at {}", newlib_src_dir.display()); + } + build_newlib(&newlib_src_dir); + + let abs_target_dir = if target_dir.is_absolute() { + target_dir + } else { + manifest_dir.join(target_dir) + }; + + let newlib_install_dir = abs_target_dir + .join(&target) + .join(&profile) + .join("newlib_install"); + + // Verify newlib output exists before proceeding + let libc_path = newlib_install_dir + .join("x86_64-elf") + .join("lib") + .join("libc.a"); + if !libc_path.exists() { + panic!( + "newlib build completed but libc.a not found at {}", + libc_path.display() + ); + } + + let libc_dir = newlib_install_dir.join("x86_64-elf").join("lib"); + + // ユーザーライブラリをビルド + let user_src_dir = manifest_dir.join("src/user"); + build_user_libs(&user_src_dir, &libc_dir); + + // newlibライブラリをramfsとfsにコピー + copy_newlib_libs(&libc_dir, &ramfs_dir.join("lib")) + .expect("cargo:warning=Failed to copy newlib libs to ramfs/lib"); + copy_newlib_libs(&libc_dir, &fs_dir.join("lib")) + .expect("cargo:warning=Failed to copy newlib libs to fs/lib"); + + // libgcc_sをfs/libにコピー + if let Ok(out) = std::process::Command::new("gcc") + .arg("-print-file-name=libgcc_s.so.1") + .output() + { + if out.status.success() { + let path = String::from_utf8_lossy(&out.stdout).trim().to_string(); + use std::path::Path; + let libs_dir = fs_dir.join("lib"); + let _ = fs::create_dir_all(&libs_dir); + if path != "libgcc_s.so.1" && Path::new(&path).exists() { + let dest = libs_dir.join("libgcc_s.so.1"); + let _ = fs::copy(&path, &dest); + #[cfg(unix)] + { + use std::os::unix::fs::symlink; + let link = libs_dir.join("libgcc_s.so"); + if !link.exists() { + let _ = symlink("libgcc_s.so.1", &link); + } + } + println!("Copied libgcc_s to fs/lib: {}", path); + } else { + let candidates = [ + "/usr/lib/x86_64-linux-gnu/libgcc_s.so.1", + "/lib/x86_64-linux-gnu/libgcc_s.so.1", + "/usr/lib64/libgcc_s.so.1", + "/lib64/libgcc_s.so.1", + ]; + for c in &candidates { + if Path::new(c).exists() { + let _ = fs::copy(c, libs_dir.join("libgcc_s.so.1")); + #[cfg(unix)] + { + use std::os::unix::fs::symlink; + let link = libs_dir.join("libgcc_s.so"); + if !link.exists() { + let _ = symlink("libgcc_s.so.1", &link); + } + } + println!("Copied libgcc_s to fs/lib from {}", c); + break; + } + } + } + } else { + println!("gcc returned non-zero when locating libgcc_s"); + } + } else { + println!("Failed to run gcc to locate libgcc_s"); + } + + // services/index.toml を解析 + let index_path = manifest_dir.join("src/services/index.toml"); + println!("cargo:rerun-if-changed={}", index_path.display()); + + let services = parse_service_index(&index_path).expect("Failed to parse index.toml"); + + prune_stale_service_artifacts(&services, &ramfs_dir, &fs_dir) + .expect("Failed to prune stale service artifacts"); + + let modules = default_modules(); + prune_stale_module_artifacts(&modules, &ramfs_dir) + .expect("Failed to prune stale module artifacts"); + + // サービスをビルド + let services_base_dir = manifest_dir.join("src/services"); + + for service in &services { + let output_dir = if service.fs_type == "initfs" { + &ramfs_dir + } else { + &fs_dir + }; + + build_service(service, &services_base_dir, output_dir) + .unwrap_or_else(|e| panic!("Failed to build service {}: {}", service.name, e)); + } + + // カーネルモジュールをビルド(initfs/Modules/*.cext) + let modules_base_dir = manifest_dir.join("src/modules"); + for module in &modules { + build_module(module, &modules_base_dir, &ramfs_dir) + .unwrap_or_else(|e| panic!("Failed to build module {}: {}", module.name, e)); + } + + // アプリケーションをビルド + let apps_dir = manifest_dir.join("src/apps"); + let applications_dir = fs_dir.join("applications"); + if apps_dir.is_dir() { + println!("Building applications"); + build_apps(&apps_dir, &applications_dir, "elf"); + + // Clean up build artifacts from applications + for entry in fs::read_dir(&applications_dir).unwrap_or_else(|_| { + panic!("Failed to read applications dir") + }) { + if let Ok(entry) = entry { + let path = entry.path(); + if path.is_dir() { + let target_dir = path.join("target"); + if target_dir.exists() { + if let Err(e) = fs::remove_dir_all(&target_dir) { + eprintln!("Warning: Failed to remove {}: {}", target_dir.display(), e); + } else { + println!("Cleaned up: {}", target_dir.display()); + } + } + } + } + } + } + + // ユーティリティコマンドをビルド + let utils_dir = manifest_dir.join("src/utils"); + let binaries_dir = fs_dir.join("bin"); + if utils_dir.is_dir() { + println!("Building utility commands"); + build_utils(&utils_dir, &binaries_dir); + } + + ensure_busybox_binary(&fs_dir).expect("Failed to ensure busybox binary"); + + // ドライバをビルド + let drivers_dir = manifest_dir.join("src/drivers"); + let drivers_binaries_dir = binaries_dir.join("drivers"); + let driver_autostart_entries = if drivers_dir.is_dir() { + println!("Building drivers"); + build_drivers(&drivers_dir, &drivers_binaries_dir) + } else { + panic!("Drivers directory not found: {}", drivers_dir.display()); + }; + + // driver.service が参照する自動起動ドライバ一覧を生成 + let driver_autostart_path = fs_dir.join("config").join("drivers.list"); + match fs::write(&driver_autostart_path, driver_autostart_entries.join("\n")) { + Ok(_) => println!("Generated {}", driver_autostart_path.display()), + Err(e) => panic!( + "Failed to write critical config {}: {}", + driver_autostart_path.display(), + e + ), + } + // services.index に基づき、initfs 以外の autostart サービス一覧を生成 (Config/services.list) + let mut services_autostart_entries: Vec = Vec::new(); + for svc in &services { + if svc.autostart { + if svc.fs_type != "initfs" { + services_autostart_entries.push(format!("/system/services/{}.service", svc.name)); + } else { + // 場合によっては initfs に autostart=true が設定されていることがある。 + // 開発者に分かるようにビルド時警告を出す。 + println!("cargo:warning=Autostart service '{}' skipped for services.list because fs='initfs'. If you want it on ATA, set fs = 'ata' in src/services/index.toml", svc.name); + } + } + } + let services_autostart_path = fs_dir.join("config").join("services.list"); + match fs::write( + &services_autostart_path, + services_autostart_entries.join("\n"), + ) { + Ok(_) => println!("Generated {}", services_autostart_path.display()), + Err(e) => panic!( + "Failed to write critical config {}: {}", + services_autostart_path.display(), + e + ), + } + + // initfs イメージを生成 + let initfs_image_path = out_dir.join("initfs.ext2"); + + create_initfs_image(&ramfs_dir, &initfs_image_path).expect("Failed to create initfs image"); + + // ext2 イメージを生成 + let ext2_image_path = out_dir.join("rootfs.ext2"); + create_ext2_image(&fs_dir, &ext2_image_path).expect("Failed to create ext2 image"); + + // make_image.sh を実行(UEFIイメージ作成) + let mkimage_script = manifest_dir.join("scripts/make_image.sh"); + if mkimage_script.exists() { + let _ = std::process::Command::new(mkimage_script).status(); + } + + println!("Build completed successfully!"); + println!(" ramfs/ -> {}", initfs_image_path.display()); + println!(" fs/ -> {}", ext2_image_path.display()); +} diff --git a/builders/apps.rs b/builders/apps.rs new file mode 100644 index 0000000..4a55c81 --- /dev/null +++ b/builders/apps.rs @@ -0,0 +1,428 @@ +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; + +use super::utils::{emit_rerun_if_changed, find_binary_in_dir, find_target_spec}; + +/// アプリケーションをビルドして指定ディレクトリにコピー +pub fn build_apps(apps_dir: &Path, output_dir: &Path, _extension: &str) { + println!("cargo:rerun-if-changed={}", apps_dir.display()); + + let entries = match fs::read_dir(apps_dir) { + Ok(entries) => entries, + Err(_) => return, + }; + + // START_TEST_APP環境変数をチェック + let _run_tests = std::env::var("START_TEST_APP") + .map(|v| v == "true" || v == "1") + .unwrap_or(false); + + for entry in entries.flatten() { + let path = entry.path(); + if !path.is_dir() { + continue; + } + + let app_name = path.file_name().unwrap().to_string_lossy().to_string(); + + /* + // testsディレクトリはSTART_TEST_APP=trueの場合のみビルド + if app_name == "ViewKit" && !run_tests { + println!("Skipping tests app (START_TEST_APP not enabled)"); + continue; + } + */ + + let cargo_toml = path.join("Cargo.toml"); + if !cargo_toml.exists() { + continue; + } + + println!("Building app: {}", app_name); + + // アプリのソースファイルを明示的に監視 + println!("cargo:rerun-if-changed={}", cargo_toml.display()); + let src_dir = path.join("src"); + if src_dir.is_dir() { + emit_rerun_if_changed(&src_dir); + } + let resources_dir = path.join("resources"); + if resources_dir.is_dir() { + emit_rerun_if_changed(&resources_dir); + } + + // カスタムターゲットファイルを探す(アプリディレクトリ内の .json を優先) + let target_spec = find_target_spec(&path); + let uses_json_target = target_spec + .as_deref() + .map(|t| t.ends_with(".json")) + .unwrap_or(false); + + // .cargo/config.toml にtargetが設定されているか確認 + let cargo_config = path.join(".cargo/config.toml"); + let cargo_config_text = fs::read_to_string(&cargo_config).ok(); + let has_config_target = cargo_config_text + .as_deref() + .map(|s| s.contains("[build]") && s.contains("target")) + .unwrap_or(false); + let config_uses_json_target = cargo_config_text + .as_deref() + .map(|s| s.contains(".json")) + .unwrap_or(false); + let uses_json_target = uses_json_target || config_uses_json_target; + + // cargoでアプリをビルド + let mut cmd = Command::new("cargo"); + cmd.args(["build", "--release"]); + if uses_json_target { + cmd.args(["-Z", "json-target-spec"]); + } + + // 外側のビルド環境変数をクリアして干渉を防ぐ + for key in &[ + "RUSTFLAGS", + "CARGO_ENCODED_RUSTFLAGS", + "CARGO_TARGET_DIR", + "CARGO_BUILD_TARGET", + "CARGO_MAKEFLAGS", + "__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", + "CARGO_BUILD_RUSTC", + "RUSTC", + "RUSTC_WRAPPER", + "RUSTC_WORKSPACE_WRAPPER", + ] { + cmd.env_remove(key); + } + + // .cargo/config.toml にtargetがある場合は --target を渡さない + if has_config_target { + println!(" Using target from .cargo/config.toml"); + } else if let Some(target) = &target_spec { + cmd.arg("--target").arg(target); + println!(" Using target: {}", target); + } else { + // デフォルトは ELF (for newlib) + let default_target = "x86_64-unknown-none"; + cmd.arg("--target").arg(default_target); + println!(" Using default target: {}", default_target); + } + + let output = cmd.current_dir(&path).output(); + + match output { + Ok(output) => { + if output.status.success() { + // ビルド成果物を探す + let target_dir = path.join("target"); + let target_name = if has_config_target { + Some("x86_64-mochios".to_string()) + } else if let Some(p) = &target_spec { + Path::new(p) + .file_stem() + .map(|s| s.to_string_lossy().to_string()) + } else { + Some("x86_64-unknown-none".to_string()) + }; + + if let Some(elf_path) = find_built_binary(&target_dir, target_name.as_deref()) { + let app_bundle_dir = output_dir.join(format!("{}.app", app_name)); + if let Err(e) = fs::create_dir_all(&app_bundle_dir) { + println!( + "cargo:warning=Failed to create app bundle dir for {}: {}", + app_name, e + ); + continue; + } + + let dest = app_bundle_dir.join("entry.elf"); + if let Err(e) = fs::copy(&elf_path, &dest) { + println!( + "cargo:warning=Failed to copy app entry for {}: {}", + app_name, e + ); + } else { + println!( + "Copied {} entry to {} (from {})", + app_name, + dest.display(), + elf_path.display() + ); + } + + let about_src = path.join("about.toml"); + let about_dest = app_bundle_dir.join("about.toml"); + if about_src.exists() { + if let Err(e) = fs::copy(&about_src, &about_dest) { + println!( + "cargo:warning=Failed to copy about.toml for {}: {}", + app_name, e + ); + } + } else { + println!( + "cargo:warning=about.toml not found for app {} ({})", + app_name, + about_src.display() + ); + } + + for icon_file in ["icon.png", "icon.jpeg", "icon.jpg"] { + let icon_src = path.join(icon_file); + if icon_src.exists() { + let icon_dest = app_bundle_dir.join(icon_file); + if let Err(e) = fs::copy(&icon_src, &icon_dest) { + println!( + "cargo:warning=Failed to copy {} for {}: {}", + icon_file, app_name, e + ); + } + break; + } + } + + for res_dir_name in ["resources", "resource"] { + let res_src = path.join(res_dir_name); + if res_src.is_dir() { + let res_dest = app_bundle_dir.join("resources"); + if let Err(e) = copy_dir_recursive(&res_src, &res_dest) { + println!( + "cargo:warning=Failed to copy resources for {}: {}", + app_name, e + ); + } + break; + } + } + + // ランタイム資産(テーマ等)を AppService 配下へ配置 + let themes_src = path.join("src").join("components"); + if themes_src.is_dir() { + let themes_dest = app_bundle_dir.join("components"); + if let Err(e) = copy_dir_recursive(&themes_src, &themes_dest) { + println!( + "cargo:warning=Failed to copy components for {}: {}", + app_name, e + ); + } + } + } else { + println!("cargo:warning=Built binary not found for {}", app_name); + } + } else { + println!("cargo:warning=Failed to build app: {}", app_name); + // エラー出力を表示 + if !output.stderr.is_empty() { + let stderr = String::from_utf8_lossy(&output.stderr); + for line in stderr.lines().take(10) { + println!("cargo:warning= {}", line); + } + } + } + } + Err(e) => { + println!( + "cargo:warning=Failed to execute cargo for {}: {}", + app_name, e + ); + } + } + } +} + +/// ユーティリティコマンド (`src/utils/`) をビルドして `output_dir` に `{name}.elf` としてコピー +pub fn build_utils(utils_dir: &Path, output_dir: &Path) { + println!("cargo:rerun-if-changed={}", utils_dir.display()); + let cargo_toml = utils_dir.join("Cargo.toml"); + if !cargo_toml.exists() { + return; + } + + println!("cargo:rerun-if-changed={}", cargo_toml.display()); + let src_dir = utils_dir.join("src"); + if src_dir.is_dir() { + emit_rerun_if_changed(&src_dir); + } + + if let Err(e) = fs::create_dir_all(output_dir) { + println!("cargo:warning=Failed to create bin dir: {}", e); + return; + } + + let mut cmd = Command::new("cargo"); + cmd.args(["build", "--release", "-Z", "json-target-spec"]); + + for key in &[ + "RUSTFLAGS", + "CARGO_ENCODED_RUSTFLAGS", + "CARGO_TARGET_DIR", + "CARGO_BUILD_TARGET", + "CARGO_MAKEFLAGS", + "__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", + "CARGO_BUILD_RUSTC", + "RUSTC", + "RUSTC_WRAPPER", + "RUSTC_WORKSPACE_WRAPPER", + ] { + cmd.env_remove(key); + } + + // Determine project root (look for ramfs/lib) + let mut project_root = utils_dir.to_path_buf(); + while project_root.parent().is_some() { + if project_root.join("ramfs").join("lib").exists() { + break; + } + project_root.pop(); + } + let libs_dir = project_root.join("ramfs").join("lib"); + + if libs_dir.exists() { + // Set RUSTFLAGS so that utilities are linked with the same crt0 and libs + let ld = libs_dir.display(); + let rustflags = format!( + "-C link-arg={0}/crt0.o -C link-arg=-static -C link-arg=-no-pie -C link-arg=--allow-multiple-definition -L {0} -C link-arg={0}/libunwind.a -C link-arg={0}/libextra.a -C link-arg={0}/libc.a -C link-arg={0}/libg.a -C link-arg={0}/libm.a -C link-arg={0}/libnosys.a -C link-arg={0}/libgcc_s.a", + ld + ); + cmd.env("RUSTFLAGS", rustflags); + } + + // Ensure build target is set to the repository's x86_64-mochios.json so output matches expectations + let target_json = project_root.join("x86_64-mochios.json"); + if target_json.exists() { + cmd.arg("--target") + .arg(target_json.to_string_lossy().to_string()); + } else { + cmd.arg("--target").arg("x86_64-mochios"); + } + + println!("Building utils from {}", utils_dir.display()); + let output = cmd.current_dir(utils_dir).output(); + + match output { + Ok(output) => { + if !output.status.success() { + println!("cargo:warning=Failed to build utils"); + let stderr = String::from_utf8_lossy(&output.stderr); + for line in stderr.lines().take(20) { + println!("cargo:warning= {}", line); + } + return; + } + // 全てのELFバイナリを探してコピー + let release_dir = utils_dir.join("target/x86_64-mochios/release"); + let binaries = find_all_binaries(&release_dir); + if binaries.is_empty() { + println!( + "cargo:warning=No binaries found in {}", + release_dir.display() + ); + } + for elf_path in binaries { + let name = elf_path.file_name().unwrap().to_string_lossy(); + let dest = output_dir.join(format!("{}.elf", name)); + if let Err(e) = fs::copy(&elf_path, &dest) { + println!("cargo:warning=Failed to copy {}.elf: {}", name, e); + } else { + println!("Copied {}.elf to {}", name, output_dir.display()); + } + } + } + Err(e) => { + println!("cargo:warning=Failed to execute cargo for utils: {}", e); + } + } +} + +/// ディレクトリ内の全ELFバイナリを返す(拡張子なし・libでない・.dでない) +fn find_all_binaries(dir: &Path) -> Vec { + let mut result = Vec::new(); + let entries = match fs::read_dir(dir) { + Ok(e) => e, + Err(_) => return result, + }; + for entry in entries.flatten() { + let path = entry.path(); + if !path.is_file() { + continue; + } + let filename = path.file_name().unwrap().to_string_lossy().to_string(); + if !filename.starts_with("lib") + && !filename.ends_with(".d") + && !filename.ends_with(".rlib") + && !filename.ends_with(".so") + && !filename.contains('.') + && is_elf(&path) + { + result.push(path); + } + } + result +} + +/// ファイルがELFマジックバイトで始まるか確認 +fn is_elf(path: &Path) -> bool { + if let Ok(mut f) = fs::File::open(path) { + use std::io::Read; + let mut magic = [0u8; 4]; + if f.read_exact(&mut magic).is_ok() { + return magic == [0x7f, b'E', b'L', b'F']; + } + } + false +} + +fn find_built_binary(target_dir: &Path, target_name: Option<&str>) -> Option { + // カスタムターゲットが指定されている場合はそのディレクトリを優先 + if let Some(target) = target_name { + let custom_target = target_dir.join(format!("{}/release", target)); + if custom_target.is_dir() { + if let Some(binary) = find_binary_in_dir(&custom_target) { + return Some(binary); + } + } + } + + // x86_64-mochios/release/ を優先的に探す + let custom_target = target_dir.join("x86_64-mochios/release"); + if custom_target.is_dir() { + if let Some(binary) = find_binary_in_dir(&custom_target) { + return Some(binary); + } + } + + // 通常のrelease/を探す + let release_dir = target_dir.join("release"); + if release_dir.is_dir() { + if let Some(binary) = find_binary_in_dir(&release_dir) { + return Some(binary); + } + } + + None +} + +fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<(), String> { + fs::create_dir_all(dst).map_err(|e| format!("Failed to create {}: {}", dst.display(), e))?; + for entry in + fs::read_dir(src).map_err(|e| format!("Failed to read {}: {}", src.display(), e))? + { + let entry = + entry.map_err(|e| format!("Failed to read entry in {}: {}", src.display(), e))?; + let src_path = entry.path(); + let dst_path = dst.join(entry.file_name()); + if src_path.is_dir() { + copy_dir_recursive(&src_path, &dst_path)?; + } else { + fs::copy(&src_path, &dst_path).map_err(|e| { + format!( + "Failed to copy {} to {}: {}", + src_path.display(), + dst_path.display(), + e + ) + })?; + } + } + Ok(()) +} diff --git a/builders/drivers.rs b/builders/drivers.rs new file mode 100644 index 0000000..a36d286 --- /dev/null +++ b/builders/drivers.rs @@ -0,0 +1,341 @@ +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; + +use super::utils::{emit_rerun_if_changed, find_binary_in_dir, find_target_spec}; + +/// ドライバクレート (`src/drivers/*`) をビルドして `fs/bin/drivers/*.elf` に配置する。 +/// +/// 戻り値は `driver.service` が起動するドライバパス(例: `bin/drivers/usb3.0.elf`)。 +pub fn build_drivers(drivers_dir: &Path, output_dir: &Path) -> Vec { + println!("cargo:rerun-if-changed={}", drivers_dir.display()); + + let mut autostart_entries = Vec::new(); + + let entries = match fs::read_dir(drivers_dir) { + Ok(entries) => entries, + Err(_) => return autostart_entries, + }; + + if let Err(e) = fs::create_dir_all(output_dir) { + println!( + "cargo:warning=Failed to create drivers output dir {}: {}", + output_dir.display(), + e + ); + return autostart_entries; + } + + for entry in entries.flatten() { + let path = entry.path(); + if !path.is_dir() { + continue; + } + + let cargo_toml = path.join("Cargo.toml"); + if !cargo_toml.exists() { + continue; + } + + let driver_dir_name = path + .file_name() + .map(|n| n.to_string_lossy().to_string()) + .unwrap_or_else(|| "unknown".to_string()); + let driver_output_name = normalize_driver_name(&driver_dir_name); + let expected_bin_name = + parse_driver_binary_name(&cargo_toml).or_else(|| Some(driver_dir_name.clone())); + + println!( + "Building driver: {} -> {}.elf", + driver_dir_name, driver_output_name + ); + + println!("cargo:rerun-if-changed={}", cargo_toml.display()); + let src_dir = path.join("src"); + if src_dir.is_dir() { + emit_rerun_if_changed(&src_dir); + } + + let target_spec = find_target_spec(&path); + let mut uses_json_target = target_spec + .as_deref() + .map(|t| t.ends_with(".json")) + .unwrap_or(false); + + let cargo_config = path.join(".cargo/config.toml"); + let cargo_config_text = fs::read_to_string(&cargo_config).ok(); + let config_target = cargo_config_text + .as_deref() + .and_then(parse_build_target_from_cargo_config_text); + let has_config_target = config_target.is_some(); + let config_uses_json_target = cargo_config_text + .as_deref() + .and_then(parse_build_target_from_cargo_config_text) + .map(|target| target.ends_with(".json")) + .unwrap_or(false); + uses_json_target = uses_json_target || config_uses_json_target; + + let mut cmd = Command::new("cargo"); + cmd.args(["build", "--release"]); + if uses_json_target { + cmd.args(["-Z", "json-target-spec"]); + } + + for key in &[ + "RUSTFLAGS", + "CARGO_ENCODED_RUSTFLAGS", + "CARGO_TARGET_DIR", + "CARGO_BUILD_TARGET", + "CARGO_MAKEFLAGS", + "__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", + "CARGO_BUILD_RUSTC", + "RUSTC", + "RUSTC_WRAPPER", + "RUSTC_WORKSPACE_WRAPPER", + ] { + cmd.env_remove(key); + } + + if has_config_target { + println!(" Using target from .cargo/config.toml"); + } else if let Some(target) = &target_spec { + cmd.arg("--target").arg(target); + println!(" Using target: {}", target); + } else { + let default_target = "x86_64-unknown-none"; + cmd.arg("--target").arg(default_target); + println!(" Using default target: {}", default_target); + } + + let output = cmd.current_dir(&path).output(); + + match output { + Ok(output) => { + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + let err_tail = tail_from_char_boundary(&stderr, 4000); + let out_tail = tail_from_char_boundary(&stdout, 2000); + panic!( + "Failed to build driver {}: status={} STDERR={} STDOUT={}", + driver_dir_name, output.status, err_tail, out_tail + ); + } + + let target_dir = path.join("target"); + let target_name = if has_config_target { + config_target + .or_else(|| { + target_spec + .as_ref() + .and_then(|p| Path::new(p).file_stem()) + .map(|s| s.to_string_lossy().to_string()) + }) + .or_else(|| Some("x86_64-unknown-none".to_string())) + } else if let Some(p) = &target_spec { + Path::new(p) + .file_stem() + .map(|s| s.to_string_lossy().to_string()) + } else { + Some("x86_64-unknown-none".to_string()) + }; + + if let Some(elf_path) = find_built_binary( + &target_dir, + target_name.as_deref(), + expected_bin_name.as_deref(), + ) { + let dest_name = format!("{}.elf", driver_output_name); + let dest = output_dir.join(&dest_name); + if let Err(e) = fs::copy(&elf_path, &dest) { + println!( + "cargo:warning=Failed to copy {} to {}: {}", + elf_path.display(), + dest.display(), + e + ); + } else { + println!( + "Copied {} to {} (from {})", + dest_name, + output_dir.display(), + elf_path.display() + ); + autostart_entries.push(format!("/bin/drivers/{}", dest_name)); + } + } else { + panic!("Built driver binary not found for {}", driver_dir_name); + } + } + Err(e) => { + panic!( + "Failed to execute cargo for driver {}: {}", + driver_dir_name, e + ); + } + } + } + + autostart_entries.sort(); + autostart_entries +} + +fn tail_from_char_boundary(s: &str, max_bytes: usize) -> &str { + if s.len() <= max_bytes { + return s; + } + + let target_start = s.len().saturating_sub(max_bytes); + let byte_index = s + .char_indices() + .rev() + .find(|(idx, _)| *idx <= target_start) + .map(|(idx, _)| idx) + .unwrap_or(0); + &s[byte_index..] +} + +fn parse_build_target_from_cargo_config_text(content: &str) -> Option { + let mut in_build = false; + + for raw_line in content.lines() { + let line = raw_line.trim(); + if line.is_empty() || line.starts_with('#') { + continue; + } + + if line.starts_with('[') && line.ends_with(']') { + in_build = line == "[build]"; + continue; + } + + if !in_build { + continue; + } + + let Some((key_raw, value_raw)) = line.split_once('=') else { + continue; + }; + if key_raw.trim() != "target" { + continue; + } + + let mut comment_stripped = String::with_capacity(value_raw.len()); + let mut in_single_quote = false; + let mut in_double_quote = false; + for ch in value_raw.chars() { + match ch { + '\'' if !in_double_quote => { + in_single_quote = !in_single_quote; + comment_stripped.push(ch); + } + '"' if !in_single_quote => { + in_double_quote = !in_double_quote; + comment_stripped.push(ch); + } + '#' if !in_single_quote && !in_double_quote => break, + _ => comment_stripped.push(ch), + } + } + + let value_no_comment = comment_stripped.trim(); + let value = value_no_comment.trim_matches('"').trim_matches('\'').trim(); + if !value.is_empty() { + return Some(value.to_string()); + } + } + + None +} + +fn normalize_driver_name(driver_dir_name: &str) -> String { + // 例: usb3_0 -> usb3.0 + driver_dir_name.replace('_', ".") +} + +fn parse_driver_binary_name(cargo_toml: &Path) -> Option { + let content = fs::read_to_string(cargo_toml).ok()?; + let mut in_bin = false; + let mut in_package = false; + let mut package_name: Option = None; + + for line in content.lines() { + let line = line.trim(); + if line.starts_with('[') && line.ends_with(']') { + in_bin = line == "[[bin]]"; + in_package = line == "[package]"; + continue; + } + + let Some((lhs, rhs)) = line.split_once('=') else { + continue; + }; + if lhs.trim() != "name" { + continue; + } + + let name = rhs + .trim() + .trim_matches('"') + .trim_matches('\'') + .trim() + .to_string(); + if name.is_empty() { + continue; + } + + if in_bin { + return Some(name); + } + if in_package && package_name.is_none() { + package_name = Some(name); + } + } + + package_name +} + +fn find_binary_in_dir_prefer(dir: &Path, preferred: Option<&str>) -> Option { + if let Some(name) = preferred { + let direct = dir.join(name); + if direct.is_file() { + return Some(direct); + } + let alt = dir.join(name.replace('-', "_")); + if alt.is_file() { + return Some(alt); + } + } + find_binary_in_dir(dir) +} + +fn find_built_binary( + target_dir: &Path, + target_name: Option<&str>, + preferred_bin: Option<&str>, +) -> Option { + if let Some(target) = target_name { + let custom_target = target_dir.join(format!("{}/release", target)); + if custom_target.is_dir() { + if let Some(binary) = find_binary_in_dir_prefer(&custom_target, preferred_bin) { + return Some(binary); + } + } + } + + let custom_target = target_dir.join("x86_64-mochios/release"); + if custom_target.is_dir() { + if let Some(binary) = find_binary_in_dir_prefer(&custom_target, preferred_bin) { + return Some(binary); + } + } + + let release_dir = target_dir.join("release"); + if release_dir.is_dir() { + if let Some(binary) = find_binary_in_dir_prefer(&release_dir, preferred_bin) { + return Some(binary); + } + } + + None +} diff --git a/builders/fs_image.rs b/builders/fs_image.rs new file mode 100644 index 0000000..a6b9a47 --- /dev/null +++ b/builders/fs_image.rs @@ -0,0 +1,396 @@ +use std::fs; +use std::path::Path; +use std::process::Command; + +use super::utils::emit_rerun_if_changed; + +/// ディレクトリ以下のファイルの合計バイト数を再帰的に計算する +fn compute_content_size(dir: &Path) -> u64 { + let mut total = 0u64; + if let Ok(rd) = fs::read_dir(dir) { + for entry in rd.flatten() { + let path = entry.path(); + if path.is_dir() { + total += compute_content_size(&path); + } else if let Ok(meta) = path.metadata() { + total += meta.len(); + } + } + } + total +} + +/// コンテンツサイズからext2イメージのブロック数を計算する +/// オーバーヘッド 25% + ext2メタデータ用 10MB を加算し、最小 32MB を保証する +fn blocks_for_dir(dir: &Path, block_size: u64) -> u64 { + let content = compute_content_size(dir); + let needed = ((content * 5 / 4) + 10 * 1024 * 1024).max(32 * 1024 * 1024); + (needed + block_size - 1) / block_size +} + +/// InitFS 用のブロック数を計算する +/// 実行時最低限の内容だけを格納するため、rootfs より小さめの余裕にする +fn blocks_for_initfs_dir(dir: &Path, block_size: u64) -> u64 { + let content = compute_content_size(dir); + let needed = ((content.saturating_mul(11) / 10) + 4 * 1024 * 1024).max(16 * 1024 * 1024); + (needed + block_size - 1) / block_size +} + +fn should_skip_initfs_library(path: &Path) -> bool { + matches!( + path.extension().and_then(|e| e.to_str()), + Some("a") | Some("o") + ) +} + +fn copy_initfs_runtime_tree(src: &Path, dst: &Path, in_libraries: bool) -> Result { + let mut copied_any = false; + + for entry in + fs::read_dir(src).map_err(|e| format!("Failed to read {}: {}", src.display(), e))? + { + let entry = + entry.map_err(|e| format!("Failed to read entry in {}: {}", src.display(), e))?; + let src_path = entry.path(); + let name = entry.file_name(); + let dst_path = dst.join(&name); + let name_str = name.to_string_lossy(); + let child_in_libraries = in_libraries || name_str == "lib"; + let src_meta = fs::symlink_metadata(&src_path) + .map_err(|e| format!("Failed to stat {}: {}", src_path.display(), e))?; + + if src_meta.is_dir() { + let child_copied = copy_initfs_runtime_tree(&src_path, &dst_path, child_in_libraries)?; + if child_copied { + if !copied_any { + fs::create_dir_all(dst) + .map_err(|e| format!("Failed to create {}: {}", dst.display(), e))?; + } + copied_any = true; + } + } else { + if child_in_libraries && should_skip_initfs_library(&src_path) { + continue; + } + + if !copied_any { + fs::create_dir_all(dst) + .map_err(|e| format!("Failed to create {}: {}", dst.display(), e))?; + copied_any = true; + } + + if src_meta.file_type().is_symlink() { + let link_target = fs::read_link(&src_path) + .map_err(|e| format!("Failed to read symlink {}: {}", src_path.display(), e))?; + #[cfg(unix)] + { + std::os::unix::fs::symlink(&link_target, &dst_path).map_err(|e| { + format!( + "Failed to replicate symlink {} -> {}: {}", + dst_path.display(), + link_target.display(), + e + ) + })?; + } + #[cfg(not(unix))] + { + let resolved = if link_target.is_absolute() { + link_target.clone() + } else { + src_path + .parent() + .unwrap_or_else(|| Path::new(".")) + .join(&link_target) + }; + fs::copy(&resolved, &dst_path).map_err(|e| { + format!( + "Failed to copy symlink target {} to {}: {}", + resolved.display(), + dst_path.display(), + e + ) + })?; + } + } else { + fs::copy(&src_path, &dst_path).map_err(|e| { + format!( + "Failed to copy {} to {}: {}", + src_path.display(), + dst_path.display(), + e + ) + })?; + } + } + } + + if !copied_any && dst.exists() { + let _ = fs::remove_dir(dst); + } + + Ok(copied_any) +} + +/// InitFS (ramfs) 用のext2イメージを生成 +pub fn create_initfs_image(ramfs_dir: &Path, output_path: &Path) -> Result<(), String> { + println!("Creating initfs ext2 image from {}", ramfs_dir.display()); + + emit_rerun_if_changed(ramfs_dir); + + // サービスのビルドでは ramfs/lib が必要だが、InitFS 本体には + // 実行時に不要な静的成果物 (.a/.o) を含めない + let staging_dir = output_path + .parent() + .unwrap_or_else(|| Path::new(".")) + .join("initfs-runtime"); + if staging_dir.exists() { + fs::remove_dir_all(&staging_dir) + .map_err(|e| format!("Failed to clean {}: {}", staging_dir.display(), e))?; + } + let copied_any = copy_initfs_runtime_tree(ramfs_dir, &staging_dir, false)?; + if !copied_any { + fs::create_dir_all(&staging_dir) + .map_err(|e| format!("Failed to create {}: {}", staging_dir.display(), e))?; + } + + let original_size = compute_content_size(ramfs_dir) / (1024 * 1024); + let runtime_size = compute_content_size(&staging_dir) / (1024 * 1024); + println!( + "initfs runtime payload: {} MB (from {} MB)", + runtime_size, original_size + ); + + let num_blocks = blocks_for_initfs_dir(&staging_dir, 4096); + println!( + "initfs: {} 4K-blocks ({} MB)", + num_blocks, + num_blocks * 4 / 1024 + ); + + // 既存ファイルを使い回すとサイズが縮まらない場合があるため、毎回作り直す + if output_path.exists() { + fs::remove_file(output_path).map_err(|e| { + format!( + "Failed to remove existing image {}: {}", + output_path.display(), + e + ) + })?; + } + + let status = Command::new("mke2fs") + .args(["-t", "ext2", "-b", "4096", "-m", "0", "-L", "initfs", "-d"]) + .arg(&staging_dir) + .arg(output_path) + .arg(num_blocks.to_string()) + .status(); + + let result = match status { + Ok(s) if s.success() => { + println!("Created initfs image at {}", output_path.display()); + Ok(()) + } + Ok(_) => Err("mke2fs failed while generating initfs image".to_string()), + Err(e) => Err(format!( + "Failed to execute mke2fs: {}. Please install e2fsprogs (mke2fs).", + e + )), + }; + + let _ = fs::remove_dir_all(&staging_dir); + result +} + +/// EXT2 ファイルシステムイメージを生成 +pub fn create_ext2_image(fs_dir: &Path, output_path: &Path) -> Result<(), String> { + println!("Creating ext2 filesystem image from {}", fs_dir.display()); + + emit_rerun_if_changed(fs_dir); + + let num_blocks = blocks_for_dir(fs_dir, 4096); + + println!( + "rootfs: {} 4K-blocks ({} MB)", + num_blocks, + num_blocks * 4 / 1024 + ); + + // 既存ファイルを使い回すとサイズが縮まらない場合があるため、毎回作り直す + if output_path.exists() { + fs::remove_file(output_path).map_err(|e| { + format!( + "Failed to remove existing image {}: {}", + output_path.display(), + e + ) + })?; + } + + let status = Command::new("mke2fs") + .args(["-t", "ext2", "-b", "4096", "-m", "0", "-L", "rootfs", "-d"]) + .arg(fs_dir) + .arg(output_path) + .arg(num_blocks.to_string()) + .status(); + + match status { + Ok(s) if s.success() => { + println!("Created ext2 image at {}", output_path.display()); + Ok(()) + } + Ok(_) => Err("mke2fs failed while generating ext2 image".to_string()), + Err(e) => Err(format!( + "Failed to execute mke2fs: {}. Please install e2fsprogs (mke2fs).", + e + )), + } +} + +/// fsディレクトリの標準レイアウトを作成 +pub fn setup_fs_layout(fs_dir: &Path, resources_src: &Path) -> Result<(), String> { + let dirs = [ + "system", // システム(カーネルやカーネルに関連するファイルを配置) + "applications", // ユーザーアプリケーションを配置 + "bin", // コマンドやユーティリティを配置 + "lib", // ライブラリを配置 + "mount", // マウントしたやつ配置 + "boot", // ブートローダー関連のファイルを配置 + "log", // ログを配置 + "home", // ユーザーディレクトリを配置 + "config", // 設定ファイルを配置 + "tmp", // 一時ファイルを配置 + "var", // 変動するデータを配置 + ]; + + for dir in &dirs { + let path = fs_dir.join(dir); + fs::create_dir_all(&path) + .map_err(|e| format!("Failed to create {}: {}", path.display(), e))?; + println!("Created directory: {}", path.display()); + } + + // src/resources/ の各サブディレクトリを対応する fs/ ディレクトリにコピー + // 例: src/resources/system/ → fs/system/ + // src/resources/config/ → fs/config/ + if resources_src.is_dir() { + for entry in fs::read_dir(resources_src) + .map_err(|e| format!("Failed to read resources dir: {}", e))? + { + let entry = entry.map_err(|e| format!("Failed to read resources entry: {}", e))?; + let src_path = entry.path(); + if !src_path.is_dir() { + continue; + } + let dir_name = entry.file_name(); + let dst_path = fs_dir.join(&dir_name); + if dst_path.exists() { + fs::remove_dir_all(&dst_path) + .map_err(|e| format!("Failed to clean {}: {}", dst_path.display(), e))?; + } + copy_dir_recursive(&src_path, &dst_path)?; + println!( + "Copied resources/{} -> {}", + dir_name.to_string_lossy(), + dst_path.display() + ); + } + } + + Ok(()) +} + +/// ディレクトリを再帰的にコピーする +fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<(), String> { + fs::create_dir_all(dst).map_err(|e| format!("Failed to create {}: {}", dst.display(), e))?; + + for entry in + fs::read_dir(src).map_err(|e| format!("Failed to read {}: {}", src.display(), e))? + { + let entry = + entry.map_err(|e| format!("Failed to read entry in {}: {}", src.display(), e))?; + let src_path = entry.path(); + let dst_path = dst.join(entry.file_name()); + + if src_path.is_dir() { + copy_dir_recursive(&src_path, &dst_path)?; + } else { + fs::copy(&src_path, &dst_path).map_err(|e| { + format!( + "Failed to copy {} to {}: {}", + src_path.display(), + dst_path.display(), + e + ) + })?; + println!("Copied: {} -> {}", src_path.display(), dst_path.display()); + } + } + + Ok(()) +} + +/// newlibライブラリをディレクトリにコピー +pub fn copy_newlib_libs(libc_dir: &Path, dest_dir: &Path) -> Result<(), String> { + fs::create_dir_all(dest_dir) + .map_err(|e| format!("Failed to create {}: {}", dest_dir.display(), e))?; + + // crt0.oをコピー + let crt0_src = libc_dir.join("crt0.o"); + let crt0_dest = dest_dir.join("crt0.o"); + fs::copy(&crt0_src, &crt0_dest) + .map_err(|e| format!("Failed to copy crt0.o to {}: {}", dest_dir.display(), e))?; + println!("Copied crt0.o to {}", dest_dir.display()); + + // 必須のライブラリ + let required_libs = ["libc.a", "libg.a", "libm.a", "libnosys.a"]; + for lib in &required_libs { + let src = libc_dir.join(lib); + let dest = dest_dir.join(lib); + fs::copy(&src, &dest).map_err(|e| { + format!( + "Failed to copy {} to {}: {}. Make sure newlib is built correctly.", + lib, + dest_dir.display(), + e + ) + })?; + println!( + "Copied {} to {} (from {})", + lib, + dest_dir.display(), + src.display() + ); + } + + // オプショナルなライブラリ(存在しない場合は空のアーカイブを作成) + let optional_libs = ["libgcc_s.a", "libunwind.a", "libextra.a"]; + for lib in &optional_libs { + let dest = dest_dir.join(lib); + let src = libc_dir.join(lib); + + if src.exists() { + // ソースが存在する場合はコピー + fs::copy(&src, &dest) + .map_err(|e| format!("Failed to copy {} to {}: {}", lib, dest_dir.display(), e))?; + println!("Copied {} to {}", lib, dest_dir.display()); + } else if !dest.exists() { + // ソースが存在しない場合は空のアーカイブを作成 + use std::process::Command; + let status = Command::new("ar") + .args(&["rcs", dest.to_str().unwrap()]) + .status() + .map_err(|e| format!("Failed to create empty archive for {}: {}", lib, e))?; + if !status.success() { + return Err(format!("Failed to create empty archive for {}", lib)); + } + println!( + "Created empty archive for {} at {}", + lib, + dest_dir.display() + ); + } + } + + Ok(()) +} diff --git a/builders/mod.rs b/builders/mod.rs new file mode 100644 index 0000000..7616663 --- /dev/null +++ b/builders/mod.rs @@ -0,0 +1,14 @@ +pub mod apps; +pub mod drivers; +pub mod fs_image; +pub mod modules; +pub mod newlib; +pub mod services; +pub mod utils; + +pub use apps::{build_apps, build_utils}; +pub use drivers::build_drivers; +pub use fs_image::{copy_newlib_libs, create_ext2_image, create_initfs_image, setup_fs_layout}; +pub use modules::{build_module, default_modules}; +pub use newlib::{build_newlib, build_user_libs}; +pub use services::{build_service, parse_service_index}; diff --git a/builders/modules.rs b/builders/modules.rs new file mode 100644 index 0000000..9420388 --- /dev/null +++ b/builders/modules.rs @@ -0,0 +1,167 @@ +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; + +use super::utils::{emit_rerun_if_changed, find_binary_in_dir}; + +#[derive(Debug, Clone)] +pub struct ModuleEntry { + pub name: &'static str, + pub dir: &'static str, + pub version: u16, + pub deps: &'static [&'static str], +} + +pub fn default_modules() -> Vec { + vec![ + ModuleEntry { + name: "disk", + dir: "disk", + version: 1, + deps: &[], + }, + ModuleEntry { + name: "fs", + dir: "fs", + version: 1, + deps: &["disk"], + }, + ] +} + +pub fn build_module( + module: &ModuleEntry, + modules_base_dir: &Path, + output_dir: &Path, +) -> Result<(), String> { + let module_dir = modules_base_dir.join(module.dir); + if !module_dir.exists() { + return Err(format!( + "Module directory not found: {}", + module_dir.display() + )); + } + + let cargo_toml = module_dir.join("Cargo.toml"); + if !cargo_toml.exists() { + return Err(format!("Cargo.toml not found for module {}", module.name)); + } + + println!("Building module: {}", module.name); + println!("cargo:rerun-if-changed={}", cargo_toml.display()); + let src_dir = module_dir.join("src"); + if src_dir.is_dir() { + emit_rerun_if_changed(&src_dir); + } + + let mut cmd = Command::new("cargo"); + cmd.args(["build", "--release"]); + for key in &[ + "RUSTFLAGS", + "CARGO_ENCODED_RUSTFLAGS", + "CARGO_TARGET_DIR", + "CARGO_BUILD_TARGET", + "CARGO_MAKEFLAGS", + "__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", + "CARGO_BUILD_RUSTC", + "RUSTC", + "RUSTC_WRAPPER", + "RUSTC_WORKSPACE_WRAPPER", + ] { + cmd.env_remove(key); + } + + let output = cmd + .current_dir(&module_dir) + .output() + .map_err(|e| format!("Failed to run cargo for module {}: {}", module.name, e))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + return Err(format!( + "Failed to build module {}: status={} STDERR={} STDOUT={}", + module.name, output.status, stderr, stdout + )); + } + + let target_dir = module_dir.join("target"); + let binary_path = find_module_binary(&target_dir).ok_or_else(|| { + format!( + "Built binary not found for module {} under {}", + module.name, + target_dir.display() + ) + })?; + + let modules_out_dir = output_dir.join("Modules"); + fs::create_dir_all(&modules_out_dir) + .map_err(|e| format!("Failed to create {}: {}", modules_out_dir.display(), e))?; + let cext_path = modules_out_dir.join(format!("{}.cext", module.name)); + build_cext(module, &binary_path, &cext_path)?; + + println!("Generated {}", cext_path.display()); + Ok(()) +} + +fn find_module_binary(target_dir: &Path) -> Option { + for profile in &["release", "debug"] { + let dir = target_dir.join(format!("x86_64-mochios/{}", profile)); + if dir.is_dir() { + if let Some(binary) = find_binary_in_dir(&dir) { + return Some(binary); + } + } + let dir = target_dir.join(profile); + if dir.is_dir() { + if let Some(binary) = find_binary_in_dir(&dir) { + return Some(binary); + } + } + } + None +} + +fn build_cext(module: &ModuleEntry, elf_path: &Path, cext_path: &Path) -> Result<(), String> { + const MAGIC: [u8; 4] = *b"MCEX"; + const ABI_VERSION: u16 = 1; + const FIXED_HEADER_SIZE: u32 = 32; + + let elf = + fs::read(elf_path).map_err(|e| format!("Failed to read {}: {}", elf_path.display(), e))?; + let name = module.name.as_bytes(); + if name.len() > u16::MAX as usize { + return Err(format!("Module name too long: {}", module.name)); + } + + let mut metadata = Vec::new(); + metadata.extend_from_slice(name); + for dep in module.deps { + let dep_bytes = dep.as_bytes(); + if dep_bytes.len() > u16::MAX as usize { + return Err(format!("Dependency name too long: {}", dep)); + } + metadata.extend_from_slice(&(dep_bytes.len() as u16).to_le_bytes()); + metadata.extend_from_slice(dep_bytes); + } + + let header_size = FIXED_HEADER_SIZE + .checked_add(u32::try_from(metadata.len()).map_err(|_| "metadata too large".to_string())?) + .ok_or_else(|| "header size overflow".to_string())?; + + let mut out = Vec::with_capacity(header_size as usize + elf.len()); + out.extend_from_slice(&MAGIC); + out.extend_from_slice(&ABI_VERSION.to_le_bytes()); + out.extend_from_slice(&module.version.to_le_bytes()); + out.extend_from_slice(&(name.len() as u16).to_le_bytes()); + out.extend_from_slice(&(module.deps.len() as u16).to_le_bytes()); + out.extend_from_slice(&header_size.to_le_bytes()); + out.extend_from_slice(&(elf.len() as u64).to_le_bytes()); + out.extend_from_slice(&0u64.to_le_bytes()); + out.extend_from_slice(&metadata); + out.extend_from_slice(&elf); + + fs::write(cext_path, out) + .map_err(|e| format!("Failed to write {}: {}", cext_path.display(), e))?; + Ok(()) +} diff --git a/builders/newlib.rs b/builders/newlib.rs new file mode 100644 index 0000000..16d34d3 --- /dev/null +++ b/builders/newlib.rs @@ -0,0 +1,327 @@ +use num_cpus; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; + +/// Well-known paths where cross-compiler toolchains are commonly installed. +const EXTRA_TOOL_PATHS: &[&str] = &[ + "/home/linuxbrew/.linuxbrew/bin", + "/usr/local/bin", + "/opt/homebrew/bin", +]; + +fn tool_exists(name: &str) -> bool { + // First try via PATH as-is + if Command::new(name).arg("--version").output().is_ok() { + return true; + } + // Then probe well-known locations (needed when launched from IDEs that + // inherit a stripped-down environment without linuxbrew in PATH) + EXTRA_TOOL_PATHS.iter().any(|dir| { + let full = Path::new(dir).join(name); + Command::new(&full).arg("--version").output().is_ok() + }) +} + +/// Return the full path to a tool, checking well-known locations before name-only fallback. +fn find_tool(name: &str) -> String { + if Command::new(name).arg("--version").output().is_ok() { + return name.to_string(); + } + for dir in EXTRA_TOOL_PATHS { + let full = Path::new(dir).join(name); + if Command::new(&full).arg("--version").output().is_ok() { + return full.to_string_lossy().into_owned(); + } + } + name.to_string() +} + +fn apply_cross_target_tools(cmd: &mut Command) { + cmd.env("CC_FOR_TARGET", find_tool("x86_64-elf-gcc")) + .env("CXX_FOR_TARGET", find_tool("x86_64-elf-g++")) + .env("AR_FOR_TARGET", find_tool("x86_64-elf-ar")) + .env("AS_FOR_TARGET", find_tool("x86_64-elf-as")) + .env("LD_FOR_TARGET", find_tool("x86_64-elf-ld")) + .env("NM_FOR_TARGET", find_tool("x86_64-elf-nm")) + .env("RANLIB_FOR_TARGET", find_tool("x86_64-elf-ranlib")) + .env("STRIP_FOR_TARGET", find_tool("x86_64-elf-strip")) + .env("OBJCOPY_FOR_TARGET", find_tool("x86_64-elf-objcopy")) + .env("OBJDUMP_FOR_TARGET", find_tool("x86_64-elf-objdump")) + .env("READELF_FOR_TARGET", find_tool("x86_64-elf-readelf")); +} + +fn apply_host_target_tool_fallback(cmd: &mut Command) { + cmd.env("CC_FOR_TARGET", "gcc") + .env("CXX_FOR_TARGET", "g++") + .env("AR_FOR_TARGET", "ar") + .env("AS_FOR_TARGET", "as") + .env("LD_FOR_TARGET", "ld") + .env("NM_FOR_TARGET", "nm") + .env("RANLIB_FOR_TARGET", "ranlib") + .env("STRIP_FOR_TARGET", "strip") + .env("OBJCOPY_FOR_TARGET", "objcopy") + .env("OBJDUMP_FOR_TARGET", "objdump") + .env("READELF_FOR_TARGET", "readelf"); +} + +pub fn build_newlib(src_dir: &Path) { + let target = env::var("TARGET").expect("TARGET not set"); + let profile = env::var("PROFILE").expect("PROFILE not set"); + + let target_dir = PathBuf::from(env::var("CARGO_TARGET_DIR").unwrap_or("target".to_string())); + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + + // Resolve absolute target dir + let abs_target_dir = if target_dir.is_absolute() { + target_dir + } else { + manifest_dir.join(target_dir) + }; + + let build_base_dir = abs_target_dir.join(&target).join(&profile); + + let install_dir = build_base_dir.join("newlib_install"); + let build_dir = build_base_dir.join("newlib_build"); + let use_host_target_tool_fallback = !tool_exists("x86_64-elf-gcc"); + + if use_host_target_tool_fallback { + println!( + "cargo:warning=x86_64-elf-gcc not found; using host gcc/binutils as target tool fallback" + ); + } + + // Check if libc.a exists in the install location + if install_dir.join("x86_64-elf/lib/libc.a").exists() { + println!("newlib already built, skipping"); + return; + } + + if !build_dir.exists() { + fs::create_dir_all(&build_dir).expect("Failed to create newlib build dir"); + } + + // Configure (if Makefile doesn't exist) + if !build_dir.join("Makefile").exists() { + println!("Configuring newlib..."); + + let configure_script = src_dir.join("configure"); + if !configure_script.exists() { + panic!( + "configure script not found at {}", + configure_script.display() + ); + } + + let abs_configure = configure_script.canonicalize().unwrap(); + + let mut configure_cmd = Command::new(abs_configure); + configure_cmd + .current_dir(&build_dir) + .arg(format!("--target={}", "x86_64-elf")) + .arg(format!("--prefix={}", install_dir.display())) + .arg("--disable-multilib"); + if use_host_target_tool_fallback { + apply_host_target_tool_fallback(&mut configure_cmd); + } else { + apply_cross_target_tools(&mut configure_cmd); + } + + let status = configure_cmd + .status() + .expect("Failed to execute newlib configure"); + + if !status.success() { + let _ = fs::remove_dir_all(&build_dir); + panic!("Newlib configure failed. Build directory cleaned up."); + } + } + + let cpu_cores = num_cpus::get(); + let make_j = format!("-j{}", cpu_cores); + + println!("Building newlib..."); + + let mut make_cmd = Command::new("make"); + make_cmd.current_dir(&build_dir).arg(make_j); + if use_host_target_tool_fallback { + apply_host_target_tool_fallback(&mut make_cmd); + } else { + apply_cross_target_tools(&mut make_cmd); + } + + let status = make_cmd.status().expect("Failed to execute newlib make"); + + if !status.success() { + let _ = fs::remove_dir_all(&build_dir); + panic!("Newlib make failed. Build directory cleaned up. Please try again."); + } + + println!("Installing newlib..."); + + let mut make_install_cmd = Command::new("make"); + make_install_cmd.current_dir(&build_dir).arg("install"); + if use_host_target_tool_fallback { + apply_host_target_tool_fallback(&mut make_install_cmd); + } else { + apply_cross_target_tools(&mut make_install_cmd); + } + + let status = make_install_cmd + .status() + .expect("Failed to execute newlib make install"); + + if !status.success() { + let _ = fs::remove_dir_all(&build_dir); + panic!("Newlib make install failed. Build directory cleaned up."); + } +} + +pub fn build_user_libs(user_dir: &Path, libc_dir: &Path) { + if !libc_dir.exists() { + fs::create_dir_all(libc_dir).expect("Failed to create libc dir"); + } + + let crt_src = user_dir.join("crt.rs"); + let lib_src = user_dir.join("lib.rs"); + let crt_obj = libc_dir.join("crt0.o"); + let libc_a = libc_dir.join("libc.a"); + let libg_a = libc_dir.join("libg.a"); + // 元の newlib libc.a を一度だけ保存しておく。以降のマージはここを起点にする。 + let libc_base_a = libc_dir.join("libc_newlib_base.a"); + let glue_lib = libc_dir.join("libuserglue.a"); + + // 初回のみ: 元の newlib libc.a をバックアップ + if !libc_base_a.exists() && libc_a.exists() { + fs::copy(&libc_a, &libc_base_a).expect("Failed to save libc_newlib_base.a"); + } + + // ソースが変更されていなければスキップ (crt0.o が存在する場合のみ) + if libc_base_a.exists() && libc_a.exists() && crt_obj.exists() { + let libc_mtime = libc_a.metadata().and_then(|m| m.modified()).ok(); + let lib_mtime = lib_src.metadata().and_then(|m| m.modified()).ok(); + let crt_mtime = crt_src.metadata().and_then(|m| m.modified()).ok(); + // Check all .rs files in user_dir for changes + let newest_src = fs::read_dir(user_dir) + .into_iter() + .flatten() + .flatten() + .filter(|e| e.path().extension().map(|x| x == "rs").unwrap_or(false)) + .filter_map(|e| e.metadata().and_then(|m| m.modified()).ok()) + .max(); + if let (Some(libc_t), Some(lib_t), Some(crt_t)) = (libc_mtime, lib_mtime, crt_mtime) { + let newest = [lib_t, crt_t] + .into_iter() + .chain(newest_src) + .max() + .unwrap_or(lib_t); + if libc_t > newest { + println!("user libs up to date, skipping"); + return; + } + } + } + + println!("Building user libs..."); + + // 1. crt0.o のビルド + let status = Command::new("rustc") + .args(["--emit", "obj"]) + .args(["--crate-type", "lib"]) + .args(["--edition", "2021"]) + .args(["--target", "x86_64-unknown-none"]) + .args(["-o", crt_obj.to_str().unwrap()]) + .arg(&crt_src) + .status() + .expect("Failed to build crt0.o"); + if !status.success() { + panic!("Failed to build crt0.o"); + } + + // 2. libuserglue.a のビルド + let status = Command::new("rustc") + .args(["--crate-type", "staticlib"]) + .args(["--edition", "2021"]) + .args(["--target", "x86_64-unknown-none"]) + .args(["-C", "panic=abort"]) + .args(["-o", glue_lib.to_str().unwrap()]) + .arg(&lib_src) + .status() + .expect("Failed to build libuserglue.a"); + if !status.success() { + panic!("Failed to build libuserglue.a"); + } + + // 3. libc_newlib_base.a + libuserglue.a → libc.a へマージ + // + // 方針: libc.a を直接上書きせず、temp ファイルに書いてから rename する。 + // 中断してもオリジナルの libc_newlib_base.a は常に無傷で残る。 + let merge_dir = libc_dir.join("merge_tmp"); + if merge_dir.exists() { + fs::remove_dir_all(&merge_dir).unwrap(); + } + fs::create_dir(&merge_dir).unwrap(); + + // libuserglue.a のオブジェクトだけを展開 (libc.a は触らない) + let status = Command::new("ar") + .current_dir(&merge_dir) + .arg("x") + .arg(&glue_lib) + .status() + .expect("Failed to extract libuserglue.a"); + if !status.success() { + panic!("ar x libuserglue.a failed"); + } + + // ベースのコピーに userglue オブジェクトを追加 (ar q = quick append, インデックスは後で再構築) + let libc_tmp = libc_dir.join("libc_merged_tmp.a"); + let base_src = if libc_base_a.exists() { + &libc_base_a + } else { + &libc_a + }; + if !base_src.exists() { + panic!( + "Base libc archive not found: expected {} or {}", + libc_base_a.display(), + libc_a.display() + ); + } + fs::copy(base_src, &libc_tmp).expect("Failed to copy libc base to temp"); + + let status = Command::new("sh") + .current_dir(&merge_dir) + .arg("-c") + .arg(format!("ar q {} *.o", libc_tmp.to_str().unwrap())) + .status() + .expect("Failed to append objects to libc temp"); + if !status.success() { + panic!("ar q libc_merged_tmp.a failed"); + } + + // シンボルインデックスを再構築 + let status = Command::new("ranlib") + .arg(&libc_tmp) + .status() + .expect("Failed to run ranlib"); + if !status.success() { + panic!("ranlib libc_merged_tmp.a failed"); + } + + // アトミックに libc.a を置き換え + fs::rename(&libc_tmp, &libc_a).expect("Failed to rename libc_merged_tmp.a to libc.a"); + + // libg.a (デバッグ版) も同期させる + if let Err(e) = fs::copy(&libc_a, &libg_a) { + panic!( + "Failed to copy merged libc.a to libg.a ({} -> {}): {}", + libc_a.display(), + libg_a.display(), + e + ); + } + + fs::remove_dir_all(&merge_dir).unwrap(); + println!("Successfully merged user glue into libc.a"); +} diff --git a/builders/services.rs b/builders/services.rs new file mode 100644 index 0000000..9704916 --- /dev/null +++ b/builders/services.rs @@ -0,0 +1,297 @@ +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; + +use super::utils::{emit_rerun_if_changed, find_binary_in_dir, find_target_spec}; + +/// サービスインデックスの情報 +#[derive(Debug, Clone)] +#[allow(dead_code)] +pub struct ServiceEntry { + pub name: String, + pub dir: String, + pub fs_type: String, + pub description: String, + pub autostart: bool, + pub order: u32, +} + +/// index.tomlを解析してサービス情報を取得 +pub fn parse_service_index(index_path: &Path) -> Result, String> { + let content = + fs::read_to_string(index_path).map_err(|e| format!("Failed to read index.toml: {}", e))?; + + // 簡易的なTOML解析(tomlクレートを使わずに) + let mut services = Vec::new(); + + let mut current_service = String::new(); + let mut current_dir = String::new(); + let mut current_fs = String::new(); + let mut current_desc = String::new(); + let mut current_autostart = false; + let mut current_order = 999; + + for line in content.lines() { + let line = line.trim(); + + // [core.service] または [core.service.NAME] を解析 + if line.starts_with("[core.service") && line.ends_with(']') { + // 前のサービスを保存 + if !current_service.is_empty() { + services.push(ServiceEntry { + name: current_service.clone(), + dir: current_dir.clone(), + fs_type: current_fs.clone(), + description: current_desc.clone(), + autostart: current_autostart, + order: current_order, + }); + } + + // 新しいサービス名を取得 + if line == "[core.service]" { + current_service = "core".to_string(); + } else if line.starts_with("[core.service.") { + let start = "[core.service.".len(); + let end = line.len() - 1; + current_service = line[start..end].to_string(); + } + + current_dir.clear(); + current_fs.clear(); + current_desc.clear(); + current_autostart = false; + current_order = 999; + } else if let Some(rest) = line.strip_prefix("dir = ") { + current_dir = rest.trim_matches('"').trim_matches('\'').to_string(); + } else if line.starts_with("fs = ") || line.starts_with("fs_type = ") { + let prefix = if line.starts_with("fs = ") { + "fs = " + } else { + "fs_type = " + }; + current_fs = line[prefix.len()..] + .trim_matches('"') + .trim_matches('\'') + .to_string(); + } else if let Some(rest) = line.strip_prefix("description = ") { + current_desc = rest.trim_matches('"').trim_matches('\'').to_string(); + } else if let Some(rest) = line.strip_prefix("autostart = ") { + current_autostart = rest.trim().parse().unwrap_or(false); + } else if let Some(rest) = line.strip_prefix("order = ") { + current_order = rest.trim().parse().unwrap_or(999); + } + } + + // 最後のサービスを保存 + if !current_service.is_empty() { + services.push(ServiceEntry { + name: current_service, + dir: current_dir, + fs_type: current_fs, + description: current_desc, + autostart: current_autostart, + order: current_order, + }); + } + + // order順にソート + services.sort_by_key(|s| s.order); + + Ok(services) +} + +/// サービスをビルドして指定ディレクトリにコピー +pub fn build_service( + service: &ServiceEntry, + services_base_dir: &Path, + output_dir: &Path, +) -> Result<(), String> { + let service_dir = services_base_dir.join(&service.dir); + + if !service_dir.exists() { + return Err(format!( + "Service directory not found: {}", + service_dir.display() + )); + } + + let cargo_toml = service_dir.join("Cargo.toml"); + if !cargo_toml.exists() { + return Err(format!("Cargo.toml not found for service {}", service.name)); + } + + println!( + "Building service: {} ({})", + service.name, service.description + ); + + // ソースファイルを監視 + println!("cargo:rerun-if-changed={}", cargo_toml.display()); + let src_dir = service_dir.join("src"); + if src_dir.is_dir() { + emit_rerun_if_changed(&src_dir); + } + + // .cargo/config.toml にtargetが設定されているか確認 + let cargo_config = service_dir.join(".cargo/config.toml"); + let cargo_config_text = fs::read_to_string(&cargo_config).ok(); + let has_config_target = cargo_config_text + .as_deref() + .map(|s| s.contains("[build]") && s.contains("target")) + .unwrap_or(false); + let config_uses_json_target = cargo_config_text + .as_deref() + .map(|s| s.contains(".json")) + .unwrap_or(false); + + let target_spec = if has_config_target { + None + } else { + find_target_spec(&service_dir) + }; + let uses_json_target = config_uses_json_target + || target_spec + .as_deref() + .map(|t| t.ends_with(".json")) + .unwrap_or(false); + + // cargoでサービスをビルド + let mut cmd = Command::new("cargo"); + // 起動時ロード時間を抑えるため、サービスは常に release でビルドする + cmd.args(["build", "--release"]); + if uses_json_target { + cmd.args(["-Z", "json-target-spec"]); + println!(" Enabling -Z json-target-spec"); + } + + // 外側の cargo ビルドの環境変数をクリア (干渉を防ぐ) + // ジョブサーバーとビルドシステムの変数をクリアして独立したビルドにする + for key in &[ + "RUSTFLAGS", + "CARGO_ENCODED_RUSTFLAGS", + "CARGO_TARGET_DIR", + "CARGO_BUILD_TARGET", + "CARGO_MAKEFLAGS", + "__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", + "CARGO_BUILD_RUSTC", + "RUSTC", + "RUSTC_WRAPPER", + "RUSTC_WORKSPACE_WRAPPER", + ] { + cmd.env_remove(key); + } + + if !has_config_target { + // .cargo/config.toml にtargetがない場合のみ --target を渡す + if let Some(target) = &target_spec { + cmd.arg("--target").arg(target); + println!(" Using target from JSON: {}", target); + } else { + let default_target = "x86_64-unknown-none"; + cmd.arg("--target").arg(default_target); + println!(" Using default target: {}", default_target); + } + } else { + println!(" Using target from .cargo/config.toml"); + } + + if service.name == "core" { + cmd.arg("--features").arg("run_tests"); + println!(" Enabling run_tests feature for core.service"); + } + + let output = cmd + .current_dir(&service_dir) + .output() + .map_err(|e| format!("Failed to run cargo: {}", e))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + // 末尾 4000 文字を優先表示 (エラー部分が末尾に多い) + let stdout = String::from_utf8_lossy(&output.stdout); + let err_tail = if stderr.len() > 2000 { + &stderr[stderr.len() - 2000..] + } else { + &stderr + }; + let out_tail = if stdout.len() > 2000 { + &stdout[stdout.len() - 2000..] + } else { + &stdout + }; + return Err(format!( + "Failed to build service {}: status={} STDERR={} STDOUT={}", + service.name, output.status, err_tail, out_tail + )); + } + + // ビルド成果物を探してコピー + let target_dir = service_dir.join("target"); + // .cargo/config.toml のターゲットかデフォルト名を使用 + let target_name: Option = if has_config_target { + Some("x86_64-mochios".to_string()) + } else { + None + }; + + if let Some(binary_path) = find_built_binary(&target_dir, target_name.as_deref()) { + let dest_name = format!("{}.service", service.name); + // ATA/ext2 サービスは services/ サブディレクトリに配置 + let effective_output_dir = if service.fs_type != "initfs" { + let services_subdir = output_dir.join("system/services"); + fs::create_dir_all(&services_subdir) + .map_err(|e| format!("Failed to create services dir: {}", e))?; + services_subdir + } else { + output_dir.to_path_buf() + }; + let dest = effective_output_dir.join(&dest_name); + + fs::copy(&binary_path, &dest) + .map_err(|e| format!("Failed to copy service binary to {}: {}", dest.display(), e))?; + + println!( + "Copied {} to {} (from {})", + dest_name, + output_dir.display(), + binary_path.display() + ); + } else { + return Err(format!( + "Built binary not found for service {}", + service.name + )); + } + + Ok(()) +} + +fn find_built_binary(target_dir: &Path, target_name: Option<&str>) -> Option { + for profile in &["release", "debug"] { + if let Some(target) = target_name { + let dir = target_dir.join(format!("{}/{}", target, profile)); + if dir.is_dir() { + if let Some(binary) = find_binary_in_dir(&dir) { + return Some(binary); + } + } + } + + let dir = target_dir.join(format!("x86_64-mochios/{}", profile)); + if dir.is_dir() { + if let Some(binary) = find_binary_in_dir(&dir) { + return Some(binary); + } + } + + let dir = target_dir.join(profile); + if dir.is_dir() { + if let Some(binary) = find_binary_in_dir(&dir) { + return Some(binary); + } + } + } + + None +} diff --git a/builders/utils.rs b/builders/utils.rs new file mode 100644 index 0000000..b37a05b --- /dev/null +++ b/builders/utils.rs @@ -0,0 +1,69 @@ +use std::fs; +use std::path::Path; + +/// ディレクトリとその中身を再帰的に監視対象に追加 +pub fn emit_rerun_if_changed(path: &Path) { + // targetディレクトリは除外 + if let Some(file_name) = path.file_name() { + if file_name == "target" || file_name == ".git" { + return; + } + } + + if let Ok(metadata) = fs::metadata(path) { + if metadata.is_file() { + println!("cargo:rerun-if-changed={}", path.display()); + } else if metadata.is_dir() { + println!("cargo:rerun-if-changed={}", path.display()); + if let Ok(entries) = fs::read_dir(path) { + for entry in entries.flatten() { + emit_rerun_if_changed(&entry.path()); + } + } + } + } +} + +/// ディレクトリ内で実行ファイルらしきものを探す +pub fn find_binary_in_dir(dir: &Path) -> Option { + let entries = fs::read_dir(dir).ok()?; + + for entry in entries.flatten() { + let path = entry.path(); + if path.is_file() { + let filename = path.file_name()?.to_string_lossy(); + // 実行可能ファイルっぽいものを探す(拡張子なし、.so, .dなどを除外) + if !filename.starts_with("lib") + && !filename.ends_with(".d") + && !filename.ends_with(".rlib") + && !filename.ends_with(".so") + && !filename.contains('.') + { + return Some(path); + } + } + } + + None +} + +/// カスタムターゲット仕様ファイルを探す +pub fn find_target_spec(app_dir: &Path) -> Option { + // .jsonファイルを探す(x86_64-*.json など) + if let Ok(entries) = fs::read_dir(app_dir) { + for entry in entries.flatten() { + let path = entry.path(); + if path.is_file() { + if let Some(filename) = path.file_name() { + let filename_str = filename.to_string_lossy(); + if filename_str.ends_with(".json") && filename_str.starts_with("x86_64-") { + // 絶対パスを返す + return path.to_str().map(|s| s.to_string()); + } + } + } + } + } + + None +} diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..e69de29 diff --git a/contributors.md b/contributors.md new file mode 100644 index 0000000..6a7d844 --- /dev/null +++ b/contributors.md @@ -0,0 +1,5 @@ +## mochiOS メンテナーリスト +- [tas0dev](https://github.com/tas0dev)(Admin) + +#### 貢献してくれた方々 +- [にあ](https://github.com/minto-dane) \ No newline at end of file diff --git a/panic.md b/panic.md deleted file mode 100644 index 501c102..0000000 --- a/panic.md +++ /dev/null @@ -1,385 +0,0 @@ -## 基本設計方針 - -**絶対panicしないカーネル** -カーネル空間でのpanicはシステム全体の停止を意味します。SwiftCoreでは`panic!`を完全に禁止し、すべてのエラーを`Result`型で表現します。 - -**エラー型の階層化** -すべてのカーネルエラーを`KernelError`列挙型に集約しますが、サブシステムごとに詳細なエラー型も用意します。 - -```rust -// トップレベルエラー型 -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum KernelError { - Memory(MemoryError), - Process(ProcessError), - Fs(FileSystemError), - Device(DeviceError), - Ipc(IpcError), - InvalidParam, - NotImplemented, -} - -// サブシステムごとの詳細エラー -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum MemoryError { - OutOfMemory, - InvalidAddress, - PermissionDenied, - AlreadyMapped, - NotMapped, - AlignmentError, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ProcessError { - InvalidPid, - ProcessNotFound, - ZombieProcess, - MaxProcessesReached, - InsufficientPrivilege, -} - -// 他のサブシステムも同様... -``` - -## エラーハンドリングの原則 - -**原則1: すべてのエラーに回復パスを定義** -各エラーに対して以下を明確にします: -- **即座に回復可能**: リトライや代替手段で処理継続 -- **呼び出し側に伝播**: 上位レイヤーでの判断が必要 -- **システムログ後に継続**: エラーを記録して処理継続 -- **プロセス終了**: 当該プロセスのみ終了 -- **致命的エラー**: システム停止(最終手段) - -```rust -impl KernelError { - /// このエラーが致命的かどうか - pub fn is_fatal(&self) -> bool { - match self { - KernelError::Memory(MemoryError::OutOfMemory) => true, - KernelError::Device(DeviceError::HardwareFailure) => true, - _ => false, - } - } - - /// このエラーがリトライ可能かどうか - pub fn is_retryable(&self) -> bool { - match self { - KernelError::Ipc(IpcError::BufferFull) => true, - KernelError::Device(DeviceError::Busy) => true, - _ => false, - } - } -} -``` - -**原則2: matchによる網羅的処理の強制** -エラーハンドリングでワイルドカードパターン(`_`)の使用を禁止します。これによりコンパイラが全パターンチェックを強制します。 - -```rust -// 良い例: すべてのエラーを明示的に処理 -match allocate_page() { - Ok(page) => use_page(page), - Err(KernelError::Memory(MemoryError::OutOfMemory)) => { - // OOMキラーを起動 - reclaim_memory_and_retry() - }, - Err(KernelError::Memory(MemoryError::InvalidAddress)) => { - log_error("Invalid address in allocation"); - return Err(KernelError::InvalidParam); - }, - Err(e) => { - // 他のエラーは上位に伝播 - return Err(e); - } -} - -// 悪い例: ワイルドカードで隠蔽(Lintで禁止) -match allocate_page() { - Ok(page) => use_page(page), - Err(_) => return Err(KernelError::Memory(MemoryError::OutOfMemory)), // NG -} -``` - -**原則3: エラーコンテキストの保持** -エラーが発生した場所とコンテキストを追跡できるようにします。 - -```rust -// エラーにコンテキストを追加 -pub struct ErrorContext { - pub error: KernelError, - pub file: &'static str, - pub line: u32, - pub function: &'static str, -} - -macro_rules! kernel_error { - ($err:expr) => { - ErrorContext { - error: $err, - file: file!(), - line: line!(), - function: core::any::type_name::(), - } - }; -} - -// 使用例 -fn allocate_page() -> Result { - if no_memory_available() { - return Err(kernel_error!( - KernelError::Memory(MemoryError::OutOfMemory) - )); - } - // ... -} -``` - -## 回復戦略の実装 - -**戦略1: リトライメカニズム** -リトライ可能なエラーに対する統一的な再試行機構を提供します。 - -```rust -pub fn retry_with_backoff( - mut f: F, - max_attempts: usize, -) -> Result -where - F: FnMut() -> Result, -{ - for attempt in 0..max_attempts { - match f() { - Ok(val) => return Ok(val), - Err(e) if e.is_retryable() => { - // 指数バックオフ - sleep_ticks(1 << attempt); - continue; - } - Err(e) => return Err(e), - } - } - Err(KernelError::Device(DeviceError::Timeout)) -} - -// 使用例 -retry_with_backoff(|| send_ipc_message(msg), 3)?; -``` - -**戦略2: フォールバック処理** -主要な処理が失敗した場合の代替手段を型で表現します。 - -```rust -pub enum AllocationStrategy { - Primary, - Fallback, - Emergency, -} - -pub fn allocate_with_fallback() -> Result { - // 通常のアロケーション - if let Ok(page) = allocate_from_pool(AllocationStrategy::Primary) { - return Ok(page); - } - - // フォールバック: スワップアウト - if let Ok(page) = swap_out_and_allocate() { - log_warn("Used swap for allocation"); - return Ok(page); - } - - // 緊急: プロセス終了してメモリ回収 - if let Ok(page) = kill_low_priority_and_allocate() { - log_warn("Killed process for memory"); - return Ok(page); - } - - Err(KernelError::Memory(MemoryError::OutOfMemory)) -} -``` - -**戦略3: タイプステートパターンでの状態管理** -エラー後の状態を型で表現し、無効な操作をコンパイル時に防ぎます。 - -```rust -// ファイルの状態を型で表現 -pub struct File { - fd: FileDescriptor, - _state: PhantomData, -} - -pub struct Open; -pub struct Closed; -pub struct Error; - -impl File { - pub fn read(&mut self, buf: &mut [u8]) -> Result { - // 読み込み処理 - } - - pub fn close(self) -> File { - // クローズ処理 - File { - fd: self.fd, - _state: PhantomData, - } - } -} - -impl File { - pub fn reopen(self) -> Result, (File, KernelError)> { - match try_reopen(self.fd) { - Ok(_) => Ok(File { - fd: self.fd, - _state: PhantomData, - }), - Err(e) => Err((File { - fd: self.fd, - _state: PhantomData, - }, e)), - } - } -} - -// Fileにはreadメソッドがないので、 -// クローズ後の読み込みはコンパイルエラー -``` - -## テスト戦略 - -**単体テスト: エラーパスの網羅** -すべてのエラーケースに対するテストを書きます。 - -```rust -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_out_of_memory_handling() { - // メモリを使い切る - let _pages = exhaust_memory(); - - match allocate_page() { - Err(KernelError::Memory(MemoryError::OutOfMemory)) => { - // 期待通り - }, - _ => panic!("Expected OutOfMemory error"), - } - } - - #[test] - fn test_error_recovery() { - simulate_low_memory(); - - // フォールバックが動作することを確認 - let page = allocate_with_fallback().expect("Should recover"); - assert!(page.is_valid()); - } -} -``` - -**統合テスト: エラー注入** -実行時にエラーを注入してシステムの挙動を確認します。 - -```rust -#[cfg(feature = "error_injection")] -pub mod error_injection { - use core::sync::atomic::{AtomicBool, Ordering}; - - static INJECT_OOM: AtomicBool = AtomicBool::new(false); - - pub fn enable_oom_injection() { - INJECT_OOM.store(true, Ordering::SeqCst); - } - - pub fn should_inject_oom() -> bool { - INJECT_OOM.load(Ordering::SeqCst) - } -} - -// アロケータ内で使用 -fn allocate_page_impl() -> Result { - #[cfg(feature = "error_injection")] - if error_injection::should_inject_oom() { - return Err(KernelError::Memory(MemoryError::OutOfMemory)); - } - - // 通常のアロケーション処理 -} -``` - -**Fuzzing: 異常系の発見** -cargo-fuzzを使ってシステムコールの異常系をテストします。 - -```rust -#[cfg(fuzzing)] -use libfuzzer_sys::fuzz_target; - -fuzz_target!(|data: &[u8]| { - if data.len() < 8 { - return; - } - - let syscall_num = u32::from_le_bytes([data[0], data[1], data[2], data[3]]); - let param = u32::from_le_bytes([data[4], data[5], data[6], data[7]]); - - // システムコールを実行してpanicしないことを確認 - let _ = execute_syscall(syscall_num, param); -}); -``` - -## Lintルールの設定 - -**カスタムLintでポリシーを強制** -Clippyのカスタムルールで、エラーハンドリングポリシーを強制します。 - -```rust -// .cargo/config.toml または rust-toolchain.toml で設定 -// [lints.clippy] -// unwrap_used = "deny" -// expect_used = "deny" -// panic = "deny" -// wildcard_enum_match_arm = "deny" // match文での _ 禁止 - -// コード内での設定 -#![deny(clippy::unwrap_used)] -#![deny(clippy::expect_used)] -#![deny(clippy::panic)] -#![deny(clippy::wildcard_enum_match_arm)] -``` - -## ドキュメント化 - -**各エラーの回復手順を文書化** -エラー型の定義に回復戦略をドキュメントとして含めます。 - -```rust -/// メモリ関連のエラー -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum MemoryError { - /// 利用可能なメモリがない - /// - /// # 回復戦略 - /// 1. スワップアウトを試みる - /// 2. キャッシュをクリアする - /// 3. 低優先度プロセスを終了する - /// 4. すべて失敗した場合、呼び出し側にエラーを返す - /// - /// # 致命度 - /// システム全体で回復不能な場合のみ致命的 - OutOfMemory, - - /// 無効なアドレスへのアクセス - /// - /// # 回復戦略 - /// プロセスにSIGSEGVを送信してプロセスを終了 - /// - /// # 致命度 - /// 非致命的(プロセスレベルで処理) - InvalidAddress, - - // ... -} -``` \ No newline at end of file diff --git a/qodana.yaml b/qodana.yaml new file mode 100644 index 0000000..f5b32be --- /dev/null +++ b/qodana.yaml @@ -0,0 +1,45 @@ +#-------------------------------------------------------------------------------# +# Qodana analysis is configured by qodana.yaml file # +# https://www.jetbrains.com/help/qodana/qodana-yaml.html # +#-------------------------------------------------------------------------------# + +version: "1.0" + +#Specify inspection profile for code analysis +profile: + name: qodana.starter + +#Enable inspections +#include: +# - name: + +#Disable inspections +exclude: + - name: RsDetachedFile + paths: + - src + - name: NewCrateVersionAvailabl + paths: + - / + +#Execute shell command before Qodana execution (Applied in CI/CD pipeline) +#bootstrap: sh ./prepare-qodana.sh + +#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline) +#plugins: +# - id: #(plugin id can be found at https://plugins.jetbrains.com) + +# Quality gate. Will fail the CI/CD pipeline if any condition is not met +# severityThresholds - configures maximum thresholds for different problem severities +# testCoverageThresholds - configures minimum code coverage on a whole project and newly added code +# Code Coverage is available in Ultimate and Ultimate Plus plans +#failureConditions: +# severityThresholds: +# any: 15 +# critical: 5 +# testCoverageThresholds: +# fresh: 70 +# total: 50 + +#Specify Qodana linter for analysis (Applied in CI/CD pipeline) +linter: jetbrains/qodana-:2026.1 diff --git a/rust-toolchain.toml b/rust-toolchain.toml index ecc21cc..f2e977a 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,4 @@ [toolchain] channel = "nightly" components = ["rust-src", "llvm-tools"] +targets = ["x86_64-unknown-uefi", "x86_64-unknown-none"] diff --git a/scripts/autoinstall.sh b/scripts/autoinstall.sh new file mode 100644 index 0000000..8dac19c --- /dev/null +++ b/scripts/autoinstall.sh @@ -0,0 +1,39 @@ +#!/bin/bash +set -e + +echo -e "\e[34mmochiOS dependencies auto installer\e[0m" +echo "This script targets Ubuntu and installs dependencies via apt." +echo "Homebrew is required for x86_64-elf-gcc and is checked (not auto-installed)." +echo "The components installed by this script are as described in the README." +echo "Cargo-related tools are not installed automatically." +read -p "Continue? [y/n]: " answer + +case "$answer" in + [yY] | [yY][eE][sS] ) + echo "Starting installation..." + ;; + * ) + echo "ok goodluck! :)" + exit 1 + ;; +esac + +if ! command -v brew &> /dev/null; then + echo -e "\e[31mError: Please install homebrew: https://brew.sh\e[0m" + exit 1 +fi + +sudo apt update +sudo apt upgrade -y +sudo apt install -y git +sudo apt install -y qemu-system-x86_64 qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virt-manager +sudo apt install -y mtools +sudo apt install -y e2fsprogs +sudo apt install -y build-essential +sudo apt install -y make +sudo apt install -y libgcc-s1 +sudo apt install -y texinfo +brew install x86_64-elf-gcc + +echo "" +echo -e "\e[32mInstallation completed successfully! :D\e[0m" \ No newline at end of file diff --git a/scripts/build-user-elf.sh b/scripts/build-user-elf.sh new file mode 100755 index 0000000..1a57d2c --- /dev/null +++ b/scripts/build-user-elf.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR=$(cd "$(dirname "$0")/.." && pwd) +INITFS_DIR="$ROOT_DIR/src/initfs" +ASM="$INITFS_DIR/hello.asm" +OUT="$INITFS_DIR/hello" + +# Use nasm + ld by default +NASM=${1:-nasm} +LD=${2:-ld} + +if ! command -v "$NASM" >/dev/null 2>&1; then + echo "Error: nasm not found. Install nasm or pass assembler as first arg." + exit 1 +fi +if ! command -v "$LD" >/dev/null 2>&1; then + echo "Error: ld not found. Install binutils or pass linker as second arg." + exit 1 +fi + +echo "Assembling with: $NASM; linking with: $LD" + +TMPOBJ="$OUT.o" +$NASM -f elf64 -o "$TMPOBJ" "$ASM" +$LD -static -o "$OUT" "$TMPOBJ" +rm -f "$TMPOBJ" + +echo "Built: $OUT" diff --git a/scripts/configure.sh b/scripts/configure.sh new file mode 100755 index 0000000..3baf530 --- /dev/null +++ b/scripts/configure.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +set -e + +TARGET_TRIPLE="x86_64-unknown-uefi" +NEWLIB_TARGET="x86_64-elf" + +# scripts1つ上をプロジェクトルートにする +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +TARGET_DIR="$PROJECT_ROOT/target/$TARGET_TRIPLE" + +PROFILE="${1:-debug}" + +INSTALL_DIR="$TARGET_DIR/$PROFILE" +BUILD_DIR="$INSTALL_DIR/newlib_build" + +SOURCE_DIR="$PROJECT_ROOT/src/lib" + +echo "Project root: $PROJECT_ROOT" +echo "Install dir : $INSTALL_DIR" +echo "Build dir : $BUILD_DIR" + +mkdir -p "$BUILD_DIR" +cd "$BUILD_DIR" + +if [ -f "config.status" ]; then + echo "Cleaning previous configure..." + make distclean || true + rm -f config.cache +fi + +echo "Running configure..." + +"$SOURCE_DIR/configure" \ + --target=x86_64-elf \ + --host=x86_64-elf \ + --prefix="$INSTALL_DIR" \ + --disable-newlib-supplied-syscalls \ + --disable-nls \ + --disable-werror \ + --disable-libssp \ + --disable-shared \ + CFLAGS_FOR_TARGET="-ffreestanding -nostdlib" + +echo "Configure complete." diff --git a/scripts/create_iso.sh b/scripts/create_iso.sh new file mode 100755 index 0000000..5226f2c --- /dev/null +++ b/scripts/create_iso.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +BOOT_SRC="${1:-}" +OUTPUT_ISO="${2:-$ROOT_DIR/target/mochiOS.iso}" + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Error: required command not found: $1" >&2 + exit 1 + fi +} + +require_cmd mkdosfs +require_cmd mmd +require_cmd mcopy +require_cmd xorriso + +MOCHIOS_IMG_DEFAULT="$ROOT_DIR/target/mochiOS.img" + +if [ -z "$BOOT_SRC" ]; then + BOOT_SRC=$(find "$ROOT_DIR/target/x86_64-unknown-uefi" -type f \( -name "BOOTX64.EFI" -o -name "boot.efi" -o -name "boot" \) -not -path "*/kernel/*" 2>/dev/null | xargs ls -t 2>/dev/null | head -1 || true) +fi + +if [ -z "${BOOT_SRC:-}" ] || [ ! -f "$BOOT_SRC" ]; then + echo "Error: UEFI boot binary not found." >&2 + echo "Usage: $0 [EFI_FILE] [OUTPUT_ISO]" >&2 + echo "Hint: run 'cargo build' first, or pass BOOTX64.EFI explicitly." >&2 + exit 1 +fi + +PROFILE="debug" +case "$BOOT_SRC" in + */release/*) PROFILE="release" ;; +esac + +FALLBACK_KERNEL="$ROOT_DIR/target/kernel/x86_64-unknown-none/$PROFILE/kernel" +FS_KERNEL="$ROOT_DIR/fs/system/kernel.elf" +if [ -f "$FALLBACK_KERNEL" ]; then + KERNEL_ELF="$FALLBACK_KERNEL" +elif [ -f "$FS_KERNEL" ]; then + KERNEL_ELF="$FS_KERNEL" +else + echo "Error: kernel.elf not found." >&2 + echo " Missing: $FALLBACK_KERNEL" >&2 + echo " Missing: $FS_KERNEL" >&2 + echo " Run 'cargo build' first to build the kernel." >&2 + exit 1 +fi + +INITFS_IMG=$(find "$ROOT_DIR/target/x86_64-unknown-uefi" -name "initfs.ext2" -not -path "*/kernel/*" 2>/dev/null | xargs ls -t 2>/dev/null | head -1 || true) +ROOTFS_IMG=$(find "$ROOT_DIR/target/x86_64-unknown-uefi" -name "rootfs.ext2" -not -path "*/kernel/*" 2>/dev/null | xargs ls -t 2>/dev/null | head -1 || true) +MOCHIOS_IMG="$MOCHIOS_IMG_DEFAULT" + +mkdir -p "$(dirname "$OUTPUT_ISO")" + +TEMP_DIR=$(mktemp -d) +# shellcheck disable=SC2064 +trap "rm -rf $TEMP_DIR" EXIT + +ESP_IMG="$TEMP_DIR/esp.img" +ISO_ROOT="$TEMP_DIR/isoroot" +EFI_DIR="$ISO_ROOT/EFI/BOOT" +SYS_DIR="$ISO_ROOT/system" + +mkdir -p "$EFI_DIR" +mkdir -p "$SYS_DIR" +cp "$BOOT_SRC" "$EFI_DIR/BOOTX64.EFI" +esp_bytes=$(stat -c%s "$BOOT_SRC") +esp_mb=$(( (esp_bytes / 1048576) + 8 )) +if [ "$esp_mb" -lt 16 ]; then + esp_mb=16 +fi + +dd if=/dev/zero of="$ESP_IMG" bs=1M count="$esp_mb" status=none +# Small ESP images are better as FAT16 to avoid firmware edge-cases. +if [ "$esp_mb" -lt 33 ]; then + mkdosfs -F 16 -n MOCHIOS "$ESP_IMG" >/dev/null +else + mkdosfs -F 32 -n MOCHIOS "$ESP_IMG" >/dev/null +fi + +mmd -i "$ESP_IMG" ::/EFI ::/EFI/BOOT ::/system +mcopy -i "$ESP_IMG" "$BOOT_SRC" ::/EFI/BOOT/BOOTX64.EFI +cp "$KERNEL_ELF" "$SYS_DIR/kernel.elf" +if [ -n "$INITFS_IMG" ] && [ -f "$INITFS_IMG" ]; then + cp "$INITFS_IMG" "$SYS_DIR/initfs.img" +else + echo "Warning: initfs.ext2 not found; ISO will not include system/initfs.img" >&2 +fi + +if [ -n "$ROOTFS_IMG" ] && [ -f "$ROOTFS_IMG" ]; then + cp "$ROOTFS_IMG" "$SYS_DIR/rootfs.ext2" +else + echo "Warning: rootfs.ext2 not found; ISO will not include system/rootfs.ext2" >&2 +fi + +cp "$ESP_IMG" "$ISO_ROOT/esp.img" + +if [ -f "$MOCHIOS_IMG" ]; then + cp "$MOCHIOS_IMG" "$ISO_ROOT/mochiOS.img" +else + echo "Warning: $MOCHIOS_IMG not found; ISO will not include mochiOS.img" >&2 + echo "Hint: run scripts/make_image.sh (or cargo build if it invokes it) to generate target/mochiOS.img" >&2 +fi + +xorriso -as mkisofs \ + -iso-level 3 \ + -full-iso9660-filenames \ + -volid "MOCHIOS" \ + -eltorito-alt-boot \ + -e esp.img \ + -no-emul-boot \ + -output "$OUTPUT_ISO" \ + "$ISO_ROOT" >/dev/null + +echo "Created UEFI-bootable ISO: $OUTPUT_ISO" diff --git a/scripts/make-uefi-boot.sh b/scripts/make-uefi-boot.sh new file mode 100755 index 0000000..5be5ca2 --- /dev/null +++ b/scripts/make-uefi-boot.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +set -euo pipefail + +CFG=${1:-debug} +ROOT_DIR=$(pwd) +TARGET_DIR="$ROOT_DIR/target/x86_64-unknown-uefi/$CFG/boot" + +echo "Assembling UEFI boot directory: $TARGET_DIR" + +mkdir -p "$TARGET_DIR/EFI/BOOT" +mkdir -p "$TARGET_DIR/services" + +if [ -d "$ROOT_DIR/src/initfs" ]; then + echo "Copying initfs files..." + cp -r "$ROOT_DIR/src/initfs/." "$TARGET_DIR/" +fi + +EFI_CANDIDATES=( + "$ROOT_DIR/target/x86_64-unknown-uefi/$CFG/boot/BOOTX64.EFI" + "$ROOT_DIR/target/x86_64-unknown-uefi/$CFG/boot/boot.efi" + "$ROOT_DIR/target/x86_64-unknown-uefi/$CFG/boot/boot" +) + +FOUND_EFI="" +for p in "${EFI_CANDIDATES[@]}"; do + if [ -f "$p" ]; then + FOUND_EFI="$p" + break + fi +done + +if [ -z "$FOUND_EFI" ]; then + echo "Searching for EFI binary in target directory..." + FOUND_EFI=$(find "$ROOT_DIR/target" -type f \( -iname "*.efi" -o -iname "boot" -o -iname "bootx64*" \) | head -n1 || true) +fi + +if [ -n "$FOUND_EFI" ]; then + echo "Found EFI binary: $FOUND_EFI" + cp "$FOUND_EFI" "$TARGET_DIR/EFI/BOOT/BOOTX64.EFI" +else + echo "Warning: No EFI binary found in target. Build the project first." >&2 +fi + +if [ -d "$ROOT_DIR/src/services" ]; then + for svc in "$ROOT_DIR/src/services"/*; do + if [ -d "$svc" ]; then + name=$(basename "$svc") + # Create a placeholder descriptor if none exists + echo "name=$name" > "$TARGET_DIR/services/$name.service" + fi + done +fi + +echo "Boot directory assembled at: $TARGET_DIR" diff --git a/scripts/make_image.sh b/scripts/make_image.sh new file mode 100755 index 0000000..4492a00 --- /dev/null +++ b/scripts/make_image.sh @@ -0,0 +1,31 @@ +#!/bin/bash +set -e + +# プロジェクトルートへ移動 +cd "$(dirname "$0")/.." + +# initfsディレクトリの場所 +INITFS_DIR="fs" +OUTPUT_IMG="target/mochiOS.img" +SIZE="256M" + +echo "Creating disk image: $OUTPUT_IMG (Source: $INITFS_DIR)" + +# ターゲットディレクトリがない場合は作成 +mkdir -p $(dirname "$OUTPUT_IMG") + +# mke2fsが利用可能か確認 +if ! command -v mke2fs &> /dev/null; then + echo "Error: mke2fs not found. Please install e2fsprogs." + exit 1 +fi + +# イメージ作成とディレクトリ内容のコピー +# -t ext2: ファイルシステムタイプ +# -b 4096: ブロックサイズ +# -d $INITFS_DIR: ディレクトリ内容をルートにコピー +# -L mochiOS: ボリュームラベル +# -F: ファイルへの書き込みを強制 +mke2fs -t ext2 -b 4096 -d "$INITFS_DIR" -L mochiOS -F "$OUTPUT_IMG" "$SIZE" + +echo "Done." diff --git a/scripts/qemu-runner.sh b/scripts/qemu-runner.sh index d3d9ae2..5ba46ac 100755 --- a/scripts/qemu-runner.sh +++ b/scripts/qemu-runner.sh @@ -19,33 +19,72 @@ for path in "${OVMF_PATHS[@]}"; do done if [ -z "$OVMF" ]; then - echo "Error: OVMF firmware not found. Please install ovmf package." - echo " Ubuntu: sudo apt install ovmf" - echo " Arch Linux: sudo pacman -S edk2-ovmf" + echo "Error: OVMF firmware not found." exit 1 fi -EFI_FILE="$1" - -if [ ! -f "$EFI_FILE" ]; then - echo "Error: EFI file not found: $EFI_FILE" +SRC="$1" +if [ -z "$SRC" ]; then + echo "Usage: $0 " exit 1 fi -TEMP_DIR=$(mktemp -d) -trap "rm -rf $TEMP_DIR" EXIT +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +TARGET_DIR="$ROOT_DIR/target" + +rm -f "$TARGET_DIR/esp.img" + +mkdir -p "$TARGET_DIR" +ESP_IMG="$TARGET_DIR/esp.img" + +BOOTX64_SRC="$SRC" + +PROFILE="debug" +[[ "$SRC" == */release/* ]] && PROFILE="release" + +FALLBACK_KERNEL="$ROOT_DIR/target/kernel/x86_64-unknown-none/$PROFILE/kernel" +FS_KERNEL="$ROOT_DIR/fs/system/kernel.elf" +KERNEL_ELF="${FALLBACK_KERNEL}" +[ ! -f "$KERNEL_ELF" ] && KERNEL_ELF="$FS_KERNEL" + +INITFS_IMG=$(find "$ROOT_DIR/target/x86_64-unknown-uefi" -name "initfs.ext2" 2>/dev/null | xargs ls -t 2>/dev/null | head -1 || true) +ROOTFS_IMG=$(find "$ROOT_DIR/target/x86_64-unknown-uefi" -name "rootfs.ext2" 2>/dev/null | xargs ls -t 2>/dev/null | head -1 || true) -mkdir -p "$TEMP_DIR/esp/EFI/BOOT" +esp_bytes=0 +for f in "$BOOTX64_SRC" "$KERNEL_ELF" "$INITFS_IMG" "$ROOTFS_IMG"; do + [ -f "$f" ] && esp_bytes=$(( esp_bytes + $(stat -c%s "$f") )) +done +esp_mb=$(( (esp_bytes / 1048576) + 50 )) + +rm -f "$ESP_IMG" +dd if=/dev/zero of="$ESP_IMG" bs=1M count="$esp_mb" status=none +mkdosfs -F 32 -n EFI "$ESP_IMG" > /dev/null -cp "$EFI_FILE" "$TEMP_DIR/esp/EFI/BOOT/BOOTX64.EFI" +mmd -i "$ESP_IMG" ::/EFI ::/EFI/BOOT ::/system +mcopy -i "$ESP_IMG" "$BOOTX64_SRC" ::/EFI/BOOT/BOOTX64.EFI + +[ -f "$KERNEL_ELF" ] && mcopy -i "$ESP_IMG" "$KERNEL_ELF" ::/system/kernel.elf +[ -f "$INITFS_IMG" ] && mcopy -i "$ESP_IMG" "$INITFS_IMG" ::/system/initfs.img +[ -f "$ROOTFS_IMG" ] && mcopy -i "$ESP_IMG" "$ROOTFS_IMG" ::/system/rootfs.ext2 + +KVM_ARGS=() +if [ -e /dev/kvm ] && [ -r /dev/kvm ]; then + KVM_ARGS=(-enable-kvm -cpu host,migratable=no,+invtsc) +fi exec qemu-system-x86_64 \ + "${KVM_ARGS[@]}" \ -bios "$OVMF" \ - -drive format=raw,file=fat:rw:"$TEMP_DIR/esp" \ - -net none \ + -drive format=raw,file="$ESP_IMG",index=0,media=disk \ + -drive id=disk0,file="$TARGET_DIR/mochiOS.img",format=raw,if=ide,index=1,media=disk \ + -usb \ + -device qemu-xhci,id=xhci \ + -device usb-kbd,bus=xhci.0 \ + -device usb-tablet,bus=xhci.0 \ + -netdev user,id=net0 \ + -device virtio-net-pci,netdev=net0 \ -m 512M \ - -serial stdio \ - -vga std \ -no-reboot \ - -d int,cpu_reset \ - -D qemu.log \ No newline at end of file + -serial stdio \ + -vga std diff --git a/scripts/requirements.sh b/scripts/requirements.sh new file mode 100755 index 0000000..64e1904 --- /dev/null +++ b/scripts/requirements.sh @@ -0,0 +1,74 @@ +#!/bin/sh + +REQUIRED_CMDS="qemu-system-x86_64 cargo rustc" +MISSING=0 + +echo "                                +⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⢀⡀⡀⡀⡀⢀⣤⣶⣶⣶⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀ +⡀⢀⣴⣾⣿⣿⣷⣦⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⢿⡿⡀⡀⡀⣿⡟⡀⡀⠈⡀⣶⣿⡀⡀⡀⡀⡀⡀⡀⣠⣶⣿⣿⣿⣷⣦⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀ +⡀⣿⡟⡀⡀⡀⡀⠉⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⢸⣿⡀⡀⡀⡀⡀⣿⣿⡀⡀⡀⡀⡀⡀⣾⡿⠉⡀⡀⡀⡀⠉⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀ +⡀⣿⣧⡀⡀⡀⡀⡀⡀⠈⣿⡇⡀⡀⡀⣿⣿⡀⡀⡀⢠⣿⠃⡀⣿⣿⡀⡀⢸⣿⠿⠿⠿⠿⡀⣿⣿⠿⠿⠿⡀⡀⣾⣿⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀⣴⣿⠿⠿⣿⣦⡀⡀⡀⢰⣿⡿⠿⠿⡟⡀⡀⣴⣿⠿⠿⣿⣦⡀ +⡀⠈⠿⣿⣷⣦⣀⡀⡀⡀⢿⣿⡀⡀⢀⣿⣿⡆⡀⡀⣾⣿⡀⡀⣿⣿⡀⡀⢸⣿⡀⡀⡀⡀⡀⣿⣿⡀⡀⡀⡀⡀⣿⣿⡀⡀⡀⡀⡀⡀⡀⡀⡀⣾⡿⡀⡀⡀⡀⢿⣿⡀⡀⢸⣿⡀⡀⡀⡀⡀⣿⡿⡀⡀⡀⠈⣿⡇ +⡀⡀⡀⡀⠈⠛⣿⣿⡀⡀⠘⣿⡄⡀⣾⡟⢹⣿⡀⢀⣿⠃⡀⡀⣿⣿⡀⡀⢸⣿⡀⡀⡀⡀⡀⣿⣿⡀⡀⡀⡀⡀⣿⣿⡀⡀⡀⡀⡀⡀⡀⡀⡀⣿⡇⡀⡀⡀⡀⢸⣿⡄⡀⢸⣿⡀⡀⡀⡀⡀⣿⣿⣿⣿⣿⣿⣿⣿ +⡀⡀⡀⡀⡀⡀⡀⣿⡧⡀⡀⢿⣿⢀⣿⡀⡀⣿⡄⣾⡿⡀⡀⡀⣿⣿⡀⡀⢸⣿⡀⡀⡀⡀⡀⣿⣿⡀⡀⡀⡀⡀⢻⣿⡄⡀⡀⡀⡀⡀⡀⡀⡀⣿⣇⡀⡀⡀⡀⣸⣿⡀⡀⢸⣿⡀⡀⡀⡀⡀⣿⣧⡀⡀⡀⡀⡀⡀ +⢀⣦⣄⣀⣀⣠⣾⣿⠃⡀⡀⡀⣿⣿⡏⡀⡀⢹⣿⣿⠁⡀⡀⡀⣿⣿⡀⡀⢸⣿⡀⡀⡀⡀⡀⠹⣿⣄⣀⣠⡄⡀⡀⠻⣿⣶⣄⣀⣀⣠⣴⡀⡀⠘⣿⣦⣀⣀⣴⣿⠋⡀⡀⢸⣿⡀⡀⡀⡀⡀⠙⣿⣦⣀⣀⣀⣤⡀ +⡀⠉⠙⠛⠛⠛⠉⡀⡀⡀⡀⡀⠙⠛⡀⡀⡀⡀⠛⠋⡀⡀⡀⡀⠛⠛⡀⡀⠘⠛⡀⡀⡀⡀⡀⡀⠉⠛⠛⠋⠁⡀⡀⡀⡀⠉⠛⠛⠛⠋⠉⡀⡀⡀⡀⠉⠛⠛⠉⡀⡀⡀⡀⠘⠛⡀⡀⡀⡀⡀⡀⡀⠉⠛⠛⠛⠉⡀ +"                                        + +# Auto install dependencies +if [ "$1" = "install" ]; then + echo "Installing dependencies..." + + if command -v apt >/dev/null 2>&1; then + PKG_MANAGER="apt" + UPDATE_CMD="sudo apt update" + INSTALL_CMD="sudo apt install -y qemu-system llvm ovmf" + elif command -v dnf >/dev/null 2>&1; then + PKG_MANAGER="dnf" + UPDATE_CMD="sudo dnf update" + INSTALL_CMD="sudo dnf install -y qemu-system llvm ovmf" + else + echo "No supported package manager found (apt or dnf). Please install dependencies manually." + exit 1 + fi + + echo "Using $PKG_MANAGER to install system dependencies..." + $UPDATE_CMD + $INSTALL_CMD + + echo "\nThe following tools should be installed via rustup:" + echo " - cargo" + echo " - rustc" + echo "Please run: curl https://sh.rustup.rs -sSf | sh" + exit 0 +fi + +echo "Checking required dependencies..." +for cmd in $REQUIRED_CMDS; do + if ! command -v "$cmd" >/dev/null 2>&1; then + echo "\033[31m[ MISSING ]\033[0m $cmd" + MISSING=1 + MISSING_LIST="$MISSING_LIST $cmd" + else + echo "\033[32m[ FOUND ]\033[0m $cmd" + fi +done + +# Check for OVMF.fd +if [ -f /usr/share/ovmf/OVMF.fd ]; then + echo "\033[32m[ FOUND ]\033[0m /usr/share/ovmf/OVMF.fd" +else + echo "\033[31m[ MISSING ]\033[0m /usr/share/ovmf/OVMF.fd" + MISSING=1 + MISSING_LIST="$MISSING_LIST OVMF" +fi + +if [ $MISSING -eq 1 ]; then + echo "\nSome dependencies are missing. Please install them before proceeding." + echo "Missing dependencies:$MISSING_LIST" + echo "Auto install: ./requirements.sh install" + exit 1 +else + echo "\nAll dependencies are installed. :D" + exit 0 +fi \ No newline at end of file diff --git a/scripts/test_elf.sh b/scripts/test_elf.sh new file mode 100755 index 0000000..6b1f844 --- /dev/null +++ b/scripts/test_elf.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +set -e + +echo "===== mochiOS ELF Execution Test =====" +echo "" + +# 1. ユーザーアプリをビルド +echo "[1/4] Building user application..." +cd src/user/test_app +./build.sh +cd ../../.. + +# 2. initfsの内容確認 +echo "" +echo "[2/4] Checking initfs contents..." +ls -lh src/initfs/ + +# 3. test.elfのELFヘッダ確認 +echo "" +echo "[3/4] Verifying ELF header..." +file src/initfs/test.elf +readelf -h src/initfs/test.elf | grep "Entry point" + +# 4. カーネルをビルド +echo "" +echo "[4/4] Building kernel..." +cargo build + +echo "" +echo "===== Build Complete =====" +echo "Run 'cargo run' to execute the kernel with test.elf" diff --git a/scripts/write_iso.sh b/scripts/write_iso.sh new file mode 100644 index 0000000..13529ec --- /dev/null +++ b/scripts/write_iso.sh @@ -0,0 +1,176 @@ +#!/usr/bin/env bash +set -euo pipefail + +# write_iso.sh +# Create GPT (ESP + rootfs) on a target block device and install mochiOS files. +# Usage: sudo ./scripts/write_iso.sh /dev/sdX [ISO_PATH] [ROOTFS_IMG] +# If only /dev/sdX is given, defaults are used and missing artifacts are built automatically. + +ISO_PATH=${2:-target/mochiOS.iso} +ROOTFS_IMG=${3:-target/mochiOS.img} +DEV=${1:-} + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Error: required command not found: $1" >&2 + exit 1 + fi +} + +require_cmd xorriso +require_cmd parted +require_cmd mkfs.vfat +require_cmd mount +require_cmd umount +require_cmd dd +require_cmd sync +require_cmd partprobe || true + +if [ -z "$DEV" ]; then + echo "Usage: $0 /dev/sdX [ISO_PATH] [ROOTFS_IMG]" >&2 + exit 1 +fi + +if [ "$(id -u)" -ne 0 ]; then + echo "This script must be run as root (sudo)." >&2 + exit 1 +fi + +if [ ! -b "$DEV" ]; then + echo "Error: $DEV is not a block device." >&2 + exit 1 +fi + +# If artifacts missing, attempt to create them automatically +if [ ! -f "$ROOTFS_IMG" ]; then + echo "[+] Rootfs image not found: $ROOTFS_IMG" + if [ -x ./scripts/make_image.sh ]; then + echo "[+] Running ./scripts/make_image.sh to build rootfs image..." + ./scripts/make_image.sh || { echo "make_image.sh failed" >&2; exit 1; } + else + echo "Error: ./scripts/make_image.sh not found or not executable. Create $ROOTFS_IMG manually." >&2 + exit 1 + fi +fi + +if [ ! -f "$ISO_PATH" ]; then + echo "[+] ISO not found: $ISO_PATH" + if [ -x ./scripts/create_iso.sh ]; then + echo "[+] Running ./scripts/create_iso.sh to build ISO..." + ./scripts/create_iso.sh || { echo "create_iso.sh failed" >&2; exit 1; } + else + echo "Error: ./scripts/create_iso.sh not found or not executable. Create $ISO_PATH manually." >&2 + exit 1 + fi +fi + +TMP_DIR=$(mktemp -d) +trap 'rm -rf "$TMP_DIR"' EXIT + +echo "[+] Extracting efiboot.img from ISO..." +xorriso -osirrox on -indev "$ISO_PATH" -extract /efiboot.img "$TMP_DIR/efiboot.img" + +if [ ! -f "$TMP_DIR/efiboot.img" ]; then + echo "Error: efiboot.img not found inside ISO" >&2 + exit 1 +fi + +MOUNT_EFI="$TMP_DIR/mnt_efiboot" +mkdir -p "$MOUNT_EFI" +mount -o loop "$TMP_DIR/efiboot.img" "$MOUNT_EFI" + +# compute partition device names (handle nvme/mmcblk which need 'p' separator) +DEV_BASE=$(basename "$DEV") +if [[ "$DEV_BASE" =~ [0-9]$ ]]; then + PART_SUFFIX="p" +else + PART_SUFFIX="" +fi +PART1="${DEV}${PART_SUFFIX}1" +PART2="${DEV}${PART_SUFFIX}2" + +cat < $PART1 + 2) rootfs ext2 -> $PART2 (rest of disk) + - Copy EFI and system from efiboot.img into ESP + - Write $ROOTFS_IMG to $PART2 + +WARNING: This will DESTROY all data on $DEV. Continue? Type YES to proceed: +EOF + +read -r CONFIRM +if [ "$CONFIRM" != "YES" ]; then + echo "Aborted by user." >&2 + umount "$MOUNT_EFI" || true + exit 1 +fi + +# Try to unmount any mounted partitions of this device +for mp in $(lsblk -ln -o MOUNTPOINT "$DEV"* 2>/dev/null | awk 'NF'); do + umount "$mp" || true +done || true + +echo "[+] Creating GPT partition table on $DEV..." +parted -s "$DEV" mklabel gpt +parted -s "$DEV" mkpart ESP fat32 1MiB 513MiB +parted -s "$DEV" set 1 boot on +parted -s "$DEV" mkpart rootfs ext2 513MiB 100% + +# Inform kernel +partprobe "$DEV" || true +sleep 1 + +# Wait for partitions to appear +for i in 1 2; do + pdev="${DEV}${PART_SUFFIX}${i}" + tries=0 + until [ -b "$pdev" ] || [ $tries -ge 20 ]; do + sleep 0.5 + tries=$((tries+1)) + done + if [ ! -b "$pdev" ]; then + echo "Error: partition $pdev did not appear" >&2 + umount "$MOUNT_EFI" || true + exit 1 + fi +done + +echo "[+] Formatting ESP ($PART1) as FAT32..." +mkfs.vfat -F 32 -n MOCHIOS "$PART1" + +MOUNT_ESP="$TMP_DIR/mnt_esp" +mkdir -p "$MOUNT_ESP" +mount "$PART1" "$MOUNT_ESP" + +echo "[+] Copying EFI/ and system/ to ESP..." +# Ensure destination dirs exist +mkdir -p "$MOUNT_ESP/EFI" +mkdir -p "$MOUNT_ESP/system" + +cp -r "$MOUNT_EFI/EFI" "$MOUNT_ESP/" || true +cp -r "$MOUNT_EFI/system" "$MOUNT_ESP/" || true +sync + +umount "$MOUNT_ESP" +umount "$MOUNT_EFI" + +# Write rootfs image (overwrite partition content) +if [ -f "$ROOTFS_IMG" ]; then + echo "[+] Writing rootfs image $ROOTFS_IMG -> $PART2" + # Ensure partition is not mounted + if mountpoint -q "$PART2"; then + echo "Error: $PART2 is mounted, aborting" >&2 + exit 1 + fi + dd if="$ROOTFS_IMG" of="$PART2" bs=4M status=progress conv=fsync || true + sync + echo "[+] rootfs written" +else + echo "Warning: rootfs image not found: $ROOTFS_IMG" >&2 + echo "You can create it with: ./scripts/make_image.sh" >&2 +fi + +echo "[+] All done. You can now boot the machine from this USB (disable Secure Boot if necessary)." +exit 0 diff --git a/src/apps/Binder b/src/apps/Binder new file mode 160000 index 0000000..2a1d3ff --- /dev/null +++ b/src/apps/Binder @@ -0,0 +1 @@ +Subproject commit 2a1d3ff9c78d25d5ddf15c7273ab9037774563cc diff --git a/src/apps/Dock b/src/apps/Dock new file mode 160000 index 0000000..60f9e31 --- /dev/null +++ b/src/apps/Dock @@ -0,0 +1 @@ +Subproject commit 60f9e310a51db6eb47b92c595f956774bb0cc7a7 diff --git a/src/apps/Kagami b/src/apps/Kagami new file mode 160000 index 0000000..8415e39 --- /dev/null +++ b/src/apps/Kagami @@ -0,0 +1 @@ +Subproject commit 8415e3955e02dcfab371e6c157efd01f3650bbdc diff --git a/src/apps/ViewKit b/src/apps/ViewKit new file mode 160000 index 0000000..1acca46 --- /dev/null +++ b/src/apps/ViewKit @@ -0,0 +1 @@ +Subproject commit 1acca46b50022912aac536aa88053c925da3c66f diff --git a/src/boot/loader.rs b/src/boot/loader.rs index 96e4485..b67f16c 100644 --- a/src/boot/loader.rs +++ b/src/boot/loader.rs @@ -3,12 +3,30 @@ extern crate alloc; -use swiftcore::{kernel_entry, BootInfo, MemoryRegion, MemoryType}; +mod vga_console; + +use core::ptr::addr_of_mut; +use mochios::{BootInfo, MemoryRegion, MemoryType}; use uefi::prelude::*; use uefi::proto::console::gop::GraphicsOutput; +use uefi::proto::loaded_image::LoadedImage; +use uefi::proto::media::file::{File, FileAttribute, FileInfo, FileMode, FileType}; +use uefi::proto::media::fs::SimpleFileSystem; +use uefi::table::boot::{ + AllocateType, MemoryType as UefiMemType, OpenProtocolAttributes, OpenProtocolParams, +}; + +/// VGA フレームバッファへ書き出す print マクロ +macro_rules! vga_print { + ($($arg:tt)*) => {{ + let _ = core::fmt::write(&mut *vga_console::CONSOLE.lock(), format_args!($($arg)*)); + }}; +} -#[global_allocator] -static ALLOCATOR: uefi::allocator::Allocator = uefi::allocator::Allocator; +macro_rules! vga_println { + () => { vga_print!("\n") }; + ($($arg:tt)*) => { vga_print!("{}\n", format_args!($($arg)*)) }; +} static mut BOOT_INFO: BootInfo = BootInfo { physical_memory_offset: 0, @@ -20,65 +38,543 @@ static mut BOOT_INFO: BootInfo = BootInfo { memory_map_addr: 0, memory_map_len: 0, memory_map_entry_size: 0, + kernel_heap_addr: 0, + initfs_addr: 0, + initfs_size: 0, + rootfs_addr: 0, + rootfs_size: 0, }; -// メモリマップを静的に保存 static mut MEMORY_MAP: [MemoryRegion; 256] = [MemoryRegion { start: 0, len: 0, region_type: MemoryType::Reserved, }; 256]; -/// UEFIエントリーポイント +/// ELF64 ファイルヘッダ +#[repr(C)] +struct Elf64Header { + e_ident: [u8; 16], + e_type: u16, + e_machine: u16, + e_version: u32, + e_entry: u64, + e_phoff: u64, + e_shoff: u64, + e_flags: u32, + e_ehsize: u16, + e_phentsize: u16, + e_phnum: u16, + e_shentsize: u16, + e_shnum: u16, + e_shstrndx: u16, +} + +/// ELF64 プログラムヘッダ +#[repr(C)] +struct Elf64Phdr { + p_type: u32, + p_flags: u32, + p_offset: u64, + p_vaddr: u64, + p_paddr: u64, + p_filesz: u64, + p_memsz: u64, + p_align: u64, +} + +const PT_LOAD: u32 = 1; +const PT_DYNAMIC: u32 = 2; + +/// ELF64 動的セクションエントリ +#[repr(C)] +struct Elf64Dyn { + d_tag: i64, + d_val: u64, +} + +/// ELF64 RELA 再配置エントリ +#[repr(C)] +struct Elf64Rela { + r_offset: u64, + r_info: u64, + r_addend: i64, +} + +const R_X86_64_RELATIVE: u32 = 8; +const DT_NULL: i64 = 0; +const DT_RELA: i64 = 7; +const DT_RELASZ: i64 = 8; +const DT_RELAENT: i64 = 9; +const READ_CHUNK_BYTES: usize = 64 * 1024; + +#[inline] +fn tick_booting_gif() {} + +/// `\system\initfs.img` を読み込んで物理アドレスとサイズを返す +unsafe fn load_initfs(bt: &BootServices, image_handle: Handle) -> (u64, usize) { + let initfs_path = cstr16!(r"\system\initfs.img"); + + // LoadedImage デバイスを優先 + let handles: alloc::vec::Vec = + if let Ok(li) = bt.open_protocol_exclusive::(image_handle) { + if let Some(dev) = li.device() { + drop(li); + alloc::vec![dev] + } else { + bt.find_handles::().unwrap_or_default() + } + } else { + bt.find_handles::().unwrap_or_default() + }; + + for handle in handles { + tick_booting_gif(); + if let Some((addr, size)) = try_load_raw(bt, image_handle, handle, initfs_path, "initfs") { + vga_println!("initfs loaded at {:#x} ({} bytes)", addr, size); + return (addr, size); + } + } + vga_println!("[WARN] initfs.img not found, initfs will be empty"); + (0, 0) +} + +/// 指定ハンドルから任意ファイルをページ単位でロードし (物理アドレス, サイズ) を返す +unsafe fn try_load_raw( + bt: &BootServices, + agent: Handle, + handle: Handle, + path: &uefi::CStr16, + label: &str, +) -> Option<(u64, usize)> { + let mut sfs = bt + .open_protocol::( + OpenProtocolParams { + handle, + agent, + controller: None, + }, + OpenProtocolAttributes::GetProtocol, + ) + .ok()?; + let mut root = sfs.open_volume().ok()?; + let fh = root + .open(path, FileMode::Read, FileAttribute::empty()) + .ok()?; + let mut file = match fh.into_type().ok()? { + FileType::Regular(f) => f, + _ => return None, + }; + let mut info_buf = [0u8; 512]; + let info = file.get_info::(&mut info_buf).ok()?; + let size = info.file_size() as usize; + if size == 0 { + return None; + } + vga_println!("{} size: {} bytes, reading...", label, size); + let pages = (size + 0xFFF) / 0x1000; + let addr = bt + .allocate_pages(AllocateType::AnyPages, UefiMemType::LOADER_DATA, pages) + .ok()?; + let buf = core::slice::from_raw_parts_mut(addr as *mut u8, size); + // 大きなファイルは UEFI Read() の上限があるためチャンク単位で読む + let mut read_total = 0usize; + while read_total < size { + tick_booting_gif(); + let read_end = core::cmp::min(read_total + READ_CHUNK_BYTES, size); + let chunk = &mut buf[read_total..read_end]; + match file.read(chunk) { + Ok(0) => break, // EOF + Ok(n) => read_total += n, + Err(_) => return None, + } + } + if read_total != size { + vga_println!("[WARN] {}: read {} / {} bytes", label, read_total, size); + return None; + } + tick_booting_gif(); + Some((addr, size)) +} + +/// `\system\kernel.elf` を読み込み、PT_LOAD セグメントを物理アドレスに展開してエントリアドレスを返す +unsafe fn load_kernel(bt: &BootServices, image_handle: Handle) -> Option { + let kernel_path = cstr16!(r"\system\kernel.elf"); + + // LoadedImage からブートローダー自身のデバイスハンドルを取得して優先的に試みる + match bt.open_protocol_exclusive::(image_handle) { + Err(e) => vga_println!("LoadedImage open failed: {:?}", e.status()), + Ok(loaded_image) => match loaded_image.device() { + None => vga_println!("LoadedImage.device() = None"), + Some(dev) => { + drop(loaded_image); + tick_booting_gif(); + if let Some(entry) = try_load_from(bt, image_handle, dev, kernel_path) { + return Some(entry); + } + vga_println!("try_load_from (device handle) failed"); + } + }, + } + + // フォールバック: 全 SimpleFileSystem ハンドルをスキャンして kernel.elf を探す + match bt.find_handles::() { + Err(e) => { + vga_println!("find_handles failed: {:?}", e.status()); + return None; + } + Ok(sfs_handles) => { + vga_println!("SFS handle count: {}", sfs_handles.len()); + for handle in sfs_handles { + tick_booting_gif(); + if let Some(entry) = try_load_from(bt, image_handle, handle, kernel_path) { + return Some(entry); + } + } + } + } + + None +} + +/// 指定 SFS ハンドルから kernel.elf のロードを試みる +unsafe fn try_load_from( + bt: &BootServices, + agent: Handle, + handle: Handle, + kernel_path: &uefi::CStr16, +) -> Option { + // GetProtocol で非排他的に開く(ファームウェアが既に開いていても失敗しない) + let mut sfs = match bt.open_protocol::( + OpenProtocolParams { + handle, + agent, + controller: None, + }, + OpenProtocolAttributes::GetProtocol, + ) { + Ok(s) => s, + Err(e) => { + vga_println!("SFS open_protocol failed: {:?}", e.status()); + return None; + } + }; + let mut root = match sfs.open_volume() { + Ok(r) => r, + Err(e) => { + vga_println!("open_volume failed: {:?}", e.status()); + return None; + } + }; + + // カーネル ELF を開く + let file_handle = match root.open(kernel_path, FileMode::Read, FileAttribute::empty()) { + Ok(f) => f, + Err(e) => { + vga_println!("file open failed: {:?}", e.status()); + return None; + } + }; + let mut file = match file_handle.into_type().ok()? { + FileType::Regular(f) => f, + _ => { + vga_println!("not a regular file"); + return None; + } + }; + + // ファイルサイズを取得する + let mut info_buf = [0u8; 512]; + let info = match file.get_info::(&mut info_buf) { + Ok(i) => i, + Err(e) => { + vga_println!("get_info failed: {:?}", e.status()); + return None; + } + }; + let file_size = info.file_size() as usize; + vga_println!("kernel.elf size: {} bytes", file_size); + + // ELF ヘッダとプログラムヘッダを小さなスタックバッファで先読みし、 + // カーネルのロードアドレス範囲を確定してからページを先に確保する。 + // (フルバッファを AnyPages で先に確保すると 0x200000 に配置される場合があり、 + // その後の Address 指定確保が NOT_FOUND で失敗するため順序を逆にする) + const HDR_READ: usize = 16384; // 16 KiB: ELF ヘッダ + プログラムヘッダテーブルを包含 + let mut hdr_buf = [0u8; HDR_READ]; + let hdr_n = match file.read(&mut hdr_buf) { + Ok(n) => n, + Err(e) => { + vga_println!("header read failed: {:?}", e.status()); + return None; + } + }; + + // ELF マジック / クラス / アーキテクチャを検証 + let hdr = &*(hdr_buf.as_ptr() as *const Elf64Header); + if &hdr.e_ident[0..4] != b"\x7fELF" || hdr.e_ident[4] != 2 || hdr.e_machine != 0x3E { + vga_println!( + "ELF check failed: ident={:?} machine={:#x}", + &hdr.e_ident[0..4], + hdr.e_machine + ); + return None; + } + + // PT_LOAD セグメント全体の物理アドレス範囲を計算する + // (セグメントは隣接・重複することがあるため、個別確保は不可) + let mut load_min = u64::MAX; + let mut load_max = 0u64; + for i in 0..hdr.e_phnum as usize { + let phdr_offset = hdr.e_phoff as usize + i * hdr.e_phentsize as usize; + if phdr_offset + size_of::() > hdr_n { + break; + } + let phdr = &*(hdr_buf.as_ptr().add(phdr_offset) as *const Elf64Phdr); + if phdr.p_type != PT_LOAD || phdr.p_memsz == 0 { + continue; + } + load_min = load_min.min(phdr.p_paddr & !0xFFF); + load_max = load_max.max((phdr.p_paddr + phdr.p_memsz + 0xFFF) & !0xFFF); + } + if load_min == u64::MAX { + vga_println!("no PT_LOAD segments"); + return None; + } + + // カーネルページをフルバッファより先に確保することで、 + // 後の AnyPages 確保が同アドレスに重ならないようにする + let kernel_pages = ((load_max - load_min) as usize) / 0x1000; + vga_println!( + "kernel range {:#x}..{:#x} ({} pages)", + load_min, + load_max, + kernel_pages + ); + match bt.allocate_pages( + AllocateType::Address(load_min), + UefiMemType::LOADER_DATA, + kernel_pages, + ) { + Ok(_) => {} + Err(e) => { + vga_println!("allocate_pages kernel failed: {:?}", e.status()); + // 診断: load_min 付近のメモリマップエントリを表示する + if let Ok(mmap) = bt.memory_map(UefiMemType::LOADER_DATA) { + vga_println!("memory map around {:#x}:", load_min); + for desc in mmap.entries() { + let end = desc.phys_start + desc.page_count * 0x1000; + if end > load_min.saturating_sub(0x200000) + && desc.phys_start < load_min + 0x200000 + { + vga_println!( + " [{:#010x}..{:#010x}] type={:?}", + desc.phys_start, + end, + desc.ty + ); + } + } + } + return None; + } + } + // 全体をゼロクリア(BSS を含む) + core::ptr::write_bytes(load_min as *mut u8, 0, (load_max - load_min) as usize); + + // ファイルを先頭に巻き戻してフルバッファに再読み込みする。 + // カーネルページが確保済みなので AnyPages は別アドレスに配置される。 + if let Err(e) = file.set_position(0) { + vga_println!("set_position failed: {:?}", e.status()); + return None; + } + let pages = (file_size + 0xFFF) / 0x1000; + let buf_phys = match bt.allocate_pages(AllocateType::AnyPages, UefiMemType::LOADER_DATA, pages) + { + Ok(p) => p, + Err(e) => { + vga_println!("allocate_pages (buf) failed: {:?}", e.status()); + return None; + } + }; + let buf = core::slice::from_raw_parts_mut(buf_phys as *mut u8, file_size); + let mut read_total = 0usize; + while read_total < file_size { + tick_booting_gif(); + let read_end = core::cmp::min(read_total + READ_CHUNK_BYTES, file_size); + let chunk = &mut buf[read_total..read_end]; + match file.read(chunk) { + Ok(0) => break, + Ok(n) => read_total += n, + Err(e) => { + vga_println!("file read failed: {:?}", e.status()); + return None; + } + } + } + vga_println!("read {} / {} bytes", read_total, file_size); + if read_total != file_size { + vga_println!( + "[WARN] kernel.elf: read {} / {} bytes", + read_total, + file_size + ); + return None; + } + tick_booting_gif(); + + // 以降のコピー・再配置処理は buf を参照するため、hdr を buf から再取得する + let hdr = &*(buf.as_ptr() as *const Elf64Header); + + // 各 PT_LOAD セグメントのデータをコピー + for i in 0..hdr.e_phnum as usize { + let phdr_offset = hdr.e_phoff as usize + i * hdr.e_phentsize as usize; + if phdr_offset + size_of::() > buf.len() { + vga_println!("phdr OOB: offset={:#x}", phdr_offset); + return None; + } + let phdr = &*(buf.as_ptr().add(phdr_offset) as *const Elf64Phdr); + if phdr.p_type != PT_LOAD || phdr.p_filesz == 0 { + continue; + } + if phdr.p_filesz > phdr.p_memsz { + vga_println!("segment filesz>memsz: idx={}", i); + return None; + } + let dst_end = match phdr.p_paddr.checked_add(phdr.p_memsz) { + Some(v) => v, + None => { + vga_println!("segment paddr overflow: idx={}", i); + return None; + } + }; + if phdr.p_paddr < load_min || dst_end > load_max { + vga_println!("segment outside load range: idx={}", i); + return None; + } + let src_start = phdr.p_offset as usize; + let src_end = match src_start.checked_add(phdr.p_filesz as usize) { + Some(v) => v, + None => { + vga_println!("segment offset overflow: idx={}", i); + return None; + } + }; + if src_end > buf.len() { + vga_println!("segment exceeds file: idx={} end={:#x}", i, src_end); + return None; + } + let dst = core::slice::from_raw_parts_mut(phdr.p_paddr as *mut u8, phdr.p_filesz as usize); + let src = &buf[src_start..src_end]; + dst.copy_from_slice(src); + } + + // PT_DYNAMIC から RELA 再配置テーブルを探して R_X86_64_RELATIVE を適用する + // ロードアドレス == リンクアドレス (0x4000000) なので load_base = 0 + let mut rela_addr = 0u64; + let mut rela_size = 0usize; + let mut rela_ent = size_of::(); + for i in 0..hdr.e_phnum as usize { + let phdr_offset = hdr.e_phoff as usize + i * hdr.e_phentsize as usize; + let phdr = &*(buf.as_ptr().add(phdr_offset) as *const Elf64Phdr); + if phdr.p_type != PT_DYNAMIC { + continue; + } + let dyn_count = phdr.p_memsz as usize / size_of::(); + let dyn_ptr = phdr.p_paddr as *const Elf64Dyn; + for j in 0..dyn_count { + let entry = &*dyn_ptr.add(j); + match entry.d_tag { + DT_NULL => break, + DT_RELA => rela_addr = entry.d_val, + DT_RELASZ => rela_size = entry.d_val as usize, + DT_RELAENT => rela_ent = entry.d_val as usize, + _ => {} + } + } + break; + } + if rela_addr != 0 && rela_size > 0 && rela_ent > 0 { + let rela_count = rela_size / rela_ent; + vga_println!("applying {} RELA relocations", rela_count); + for i in 0..rela_count { + let rela = &*((rela_addr as usize + i * rela_ent) as *const Elf64Rela); + if (rela.r_info & 0xFFFF_FFFF) as u32 == R_X86_64_RELATIVE { + let target = rela.r_offset as *mut u64; + *target = rela.r_addend as u64; // load_base = 0 + } + } + } + + Some(hdr.e_entry) +} + +/// UEFI エントリーポイント #[entry] -fn main(_image_handle: Handle, mut system_table: SystemTable) -> Status { - if let Err(_) = uefi::helpers::init(&mut system_table) { +unsafe fn main(image_handle: Handle, mut system_table: SystemTable) -> Status { + if uefi::helpers::init(&mut system_table).is_err() { return Status::UNSUPPORTED; } - let _ = system_table.stdout().clear(); - let _ = system_table - .stdout() - .output_string(cstr16!("SwiftCore starting...\n")); - - // Graphics Output Protocolを取得してフレームバッファ情報を保存 - let (fb_addr, fb_size, screen_w, screen_h, stride) = { + // ── GOP フレームバッファを最初に取得してコンソールを初期化 ────────────── + let (_fb_ptr, fb_addr, fb_size, screen_w, screen_h, stride) = { let gop_handle = match system_table .boot_services() .get_handle_for_protocol::() { - Ok(handle) => handle, + Ok(h) => h, Err(_) => return Status::UNSUPPORTED, }; - let mut gop = match system_table .boot_services() .open_protocol_exclusive::(gop_handle) { - Ok(gop) => gop, + Ok(g) => g, Err(_) => return Status::UNSUPPORTED, }; - let mode_info = gop.current_mode_info(); - let mut framebuffer = gop.frame_buffer(); - - ( - framebuffer.as_mut_ptr() as u64, - framebuffer.size(), - mode_info.resolution().0, - mode_info.resolution().1, - mode_info.stride(), - ) + let mut fb = gop.frame_buffer(); + let fb_ptr = fb.as_mut_ptr() as *mut u32; + let fb_sz = fb.size(); + let (w, h) = mode_info.resolution(); + let st = mode_info.stride(); + vga_console::CONSOLE.lock().init(fb_ptr, w, h, st); + (fb_ptr, fb_ptr as u64, fb_sz, w, h, st) + }; + + vga_println!("mochiOS bootloader"); + vga_println!("Framebuffer: {}x{} stride={}", screen_w, screen_h, stride); + // booting.gif disabled; proceed without animation + + // カーネルをロード (boot_services の借用をスコープで切る) + let kernel_entry_addr = { + let bt = system_table.boot_services(); + unsafe { load_kernel(bt, image_handle) } + }; + let kernel_entry_addr = match kernel_entry_addr { + Some(addr) => addr, + None => { + vga_println!("Failed to load kernel.elf"); + return Status::NOT_FOUND; + } + }; + + // initfs を ESP から読み込む + let (initfs_addr, initfs_size) = { + let bt = system_table.boot_services(); + unsafe { load_initfs(bt, image_handle) } }; - // Boot Servicesを終了してメモリマップを取得 + // rootfs は起動後にFS層がマウントして利用するため、 + // ブートローダーではプリロードしない(起動時間短縮) + let (rootfs_addr, rootfs_size) = (0u64, 0usize); + + // Boot services を終了してメモリマップを取得 let (_system_table, memory_map_iter) = - unsafe { system_table.exit_boot_services(uefi::table::boot::MemoryType::LOADER_DATA) }; + unsafe { system_table.exit_boot_services(UefiMemType::LOADER_DATA) }; - // メモリマップを静的配列にコピー let map_count; unsafe { - let mut count = 0; + let mut count = 0usize; for (i, desc) in memory_map_iter.entries().enumerate() { if i >= 256 { break; @@ -87,12 +583,11 @@ fn main(_image_handle: Handle, mut system_table: SystemTable) -> Status { start: desc.phys_start, len: desc.page_count * 4096, region_type: match desc.ty { - uefi::table::boot::MemoryType::CONVENTIONAL => MemoryType::Usable, - uefi::table::boot::MemoryType::ACPI_RECLAIM => MemoryType::AcpiReclaimable, - uefi::table::boot::MemoryType::ACPI_NON_VOLATILE => MemoryType::AcpiNvs, - uefi::table::boot::MemoryType::UNUSABLE => MemoryType::BadMemory, - uefi::table::boot::MemoryType::LOADER_CODE - | uefi::table::boot::MemoryType::LOADER_DATA => { + UefiMemType::CONVENTIONAL => MemoryType::Usable, + UefiMemType::ACPI_RECLAIM => MemoryType::AcpiReclaimable, + UefiMemType::ACPI_NON_VOLATILE => MemoryType::AcpiNvs, + UefiMemType::UNUSABLE => MemoryType::BadMemory, + UefiMemType::LOADER_CODE | UefiMemType::LOADER_DATA => { MemoryType::BootloaderReclaimable } _ => MemoryType::Reserved, @@ -113,10 +608,17 @@ fn main(_image_handle: Handle, mut system_table: SystemTable) -> Status { BOOT_INFO.stride = stride; BOOT_INFO.memory_map_addr = MEMORY_MAP.as_ptr() as u64; BOOT_INFO.memory_map_len = map_count; - BOOT_INFO.memory_map_entry_size = core::mem::size_of::(); + BOOT_INFO.memory_map_entry_size = size_of::(); + // kernel_heap_addr はカーネル自身が entry.rs 内で設定する + BOOT_INFO.kernel_heap_addr = 0; + BOOT_INFO.initfs_addr = initfs_addr; + BOOT_INFO.initfs_size = initfs_size; + BOOT_INFO.rootfs_addr = rootfs_addr; + BOOT_INFO.rootfs_size = rootfs_size; } - unsafe { - kernel_entry(&*core::ptr::addr_of!(BOOT_INFO)); - } + // カーネルへジャンプ (system V AMD64 ABI) + let kernel_entry: unsafe extern "sysv64" fn(*mut BootInfo) -> ! = + core::mem::transmute(kernel_entry_addr); + unsafe { kernel_entry(addr_of_mut!(BOOT_INFO)) } } diff --git a/src/boot/vga_console.rs b/src/boot/vga_console.rs new file mode 100644 index 0000000..edb306c --- /dev/null +++ b/src/boot/vga_console.rs @@ -0,0 +1,361 @@ +//! ブートローダー用 VGA フレームバッファコンソール +//! +//! GOP で取得したフレームバッファに直接ピクセルを書き込む。 +//! UEFI ConOut や ExitBootservices の影響を受けない。 + +use core::fmt; + +/// 8×8 ビットマップフォント (ASCII 0x20–0x7F) +/// 各要素 = 1文字のビットマップ (8 行 × 8 列) +/// ビット7 が左端、ビット0 が右端 +#[rustfmt::skip] +static FONT_8X8: [[u8; 8]; 96] = [ + // 0x20 ' ' + [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + // 0x21 '!' + [0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00], + // 0x22 '"' + [0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + // 0x23 '#' + [0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00], + // 0x24 '$' + [0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00], + // 0x25 '%' + [0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00], + // 0x26 '&' + [0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00], + // 0x27 '\'' + [0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00], + // 0x28 '(' + [0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00], + // 0x29 ')' + [0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00], + // 0x2A '*' + [0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00], + // 0x2B '+' + [0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00], + // 0x2C ',' + [0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06], + // 0x2D '-' + [0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00], + // 0x2E '.' + [0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00], + // 0x2F '/' + [0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00], + // 0x30 '0' + [0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00], + // 0x31 '1' + [0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00], + // 0x32 '2' + [0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00], + // 0x33 '3' + [0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00], + // 0x34 '4' + [0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00], + // 0x35 '5' + [0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00], + // 0x36 '6' + [0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00], + // 0x37 '7' + [0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00], + // 0x38 '8' + [0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00], + // 0x39 '9' + [0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00], + // 0x3A ':' + [0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00], + // 0x3B ';' + [0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06], + // 0x3C '<' + [0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00], + // 0x3D '=' + [0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00], + // 0x3E '>' + [0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00], + // 0x3F '?' + [0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00], + // 0x40 '@' + [0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00], + // 0x41 'A' + [0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00], + // 0x42 'B' + [0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00], + // 0x43 'C' + [0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00], + // 0x44 'D' + [0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00], + // 0x45 'E' + [0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00], + // 0x46 'F' + [0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00], + // 0x47 'G' + [0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00], + // 0x48 'H' + [0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00], + // 0x49 'I' + [0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00], + // 0x4A 'J' + [0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00], + // 0x4B 'K' + [0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00], + // 0x4C 'L' + [0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00], + // 0x4D 'M' + [0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00], + // 0x4E 'N' + [0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00], + // 0x4F 'O' + [0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00], + // 0x50 'P' + [0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00], + // 0x51 'Q' + [0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00], + // 0x52 'R' + [0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00], + // 0x53 'S' + [0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00], + // 0x54 'T' + [0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00], + // 0x55 'U' + [0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00], + // 0x56 'V' + [0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00], + // 0x57 'W' + [0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00], + // 0x58 'X' + [0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00], + // 0x59 'Y' + [0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00], + // 0x5A 'Z' + [0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00], + // 0x5B '[' + [0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00], + // 0x5C '\' + [0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00], + // 0x5D ']' + [0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00], + // 0x5E '^' + [0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00], + // 0x5F '_' + [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF], + // 0x60 '`' + [0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00], + // 0x61 'a' + [0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00], + // 0x62 'b' + [0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00], + // 0x63 'c' + [0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00], + // 0x64 'd' + [0x38, 0x30, 0x30, 0x3E, 0x33, 0x33, 0x6E, 0x00], + // 0x65 'e' + [0x00, 0x00, 0x1E, 0x33, 0x3F, 0x03, 0x1E, 0x00], + // 0x66 'f' + [0x1C, 0x36, 0x06, 0x0F, 0x06, 0x06, 0x0F, 0x00], + // 0x67 'g' + [0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F], + // 0x68 'h' + [0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00], + // 0x69 'i' + [0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00], + // 0x6A 'j' + [0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E], + // 0x6B 'k' + [0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00], + // 0x6C 'l' + [0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00], + // 0x6D 'm' + [0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00], + // 0x6E 'n' + [0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00], + // 0x6F 'o' + [0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00], + // 0x70 'p' + [0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F], + // 0x71 'q' + [0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78], + // 0x72 'r' + [0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00], + // 0x73 's' + [0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00], + // 0x74 't' + [0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00], + // 0x75 'u' + [0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00], + // 0x76 'v' + [0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00], + // 0x77 'w' + [0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00], + // 0x78 'x' + [0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00], + // 0x79 'y' + [0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F], + // 0x7A 'z' + [0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00], + // 0x7B '{' + [0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00], + // 0x7C '|' + [0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00], + // 0x7D '}' + [0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00], + // 0x7E '~' + [0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + // 0x7F DEL (block) + [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], +]; + +const CHAR_W: usize = 8; +const CHAR_H: usize = 8; +const FG_COLOR: u32 = 0x00CCCCCC; // 明るいグレー +const BG_COLOR: u32 = 0x00000000; // 黒 + +/// ブートフェーズ専用 VGA フレームバッファコンソール +pub struct VgaConsole { + fb: *mut u32, + width: usize, + height: usize, + stride: usize, // ピクセル単位の1行あたりの幅 + col: usize, // 現在の文字カラム + row: usize, // 現在の文字ロウ +} + +// ブートローダーはシングルスレッド動作なので Send/Sync を手動実装 +unsafe impl Send for VgaConsole {} +unsafe impl Sync for VgaConsole {} + +impl VgaConsole { + pub const fn new() -> Self { + Self { + fb: core::ptr::null_mut(), + width: 0, + height: 0, + stride: 0, + col: 0, + row: 0, + } + } + + /// GOP フレームバッファで初期化し、画面をクリア + pub fn init(&mut self, fb_ptr: *mut u32, width: usize, height: usize, stride: usize) { + self.fb = fb_ptr; + self.width = width; + self.height = height; + self.stride = stride; + self.col = 0; + self.row = 0; + self.clear(); + } + + pub fn clear(&mut self) { + if self.fb.is_null() { + return; + } + let total = self.stride * self.height; + unsafe { + for i in 0..total { + self.fb.add(i).write_volatile(BG_COLOR); + } + } + self.col = 0; + self.row = 0; + } + + fn cols(&self) -> usize { + self.width / CHAR_W + } + fn rows(&self) -> usize { + self.height / CHAR_H + } + + /// 1行上にスクロール + fn scroll_up(&mut self) { + if self.fb.is_null() { + return; + } + let row_pixels = CHAR_H * self.stride; + let total_rows = self.rows(); + // 全行を1行分上に移動 + unsafe { + let src = self.fb.add(row_pixels); + let len = row_pixels * (total_rows - 1); + core::ptr::copy(src, self.fb, len); + // 最下行をクリア + let last_row = self.fb.add(row_pixels * (total_rows - 1)); + for i in 0..row_pixels { + last_row.add(i).write_volatile(BG_COLOR); + } + } + self.row = total_rows - 1; + } + + /// 文字を現在位置に描画(制御文字処理あり) + pub fn put_char(&mut self, c: char) { + if self.fb.is_null() { + return; + } + + match c { + '\n' => { + self.col = 0; + self.row += 1; + if self.row >= self.rows() { + self.scroll_up(); + } + return; + } + '\r' => { + self.col = 0; + return; + } + _ => {} + } + + let code = c as usize; + let glyph = if code >= 0x20 && code <= 0x7F { + &FONT_8X8[code - 0x20] + } else { + &FONT_8X8[0] // 不明文字はスペース + }; + + let px = self.col * CHAR_W; + let py = self.row * CHAR_H; + + for (row_idx, &bits) in glyph.iter().enumerate() { + for bit_idx in 0..CHAR_W { + let color = if (bits >> bit_idx) & 1 != 0 { + FG_COLOR + } else { + BG_COLOR + }; + let x = px + bit_idx; + let y = py + row_idx; + if x < self.width && y < self.height { + unsafe { + self.fb.add(y * self.stride + x).write_volatile(color); + } + } + } + } + + self.col += 1; + if self.col >= self.cols() { + self.col = 0; + self.row += 1; + if self.row >= self.rows() { + self.scroll_up(); + } + } + } +} + +impl fmt::Write for VgaConsole { + fn write_str(&mut self, s: &str) -> fmt::Result { + for c in s.chars() { + self.put_char(c); + } + Ok(()) + } +} + +/// グローバルコンソール(シングルスレッドのブートローダー用) +pub static CONSOLE: spin::Mutex = spin::Mutex::new(VgaConsole::new()); diff --git a/src/core/.cargo/config.toml b/src/core/.cargo/config.toml new file mode 100644 index 0000000..9d5a807 --- /dev/null +++ b/src/core/.cargo/config.toml @@ -0,0 +1,6 @@ +[build] +target = "x86_64-unknown-none" + +[unstable] +build-std = ["core", "alloc"] +build-std-features = ["compiler-builtins-mem"] diff --git a/src/core/Cargo.toml b/src/core/Cargo.toml new file mode 100644 index 0000000..b305ab3 --- /dev/null +++ b/src/core/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "mochios-core" +version = "0.1.0" +edition = "2021" +build = "core_build.rs" + +[[bin]] +name = "kernel" +path = "entry.rs" +test = false +bench = false + +[dependencies] +mochios = { package = "mochiOS", path = "../..", default-features = false } +linked_list_allocator = "0.10.5" + +[features] +kcfi = ["mochios/kcfi"] +cet-ibt = ["mochios/cet-ibt"] +cet-shadow-stack = ["mochios/cet-shadow-stack"] + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" diff --git a/src/core/audit.rs b/src/core/audit.rs new file mode 100644 index 0000000..daea2b1 --- /dev/null +++ b/src/core/audit.rs @@ -0,0 +1,116 @@ +//! 監査ログ +//! +//! panic の代わりに、拒否・隔離・破損検出・復旧不能な局所失敗を +//! append-only な簡易リングバッファへ記録する。 + +use core::fmt; +use core::sync::atomic::{AtomicUsize, Ordering}; + +use crate::interrupt::spinlock::SpinLock; + +const AUDIT_CAPACITY: usize = 256; +const AUDIT_MSG_LEN: usize = 160; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AuditEventKind { + Deny, + Fault, + Revoke, + Quarantine, + Restart, + Policy, + Usercopy, + Device, + Exec, + Ipc, + Memory, +} + +#[derive(Clone, Copy)] +pub struct AuditRecord { + seq: u64, + kind: AuditEventKind, + len: usize, + msg: [u8; AUDIT_MSG_LEN], +} + +impl AuditRecord { + const fn empty() -> Self { + Self { + seq: 0, + kind: AuditEventKind::Fault, + len: 0, + msg: [0; AUDIT_MSG_LEN], + } + } + + fn write_message(&mut self, message: &str) { + let bytes = message.as_bytes(); + let len = bytes.len().min(AUDIT_MSG_LEN); + self.msg[..len].copy_from_slice(&bytes[..len]); + if len < AUDIT_MSG_LEN { + self.msg[len..].fill(0); + } + self.len = len; + } + + pub fn message(&self) -> &str { + core::str::from_utf8(&self.msg[..self.len]).unwrap_or("") + } + + pub fn seq(&self) -> u64 { + self.seq + } + + pub fn kind(&self) -> AuditEventKind { + self.kind + } +} + +impl fmt::Debug for AuditRecord { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("AuditRecord") + .field("seq", &self.seq) + .field("kind", &self.kind) + .field("message", &self.message()) + .finish() + } +} + +static AUDIT_LOG: SpinLock<[AuditRecord; AUDIT_CAPACITY]> = + SpinLock::new([AuditRecord::empty(); AUDIT_CAPACITY]); +static AUDIT_SEQ: AtomicUsize = AtomicUsize::new(1); + +pub fn log(kind: AuditEventKind, message: &str) { + let seq = AUDIT_SEQ.fetch_add(1, Ordering::Relaxed) as u64; + let idx = (seq as usize) % AUDIT_CAPACITY; + { + let mut log = AUDIT_LOG.lock(); + let slot = &mut log[idx]; + slot.seq = seq; + slot.kind = kind; + slot.write_message(message); + } + crate::warn!("[AUDIT {:?} #{seq}] {}", kind, message); +} + +pub fn snapshot_into(out: &mut [AuditRecord]) -> usize { + if out.is_empty() { + return 0; + } + let next_seq = AUDIT_SEQ.load(Ordering::Acquire) as u64; + let start_seq = next_seq.saturating_sub(AUDIT_CAPACITY as u64).max(1); + let log = AUDIT_LOG.lock(); + let mut written = 0; + for seq in start_seq..next_seq { + if written >= out.len() { + break; + } + let idx = (seq as usize) % AUDIT_CAPACITY; + if log[idx].seq == seq { + out[written] = log[idx]; + written += 1; + } + } + written +} diff --git a/src/core/core_build.rs b/src/core/core_build.rs new file mode 100644 index 0000000..f1ab3fe --- /dev/null +++ b/src/core/core_build.rs @@ -0,0 +1,5 @@ +fn main() { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + println!("cargo:rustc-link-arg=-T{}/kernel.ld", manifest_dir); + println!("cargo:rerun-if-changed=kernel.ld"); +} diff --git a/src/core/cpu.rs b/src/core/cpu.rs new file mode 100644 index 0000000..1c875f5 --- /dev/null +++ b/src/core/cpu.rs @@ -0,0 +1,675 @@ +//! CPU機能の初期化 +//! +//! CR0/CR4レジスタの設定、SSE/FPUの有効化など + +use crate::sprintln; +use core::arch::asm; +use core::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use spin::Mutex; + +static FSGSBASE_SUPPORTED: AtomicBool = AtomicBool::new(false); +static UMIP_SUPPORTED: AtomicBool = AtomicBool::new(false); +static SMEP_SUPPORTED: AtomicBool = AtomicBool::new(false); +static SMAP_SUPPORTED: AtomicBool = AtomicBool::new(false); +static SMAP_ENABLED: AtomicBool = AtomicBool::new(false); +static IBPB_SUPPORTED: AtomicBool = AtomicBool::new(false); +static CET_IBT_SUPPORTED: AtomicBool = AtomicBool::new(false); +static CET_SHSTK_SUPPORTED: AtomicBool = AtomicBool::new(false); +static RDRAND_SUPPORTED: AtomicBool = AtomicBool::new(false); +static SPEC_CTRL_MASK: AtomicU64 = AtomicU64::new(0); +static BOOT_ENTROPY: AtomicU64 = AtomicU64::new(0); +static CMOS_LOCK: Mutex<()> = Mutex::new(()); + +#[derive(Clone, Copy, PartialEq, Eq)] +enum CpuVendor { + Intel, + Amd, + Other, +} + +#[derive(Clone, Copy)] +struct CpuidResult { + eax: u32, + ebx: u32, + ecx: u32, + edx: u32, +} + +#[inline] +fn cpuid(leaf: u32, subleaf: u32) -> CpuidResult { + // rbx は LLVM が予約するため、xchg で退避・復帰する。 + let eax: u32; + let ebx: u64; + let ecx: u32; + let edx: u32; + unsafe { + asm!( + "xchg {rbx_tmp}, rbx", + "cpuid", + "xchg {rbx_tmp}, rbx", + inout("eax") leaf => eax, + inout("ecx") subleaf => ecx, + rbx_tmp = inout(reg) 0u64 => ebx, + out("edx") edx, + options(nomem, nostack) + ); + } + CpuidResult { + eax, + ebx: ebx as u32, + ecx, + edx, + } +} + +/// CPUの初期化(SSE/FPU/NXE/WP/UMIP/SMEP/SMAP/SpecCtrl有効化) +pub fn init() { + crate::info!("Initializing CPU features..."); + + detect_cpu_features(); + + unsafe { + enable_nxe(); + enable_write_protect(); + enable_fpu(); + enable_sse(); + enable_umip(); + enable_smep_smap(); + enable_speculation_controls(); + report_optional_control_flow_features(); + } +} + +fn detect_cpu_features() { + FSGSBASE_SUPPORTED.store(cpu_has_fsgsbase(), Ordering::Release); + UMIP_SUPPORTED.store(cpu_has_umip(), Ordering::Release); + SMEP_SUPPORTED.store(cpu_has_smep(), Ordering::Release); + SMAP_SUPPORTED.store(cpu_has_smap(), Ordering::Release); + RDRAND_SUPPORTED.store(cpu_has_rdrand(), Ordering::Release); +} + +/// EFER.NXEを有効化(NO_EXECUTEページテーブルフラグを機能させる) +/// +/// NXE (No-Execute Enable) を IA32_EFER MSR (0xC0000080) のビット11にセットする。 +/// これにより PTE の bit 63 (NO_EXECUTE) が有効になり、データページでのコード実行を防ぐ。 +unsafe fn enable_nxe() { + const IA32_EFER: u32 = 0xC000_0080; + const NXE_BIT: u64 = 1 << 11; + let lo: u32; + let hi: u32; + asm!("rdmsr", in("ecx") IA32_EFER, out("eax") lo, out("edx") hi, options(nomem, nostack)); + let efer = ((hi as u64) << 32) | (lo as u64); + if efer & NXE_BIT == 0 { + let new_efer = efer | NXE_BIT; + asm!( + "wrmsr", + in("ecx") IA32_EFER, + in("eax") (new_efer as u32), + in("edx") ((new_efer >> 32) as u32), + options(nomem, nostack) + ); + crate::info!("EFER.NXE enabled"); + } +} + +/// FPUを有効化 +unsafe fn enable_fpu() { + // CR0レジスタを読み取り + let mut cr0: u64; + asm!("mov {}, cr0", out(reg) cr0, options(nomem, nostack)); + + // ビット2 (EM - Emulation) をクリア + cr0 &= !(1 << 2); + // ビット1 (MP - Monitor Coprocessor) をセット + cr0 |= 1 << 1; + // ビット5 (NE - Numeric Error) をセット + cr0 |= 1 << 5; + + // CR0レジスタに書き込み + asm!("mov cr0, {}", in(reg) cr0, options(nomem, nostack)); +} + +/// CR0.WP を有効化して supervisor write-protect を強制する +unsafe fn enable_write_protect() { + let mut cr0 = read_cr0(); + const CR0_WP_BIT: u64 = 1 << 16; + if (cr0 & CR0_WP_BIT) == 0 { + cr0 |= CR0_WP_BIT; + write_cr0(cr0); + crate::info!("CR0.WP enabled"); + } +} + +/// SSEを有効化 +unsafe fn enable_sse() { + // CR4レジスタを読み取り + let mut cr4: u64; + asm!("mov {}, cr4", out(reg) cr4, options(nomem, nostack)); + + // ビット9 (OSFXSR) をセット - FXSAVE/FXRSTOR命令のサポート + cr4 |= 1 << 9; + // ビット10 (OSXMMEXCPT) をセット - SSE例外のサポート + cr4 |= 1 << 10; + // ビット16 (FSGSBASE) をセット - RDFSBASE/WRFSBASE命令のサポート (TLS用) + // CPUID leaf 7, EBX bit 0 でサポート確認 + if FSGSBASE_SUPPORTED.load(Ordering::Acquire) { + cr4 |= 1 << 16; + crate::info!("FSGSBASE enabled"); + } else { + crate::info!("FSGSBASE not supported, using IA32_FS_BASE MSR"); + } + + // CR4レジスタに書き込み + asm!("mov cr4, {}", in(reg) cr4, options(nomem, nostack)); +} + +/// UMIP を有効化してユーザーモードからの SGDT/SIDT 等による情報漏えいを抑止する +unsafe fn enable_umip() { + if !UMIP_SUPPORTED.load(Ordering::Acquire) { + crate::warn!("UMIP not supported; skipping"); + return; + } + + let mut cr4 = read_cr4(); + cr4 |= 1 << 11; + write_cr4(cr4); + crate::info!("UMIP enabled"); +} + +/// SMEP/SMAPを有効化 +unsafe fn enable_smep_smap() { + let mut cr4 = read_cr4(); + + if SMEP_SUPPORTED.load(Ordering::Acquire) { + // ビット20 (SMEP) をセット - カーネルモードでのユーザーページ実行禁止 (L-1修正) + // ret2usr 等のカーネルモード特権昇格攻撃を防ぐ + cr4 |= 1 << 20; + crate::info!("SMEP enabled"); + } else { + crate::warn!("SMEP not supported; skipping"); + } + + if SMAP_SUPPORTED.load(Ordering::Acquire) { + // ビット21 (SMAP) をセット - カーネルモードでのユーザーページアクセス禁止 (L-1修正) + // カーネルが誤ってユーザー空間メモリを読み書きする脆弱性を防ぐ + cr4 |= 1 << 21; + SMAP_ENABLED.store(true, Ordering::Release); + crate::info!("SMAP enabled"); + } else { + crate::warn!("SMAP not supported; skipping"); + } + + write_cr4(cr4); +} + +unsafe fn enable_speculation_controls() { + const IA32_SPEC_CTRL: u32 = 0x48; + const IA32_PRED_CMD: u32 = 0x49; + const IA32_EFER: u32 = 0xC000_0080; + const SPEC_CTRL_IBRS: u64 = 1 << 0; + const SPEC_CTRL_STIBP: u64 = 1 << 1; + const SPEC_CTRL_BHI_DIS_S: u64 = 1 << 10; + const EFER_AUTOIBRS: u64 = 1 << 21; + + let vendor = cpu_vendor(); + + let mut spec_ctrl_mask = 0u64; + if cpu_has_ibrs_ibpb() && !cpu_has_amd_autoibrs() { + spec_ctrl_mask |= SPEC_CTRL_IBRS; + } + if cpu_has_stibp() { + spec_ctrl_mask |= SPEC_CTRL_STIBP; + } + if vendor == CpuVendor::Intel && cpu_has_bhi_ctrl() { + spec_ctrl_mask |= SPEC_CTRL_BHI_DIS_S; + } + if spec_ctrl_mask != 0 { + let current = read_msr(IA32_SPEC_CTRL); + write_msr(IA32_SPEC_CTRL, current | spec_ctrl_mask); + SPEC_CTRL_MASK.store(spec_ctrl_mask, Ordering::Release); + crate::info!("IA32_SPEC_CTRL hardened with mask {:#x}", spec_ctrl_mask); + } + + if cpu_has_amd_autoibrs() { + let efer = read_msr(IA32_EFER); + if (efer & EFER_AUTOIBRS) == 0 { + write_msr(IA32_EFER, efer | EFER_AUTOIBRS); + crate::info!("AMD AutoIBRS enabled"); + } + } + + if cpu_has_ibrs_ibpb() { + let _ = IA32_PRED_CMD; + IBPB_SUPPORTED.store(true, Ordering::Release); + crate::info!("IBPB supported"); + } +} + +unsafe fn report_optional_control_flow_features() { + let ibt = cpu_has_cet_ibt(); + let shstk = cpu_has_cet_shadow_stack(); + CET_IBT_SUPPORTED.store(ibt, Ordering::Release); + CET_SHSTK_SUPPORTED.store(shstk, Ordering::Release); + crate::info!( + "Optional CFI/CET support detected: IBT={}, shadow_stack={}", + ibt, + shstk + ); +} + +fn cpuid_leaf7_ebx() -> u32 { + cpuid(7, 0).ebx +} + +fn cpuid_leaf7_ecx() -> u32 { + cpuid(7, 0).ecx +} + +fn cpuid_leaf7_edx() -> u32 { + cpuid(7, 0).edx +} + +fn cpu_has_cet_shadow_stack() -> bool { + (cpuid_leaf7_ecx() & (1 << 7)) != 0 +} + +fn cpu_has_cet_ibt() -> bool { + (cpuid_leaf7_edx() & (1 << 20)) != 0 +} + +fn cpuid_leaf7_subleaf2_edx() -> u32 { + cpuid(7, 2).edx +} + +fn cpuid_max_extended_leaf() -> u32 { + cpuid(0x8000_0000, 0).eax +} + +fn cpuid_ext_8000_0008_ebx() -> u32 { + if cpuid_max_extended_leaf() < 0x8000_0008 { + return 0; + } + cpuid(0x8000_0008, 0).ebx +} + +fn cpuid_ext_8000_0021_eax() -> u32 { + if cpuid_max_extended_leaf() < 0x8000_0021 { + return 0; + } + cpuid(0x8000_0021, 0).eax +} + +fn cpu_vendor() -> CpuVendor { + let result = cpuid(0, 0); + match (result.ebx, result.edx, result.ecx) { + (0x756e_6547, 0x4965_6e69, 0x6c65_746e) => CpuVendor::Intel, + (0x6874_7541, 0x6974_6e65, 0x444d_4163) => CpuVendor::Amd, + _ => CpuVendor::Other, + } +} + +fn cpuid_leaf1_ecx() -> u32 { + cpuid(1, 0).ecx +} + +/// CPUID で UMIP サポートを確認 (leaf 7, ECX bit 2) +fn cpu_has_umip() -> bool { + (cpuid_leaf7_ecx() & (1 << 2)) != 0 +} + +/// CPUID で FSGSBASE サポートを確認 (leaf 7, EBX bit 0) +fn cpu_has_fsgsbase() -> bool { + (cpuid_leaf7_ebx() & (1 << 0)) != 0 +} + +/// CPUID で SMEP サポートを確認 (leaf 7, EBX bit 7) +fn cpu_has_smep() -> bool { + (cpuid_leaf7_ebx() & (1 << 7)) != 0 +} + +/// CPUID で SMAP サポートを確認 (leaf 7, EBX bit 20) +fn cpu_has_smap() -> bool { + (cpuid_leaf7_ebx() & (1 << 20)) != 0 +} + +/// CPUID で IBRS/IBPB サポートを確認 (leaf 7, EDX bit 26) +fn cpu_has_ibrs_ibpb() -> bool { + (cpuid_leaf7_edx() & (1 << 26)) != 0 +} + +/// CPUID で STIBP サポートを確認 +fn cpu_has_stibp() -> bool { + ((cpuid_leaf7_edx() & (1 << 27)) != 0) || ((cpuid_ext_8000_0008_ebx() & (1 << 15)) != 0) +} + +/// Intel BHI control bit の有無を確認 (leaf 7, subleaf 2, EDX bit 4) +fn cpu_has_bhi_ctrl() -> bool { + (cpuid_leaf7_subleaf2_edx() & (1 << 4)) != 0 +} + +/// AMD AutoIBRS サポートを確認 (Fn8000_0021:EAX bit 8) +fn cpu_has_amd_autoibrs() -> bool { + cpu_vendor() == CpuVendor::Amd && (cpuid_ext_8000_0021_eax() & (1 << 8)) != 0 +} + +/// CPUID で RDRAND サポートを確認 (leaf 1, ECX bit 30) +fn cpu_has_rdrand() -> bool { + (cpuid_leaf1_ecx() & (1 << 30)) != 0 +} + +/// 可能なら CPU のハードウェア乱数 (RDRAND) を返す +pub fn hw_random_u64() -> Option { + if !RDRAND_SUPPORTED.load(Ordering::Acquire) { + return None; + } + for _ in 0..10 { + let value: u64; + let ok: u8; + unsafe { + asm!( + "rdrand {val}", + "setc {ok}", + val = out(reg) value, + ok = out(reg_byte) ok, + options(nomem, nostack) + ); + } + if ok != 0 { + return Some(value); + } + } + None +} + +/// FS ベースを書き込む (WRFSBASE または IA32_FS_BASE MSR) +/// +/// # Safety +/// 呼び出し側は `val` が現在スレッドの有効な TLS ベース値であることを保証する必要がある。 +pub unsafe fn write_fs_base(val: u64) { + if FSGSBASE_SUPPORTED.load(Ordering::Acquire) { + asm!("wrfsbase {}", in(reg) val, options(nostack, preserves_flags)); + } else { + // IA32_FS_BASE MSR = 0xC0000100 + let lo = val as u32; + let hi = (val >> 32) as u32; + asm!("wrmsr", in("ecx") 0xC000_0100u32, in("eax") lo, in("edx") hi, options(nomem, nostack)); + } +} + +/// FS ベースを読み込む (RDFSBASE または IA32_FS_BASE MSR) +/// +/// # Safety +/// 呼び出し側は、現在の実行コンテキストで FS ベース読み出しが安全であることを保証する必要がある。 +pub unsafe fn read_fs_base() -> u64 { + if FSGSBASE_SUPPORTED.load(Ordering::Acquire) { + let val: u64; + asm!("rdfsbase {}", out(reg) val, options(nostack, preserves_flags)); + val + } else { + let lo: u32; + let hi: u32; + asm!("rdmsr", in("ecx") 0xC000_0100u32, out("eax") lo, out("edx") hi, options(nomem, nostack)); + ((hi as u64) << 32) | (lo as u64) + } +} + +pub fn is_smap_enabled() -> bool { + // CR4レジスタから現在の状態を直接読む(キャッシュではなく実値) + unsafe { + let cr4 = read_cr4(); + const CR4_SMAP_BIT: u64 = 1 << 21; + (cr4 & CR4_SMAP_BIT) != 0 + } +} + +/// SMAP を一時的に無効化(カーネル初期化時など必要な場合に使用) +pub unsafe fn disable_smap() { + let mut cr4 = read_cr4(); + const CR4_SMAP_BIT: u64 = 1 << 21; + if (cr4 & CR4_SMAP_BIT) != 0 { + cr4 &= !CR4_SMAP_BIT; + write_cr4(cr4); + } +} + +/// SMAP を再度有効化 +pub unsafe fn enable_smap() { + let mut cr4 = read_cr4(); + const CR4_SMAP_BIT: u64 = 1 << 21; + if SMAP_ENABLED.load(Ordering::Acquire) && (cr4 & CR4_SMAP_BIT) == 0 { + cr4 |= CR4_SMAP_BIT; + write_cr4(cr4); + } +} + +#[inline] +pub fn speculation_barrier() { + #[cfg(target_arch = "x86_64")] + unsafe { + asm!("lfence", options(nomem, nostack, preserves_flags)); + } +} + +pub fn branch_predictor_barrier() { + const IA32_PRED_CMD: u32 = 0x49; + if IBPB_SUPPORTED.load(Ordering::Acquire) { + unsafe { + write_msr(IA32_PRED_CMD, 1); + } + } + speculation_barrier(); +} + +pub struct SmapSmepGuard { + smap_was: bool, + smep_was: bool, +} + +impl SmapSmepGuard { + /// Create a guard that disables SMAP and SMEP if they are currently enabled. + /// On drop, the previous state is restored. + pub fn new() -> Self { + unsafe { + let smap = is_smap_enabled(); + // SMEP state read from CR4 directly + let cr4 = read_cr4(); + const CR4_SMEP_BIT: u64 = 1 << 20; + let smep = (cr4 & CR4_SMEP_BIT) != 0; + + if smap { + disable_smap(); + } + if smep { + // clear SMEP bit + let mut cr4 = read_cr4(); + cr4 &= !CR4_SMEP_BIT; + write_cr4(cr4); + } + + SmapSmepGuard { + smap_was: smap, + smep_was: smep, + } + } + } +} + +impl Drop for SmapSmepGuard { + fn drop(&mut self) { + unsafe { + // restore SMEP first + if self.smep_was { + let mut cr4 = read_cr4(); + const CR4_SMEP_BIT: u64 = 1 << 20; + cr4 |= CR4_SMEP_BIT; + write_cr4(cr4); + } + if self.smap_was { + enable_smap(); + } + } + } +} + +pub fn reassert_runtime_hardening() { + unsafe { + let mut cr0 = read_cr0(); + let mut cr4 = read_cr4(); + cr0 |= 1 << 16; + if UMIP_SUPPORTED.load(Ordering::Acquire) { + cr4 |= 1 << 11; + } + if SMEP_SUPPORTED.load(Ordering::Acquire) { + cr4 |= 1 << 20; + } + if SMAP_SUPPORTED.load(Ordering::Acquire) { + cr4 |= 1 << 21; + } + write_cr0(cr0); + write_cr4(cr4); + let spec_ctrl_mask = SPEC_CTRL_MASK.load(Ordering::Acquire); + if spec_ctrl_mask != 0 { + const IA32_SPEC_CTRL: u32 = 0x48; + let current = read_msr(IA32_SPEC_CTRL); + if (current & spec_ctrl_mask) != spec_ctrl_mask { + write_msr(IA32_SPEC_CTRL, current | spec_ctrl_mask); + } + } + } +} + +#[inline] +fn rdtsc() -> u64 { + let lo: u32; + let hi: u32; + unsafe { + asm!("rdtsc", out("eax") lo, out("edx") hi, options(nomem, nostack, preserves_flags)); + } + ((hi as u64) << 32) | (lo as u64) +} + +#[inline] +fn aslr_mix64(mut x: u64) -> u64 { + x ^= x >> 30; + x = x.wrapping_mul(0xbf58_476d_1ce4_e5b9); + x ^= x >> 27; + x = x.wrapping_mul(0x94d0_49bb_1331_11eb); + x ^ (x >> 31) +} + +#[inline] +fn cmos_read(reg: u8) -> u8 { + use x86_64::instructions::port::Port; + unsafe { + let mut index = Port::::new(0x70); + let mut data = Port::::new(0x71); + index.write(0x80 | reg); + data.read() + } +} + +#[inline] +fn bcd_to_bin(v: u8) -> u8 { + (v & 0x0f) + ((v >> 4) * 10) +} + +fn rtc_entropy_u64() -> u64 { + let _guard = CMOS_LOCK.lock(); + while (cmos_read(0x0A) & 0x80) != 0 { + core::hint::spin_loop(); + } + let mut sec = cmos_read(0x00); + let mut min = cmos_read(0x02); + let mut hour = cmos_read(0x04); + let mut day = cmos_read(0x07); + let mut mon = cmos_read(0x08); + let mut year = cmos_read(0x09); + let reg_b = cmos_read(0x0B); + if (reg_b & 0x04) == 0 { + sec = bcd_to_bin(sec); + min = bcd_to_bin(min); + hour = bcd_to_bin(hour & 0x7f); + day = bcd_to_bin(day); + mon = bcd_to_bin(mon); + year = bcd_to_bin(year); + } + (sec as u64) + | ((min as u64) << 8) + | ((hour as u64) << 16) + | ((day as u64) << 24) + | ((mon as u64) << 32) + | ((year as u64) << 40) +} + +/// ASLR 用のブート時エントロピーを返す(同一ブート中は固定、ブート間は変化を期待)。 +pub fn boot_entropy_u64() -> u64 { + let cached = BOOT_ENTROPY.load(Ordering::Relaxed); + if cached != 0 { + return cached; + } + + let mut seed = rdtsc() + ^ rtc_entropy_u64().rotate_left(19) + ^ (core::ptr::addr_of!(BOOT_ENTROPY) as u64).rotate_left(7); + if let Some(hw) = hw_random_u64() { + seed ^= hw.rotate_left(23); + } + if seed == 0 { + seed = 0x243f_6a88_85a3_08d3; + } + + let mixed = aslr_mix64(seed); + match BOOT_ENTROPY.compare_exchange(0, mixed, Ordering::SeqCst, Ordering::Relaxed) { + Ok(_) => mixed, + Err(v) => v, + } +} + +unsafe fn read_msr(msr: u32) -> u64 { + let lo: u32; + let hi: u32; + asm!( + "rdmsr", + in("ecx") msr, + out("eax") lo, + out("edx") hi, + options(nomem, nostack) + ); + ((hi as u64) << 32) | (lo as u64) +} + +unsafe fn write_msr(msr: u32, val: u64) { + let lo = val as u32; + let hi = (val >> 32) as u32; + asm!( + "wrmsr", + in("ecx") msr, + in("eax") lo, + in("edx") hi, + options(nomem, nostack) + ); +} + +#[inline] +unsafe fn read_cr0() -> u64 { + let cr0: u64; + asm!("mov {}, cr0", out(reg) cr0, options(nomem, nostack)); + cr0 +} + +#[inline] +unsafe fn write_cr0(val: u64) { + asm!("mov cr0, {}", in(reg) val, options(nomem, nostack)); +} + +#[inline] +unsafe fn read_cr4() -> u64 { + let cr4: u64; + asm!("mov {}, cr4", out(reg) cr4, options(nomem, nostack)); + cr4 +} + +#[inline] +unsafe fn write_cr4(val: u64) { + asm!("mov cr4, {}", in(reg) val, options(nomem, nostack)); +} diff --git a/src/core/elf/loader.rs b/src/core/elf/loader.rs new file mode 100644 index 0000000..72f1f74 --- /dev/null +++ b/src/core/elf/loader.rs @@ -0,0 +1,198 @@ +extern crate alloc; +use alloc::vec::Vec; +use core::convert::TryInto; + +/// ELF64ヘッダとプログラムヘッダの定義 +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct Elf64Ehdr { + /// ELF識別子 + pub e_ident: [u8; 16], + /// ELFのタイプ + pub e_type: u16, + /// マシンアーキテクチャ + pub e_machine: u16, + /// ELFのバージョン + pub e_version: u32, + /// エントリポイントの仮想アドレス + pub e_entry: u64, + /// プログラムヘッダテーブルのファイルオフセット + pub e_phoff: u64, + /// セクションヘッダテーブルのファイルオフセット + pub e_shoff: u64, + /// ELFフラグ + pub e_flags: u32, + /// ELFヘッダのサイズ + pub e_ehsize: u16, + /// プログラムヘッダエントリのサイズ + pub e_phentsize: u16, + /// プログラムヘッダエントリの数 + pub e_phnum: u16, + /// セクションヘッダエントリのサイズ + pub e_shentsize: u16, + /// セクションヘッダエントリの数 + pub e_shnum: u16, + /// セクション名文字列テーブルのインデックス + pub e_shstrndx: u16, +} + +/// プログラムヘッダ +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct Elf64Phdr { + /// プログラムヘッダのタイプ + pub p_type: u32, + /// プログラムヘッダのフラグ + pub p_flags: u32, + /// ファイル内のオフセット + pub p_offset: u64, + /// 仮想アドレス + pub p_vaddr: u64, + /// 物理アドレス + pub p_paddr: u64, + /// ファイル内のサイズ + pub p_filesz: u64, + /// メモリ上のサイズ + pub p_memsz: u64, + /// アライメント + pub p_align: u64, +} + +/// プログラムヘッダタイプ +pub const PT_NULL: u32 = 0; +/// ロード可能セグメント +pub const PT_LOAD: u32 = 1; + +/// ELFヘッダをパースする +/// +/// ## Arguments +/// - `data`: ELFファイルのバイト列 +/// +/// ## Returns +/// ELFヘッダの構造体。パースに失敗した場合はNone +pub fn parse_elf_header(data: &[u8]) -> Option { + if data.len() < 64 { + return None; + } + + // ELFヘッダの最初の16バイトは識別子で、残りは固定サイズのフィールド + let mut e_ident = [0u8; 16]; + e_ident.copy_from_slice(&data[0..16]); + + /// ELFのマジックが正しいか + if &e_ident[0..4] != b"\x7fELF" { + return None; + } + + // 怒涛のパースと代入() + let e_type = u16::from_le_bytes(data[16..18].try_into().ok()?); + let e_machine = u16::from_le_bytes(data[18..20].try_into().ok()?); + + // ELFアーキテクチャ検証: x86-64 (EM_X86_64 = 0x3E) のみ受け付ける (MED-07) + const EM_X86_64: u16 = 0x3E; + if e_machine != EM_X86_64 { + return None; + } + + let e_version = u32::from_le_bytes(data[20..24].try_into().ok()?); + let e_entry = u64::from_le_bytes(data[24..32].try_into().ok()?); + let e_phoff = u64::from_le_bytes(data[32..40].try_into().ok()?); + let e_shoff = u64::from_le_bytes(data[40..48].try_into().ok()?); + let e_flags = u32::from_le_bytes(data[48..52].try_into().ok()?); + let e_ehsize = u16::from_le_bytes(data[52..54].try_into().ok()?); + let e_phentsize = u16::from_le_bytes(data[54..56].try_into().ok()?); + let e_phnum = u16::from_le_bytes(data[56..58].try_into().ok()?); + let e_shentsize = u16::from_le_bytes(data[58..60].try_into().ok()?); + let e_shnum = u16::from_le_bytes(data[60..62].try_into().ok()?); + let e_shstrndx = u16::from_le_bytes(data[62..64].try_into().ok()?); + + Some(Elf64Ehdr { + e_ident, + e_type, + e_machine, + e_version, + e_entry, + e_phoff, + e_shoff, + e_flags, + e_ehsize, + e_phentsize, + e_phnum, + e_shentsize, + e_shnum, + e_shstrndx, + }) +} + +/// プログラムヘッダをパースする +/// +/// ## Arguments +/// - `data`: ELFファイルのバイト列 +/// - `offset`: プログラムヘッダの開始オフセット +/// +/// ## Returns +/// プログラムヘッダの構造体。パースに失敗した場合はNone +pub fn parse_phdr(data: &[u8], offset: usize) -> Option { + if data.len() < offset + 56 { + return None; + } + + let p_type = u32::from_le_bytes(data[offset..offset + 4].try_into().ok()?); + let p_flags = u32::from_le_bytes(data[offset + 4..offset + 8].try_into().ok()?); + let p_offset = u64::from_le_bytes(data[offset + 8..offset + 16].try_into().ok()?); + let p_vaddr = u64::from_le_bytes(data[offset + 16..offset + 24].try_into().ok()?); + let p_paddr = u64::from_le_bytes(data[offset + 24..offset + 32].try_into().ok()?); + let p_filesz = u64::from_le_bytes(data[offset + 32..offset + 40].try_into().ok()?); + let p_memsz = u64::from_le_bytes(data[offset + 40..offset + 48].try_into().ok()?); + let p_align = u64::from_le_bytes(data[offset + 48..offset + 56].try_into().ok()?); + + Some(Elf64Phdr { + p_type, + p_flags, + p_offset, + p_vaddr, + p_paddr, + p_filesz, + p_memsz, + p_align, + }) +} + +/// ロード可能セグメントのリストを取得する +/// +/// ## Arguments +/// - `data`: ELFファイルのバイト列 +/// +/// ## Returns +/// セグメントのベクタ。各セグメントは (仮想アドレス, メモリサイズ, ファイルサイズ, オフセット, フラグ) のタプル。 +pub type LoadableSegment = (u64, u64, u64, u64, u32); + +pub fn list_loadable_segments(data: &[u8]) -> Option> { + let eh = parse_elf_header(data)?; + let mut res = Vec::new(); + let phoff = eh.e_phoff as usize; + let phentsize = eh.e_phentsize as usize; + let phnum = eh.e_phnum as usize; + + for i in 0..phnum { + let off = phoff + i * phentsize; + let ph = parse_phdr(data, off)?; + if ph.p_type == PT_LOAD { + res.push((ph.p_vaddr, ph.p_memsz, ph.p_filesz, ph.p_offset, ph.p_flags)); + } + } + + Some(res) +} + +/// エントリポイントの仮想アドレスを取得する +/// +/// ## Arguments +/// - `data`: ELFファイルのバイト列 +/// +/// ## Returns +/// エントリポイントの仮想アドレス。パースに失敗した場合はNone +pub fn entry_point(data: &[u8]) -> Option { + let eh = parse_elf_header(data)?; + Some(eh.e_entry) +} diff --git a/src/core/elf/mod.rs b/src/core/elf/mod.rs new file mode 100644 index 0000000..186dcf6 --- /dev/null +++ b/src/core/elf/mod.rs @@ -0,0 +1 @@ +pub mod loader; diff --git a/src/core/entry.rs b/src/core/entry.rs new file mode 100644 index 0000000..ef917c0 --- /dev/null +++ b/src/core/entry.rs @@ -0,0 +1,41 @@ +#![no_std] +#![no_main] + +//! カーネルスタンドアローンバイナリのエントリポイント +//! +//! ブートローダーは sysv64 呼び出し規約で kernel_entry(boot_info_ptr) を呼ぶ。 +//! ここで自前の LockedHeap アロケータを設定してから mochios のカーネル本体へ移譲する。 + +extern crate alloc; + +use linked_list_allocator::LockedHeap; + +/// カーネルのグローバルアロケータ +/// mem::init 内の init_heap がこの LockedHeap を初期化する +#[global_allocator] +static KERNEL_ALLOCATOR: LockedHeap = LockedHeap::empty(); + +/// ELF エントリポイント +/// +/// ブートローダーが構築した BootInfo の kernel_heap_addr フィールドを +/// 自分の KERNEL_ALLOCATOR のアドレスで上書きしてから kernel_entry を呼ぶ。 +/// これにより mochios の init_heap が正しいアロケータを初期化できる。 +#[no_mangle] +pub unsafe extern "sysv64" fn kernel_entry(boot_info_ptr: *mut mochios::BootInfo) -> ! { + // kernel_heap_addr = &KERNEL_ALLOCATOR(init_heap がここを初期化する) + (*boot_info_ptr).kernel_heap_addr = + &KERNEL_ALLOCATOR as *const LockedHeap as u64; + + // ブートローダーがロードした initfs イメージを fs モジュールに設定 + mochios::init::fs::set_image( + (*boot_info_ptr).initfs_addr, + (*boot_info_ptr).initfs_size, + ); + mochios::init::fs::set_rootfs( + (*boot_info_ptr).rootfs_addr, + (*boot_info_ptr).rootfs_size, + ); + + let boot_info: &'static mochios::BootInfo = &*(boot_info_ptr as *const _); + mochios::kernel_entry(boot_info) +} diff --git a/src/core/init/fs.rs b/src/core/init/fs.rs new file mode 100644 index 0000000..9562295 --- /dev/null +++ b/src/core/init/fs.rs @@ -0,0 +1,597 @@ +//! 起動時にメモリへ展開済みのファイルシステム (read-only) +//! +//! - root 直下+サブディレクトリ対応 +//! - 直接ブロック + 単一間接ブロック対応 +//! - 動的バッファで任意サイズのファイルを読み取り可能 + +use alloc::vec::Vec; +use core::str; + +/// EXT2ファイルシステムのマジックナンバー +pub const EXT2_MAGIC: u16 = 0xEF53; + +/// BootInfo から設定される initfs イメージへのスライス +/// ブートローダーが initfs_addr / initfs_size を設定した後に init() で初期化される +static mut INITFS_SLICE: &[u8] = &[]; + +/// rootfs (ext2) イメージへのスライス +static mut ROOTFS_SLICE: &[u8] = &[]; + +/// initfs スライスを BootInfo から設定する(kernel_entry から呼ばれる) +pub unsafe fn set_image(addr: u64, size: usize) { + if addr != 0 && size != 0 { + INITFS_SLICE = core::slice::from_raw_parts(addr as *const u8, size); + } +} + +/// rootfs スライスを BootInfo から設定する(kernel_entry から呼ばれる) +pub unsafe fn set_rootfs(addr: u64, size: usize) { + if addr != 0 && size != 0 { + ROOTFS_SLICE = core::slice::from_raw_parts(addr as *const u8, size); + } +} + +#[inline] +fn ext2_image() -> &'static [u8] { + unsafe { INITFS_SLICE } +} + +#[inline] +fn rootfs_image() -> &'static [u8] { + unsafe { ROOTFS_SLICE } +} + +/// スーパーブロックの構造体 +#[derive(Debug, Clone, Copy)] +struct Superblock { + /// ブロックサイズ (1024 << log_block_size) + block_size: u32, + /// inodeのサイズ + inode_size: u16, + /// グループあたりのinode数 + inodes_per_group: u32, +} + +/// グループディスクリプタの構造体 +#[derive(Debug, Clone, Copy)] +struct GroupDesc { + /// inodeテーブルの開始ブロック番号 + inode_table: u32, +} + +/// inodeの構造体 +#[derive(Debug, Clone, Copy)] +struct Inode { + /// ファイルの種類とアクセス権限 + mode: u16, + /// ファイルサイズ + size: u32, + /// 直接ブロック + 単一間接ブロック + 二重間接ブロックのブロック番号 + blocks: [u32; 15], +} + +/// ファイルシステムのエントリ(ファイル名とデータ) +#[derive(Debug, Clone)] +pub struct FsEntry<'a> { + /// ファイル名 + pub name: &'a str, + /// ファイルデータ + pub data: Vec, +} + +/// ファイルシステムのエントリを列挙するイテレータ +pub struct FsEntries<'a> { + /// イメージ全体のバイトスライス + image: &'a [u8], + /// スーパーブロックの情報 + sb: Superblock, + /// 対象ディレクトリのinode + inode: Inode, + /// 現在のブロックインデックス + block_idx: usize, + /// 現在のブロック内のオフセット + offset: usize, + /// ディレクトリ内の残りバイト数 + remaining_bytes: usize, +} + +/// initfsを初期化して情報を出力する +pub fn init() { + let image = ext2_image(); + let sb = match superblock(image) { + Some(sb) => sb, + None => { + crate::warn!("initfs: invalid image"); + return; + } + }; + + let root = match inode(image, sb, 2) { + Some(inode) if is_dir(inode.mode) => inode, + _ => { + crate::warn!("initfs: invalid root inode"); + return; + } + }; + + crate::debug!( + "initfs: block_size={} inode_size={}", + sb.block_size, + sb.inode_size + ); + + let mut count = 0usize; + if let Some(names) = readdir_path_in(image, "/") { + for name in names { + let size = find_inode_in_dir(image, sb, root, &name) + .and_then(|inode_num| inode(image, sb, inode_num)) + .map(|inode| inode.size as usize) + .unwrap_or(0); + crate::debug!("initfs: {} ({} bytes)", name, size); + count += 1; + } + } + crate::debug!("initfs: {} entries", count); +} + +/// ファイルを取得(rootfs を優先し、なければ initfs を検索) +/// +/// ## Arguments +/// - `name`: ルートからのパス(例: "hello.txt", "system/fonts/ter-u12b.bdf") +/// +/// ## Returns +/// - ファイルが存在すれば内容のバイトベクタ、存在しなければNone +pub fn read(name: &str) -> Option> { + // rootfs から先に検索 + if !rootfs_image().is_empty() { + if let Some(data) = read_path_in(rootfs_image(), name) { + return Some(data); + } + } + // fallback: initfs + read_path_in(ext2_image(), name) +} + +/// ファイル一覧を取得(root直下) +/// +/// ## Returns +/// - root直下のファイルとサブディレクトリを列挙するイテレータ +pub fn entries() -> FsEntries<'static> { + let sb = superblock(ext2_image()).unwrap_or(Superblock { + block_size: 1024, + inode_size: 128, + inodes_per_group: 0, + }); + let root = inode(ext2_image(), sb, 2).unwrap_or(Inode { + mode: 0, + size: 0, + blocks: [0; 15], + }); + FsEntries::new(ext2_image(), sb, root) +} + +/// 2バイトのリトルエンディアン整数を読み取る +fn read_u16(image: &[u8], offset: usize) -> Option { + let bytes = image.get(offset..offset + 2)?; + Some(u16::from_le_bytes([bytes[0], bytes[1]])) +} + +/// 4バイトのリトルエンディアン整数を読み取る +fn read_u32(image: &[u8], offset: usize) -> Option { + let bytes = image.get(offset..offset + 4)?; + Some(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])) +} + +/// スーパーブロックを読み取る +fn superblock(image: &[u8]) -> Option { + if image.len() < 2048 { + return None; + } + + let sb_off = 1024; + let magic = read_u16(image, sb_off + 56)?; + + if magic != EXT2_MAGIC { + return None; + } + + let log_block_size = read_u32(image, sb_off + 24)?; + let block_size = 1024u32.checked_shl(log_block_size)?; + let inode_size = read_u16(image, sb_off + 88)?; + let inodes_per_group = read_u32(image, sb_off + 40)?; + + Some(Superblock { + block_size, + inode_size, + inodes_per_group, + }) +} + +fn group_desc(image: &[u8], sb: Superblock, group: u32) -> Option { + let gdt_off = if sb.block_size == 1024 { + (sb.block_size * 2) as usize + } else { + sb.block_size as usize + }; + let desc_off = gdt_off + (group as usize) * 32; + let inode_table = read_u32(image, desc_off + 8)?; + Some(GroupDesc { inode_table }) +} + +fn inode(image: &[u8], sb: Superblock, inode_num: u32) -> Option { + if inode_num == 0 { + return None; + } + // inodes_per_group が 0 なら除算パニックを防ぐ (#30) + if sb.inodes_per_group == 0 { + return None; + } + let group = (inode_num - 1) / sb.inodes_per_group; + let index = (inode_num - 1) % sb.inodes_per_group; + let gd = group_desc(image, sb, group)?; + let inode_table = gd.inode_table as usize * sb.block_size as usize; + let inode_off = inode_table + (index as usize) * (sb.inode_size as usize); + + let mode = read_u16(image, inode_off)?; + let size = read_u32(image, inode_off + 4)?; + + let mut blocks = [0u32; 15]; + let blocks_off = inode_off + 40; + for (i, block) in blocks.iter_mut().enumerate() { + *block = read_u32(image, blocks_off + i * 4)?; + } + + Some(Inode { mode, size, blocks }) +} + +fn is_dir(mode: u16) -> bool { + mode & 0x4000 != 0 +} + +fn block_slice(image: &[u8], block_size: u32, block: u32) -> Option<&[u8]> { + if block == 0 { + return None; + } + let start = block as usize * block_size as usize; + let end = start + block_size as usize; + image.get(start..end) +} + +fn data_block_number( + image: &[u8], + sb: Superblock, + inode: Inode, + block_index: usize, +) -> Option { + // block_size が 0 なら除算パニックを防ぐ + if sb.block_size == 0 { + return None; + } + let entries_per_block = sb.block_size as usize / 4; + + // 直接ブロック (0-11) + if block_index < 12 { + return Some(inode.blocks[block_index]); + } + + // 単一間接ブロック (12 .. 12+N) + let idx = block_index - 12; + if idx < entries_per_block { + let indirect = inode.blocks[12]; + if indirect == 0 { + return None; + } + let block = block_slice(image, sb.block_size, indirect)?; + return read_u32(block, idx * 4); + } + + // 二重間接ブロック (12+N .. 12+N+N*N) + let idx2 = idx - entries_per_block; + if idx2 < entries_per_block * entries_per_block { + let dindirect = inode.blocks[13]; + if dindirect == 0 { + return None; + } + let l1 = block_slice(image, sb.block_size, dindirect)?; + let l1_idx = idx2 / entries_per_block; + let l1_entry = read_u32(l1, l1_idx * 4)?; + if l1_entry == 0 { + return None; + } + let l2 = block_slice(image, sb.block_size, l1_entry)?; + let l2_idx = idx2 % entries_per_block; + return read_u32(l2, l2_idx * 4); + } + + None +} + +fn read_inode_data(image: &[u8], sb: Superblock, inode_num: u32) -> Option> { + let inode = inode(image, sb, inode_num)?; + if is_dir(inode.mode) { + return Some(Vec::new()); + } + if inode.size == 0 { + return Some(Vec::new()); + } + let size = inode.size as usize; + let blocks_needed = size.div_ceil(sb.block_size as usize); + let mut buf = Vec::with_capacity(size); + + for block_idx in 0..blocks_needed { + let block_num = data_block_number(image, sb, inode, block_idx)?; + if block_num == 0 { + // スパースファイルのホール: ゼロで埋めて続行 + let to_fill = core::cmp::min(sb.block_size as usize, size - buf.len()); + buf.extend(core::iter::repeat_n(0u8, to_fill)); + if buf.len() >= size { + break; + } + continue; + } + let block = block_slice(image, sb.block_size, block_num)?; + let to_copy = core::cmp::min(block.len(), size - buf.len()); + buf.extend_from_slice(&block[..to_copy]); + if buf.len() >= size { + break; + } + } + + Some(buf) +} + +impl<'a> FsEntries<'a> { + fn new(image: &'a [u8], sb: Superblock, inode: Inode) -> Self { + Self { + image, + sb, + inode, + block_idx: 0, + offset: 0, + remaining_bytes: inode.size as usize, + } + } +} + +impl<'a> Iterator for FsEntries<'a> { + type Item = FsEntry<'a>; + + fn next(&mut self) -> Option { + loop { + if self.remaining_bytes == 0 { + return None; + } + + let block = data_block_number(self.image, self.sb, self.inode, self.block_idx)?; + if block == 0 { + return None; + } + let data = block_slice(self.image, self.sb.block_size, block)?; + if self.offset >= data.len() { + self.block_idx += 1; + self.offset = 0; + continue; + } + + let base = self.offset; + let inode = read_u32(data, base)?; + let rec_len = read_u16(data, base + 4)? as usize; + let name_len = *data.get(base + 6)? as usize; + + if rec_len == 0 { + return None; + } + + self.offset += rec_len; + self.remaining_bytes = self.remaining_bytes.saturating_sub(rec_len); + + if inode == 0 { + continue; + } + + let name_bytes = data.get(base + 8..base + 8 + name_len)?; + let name = str::from_utf8(name_bytes).ok()?; + let data = read_inode_data(self.image, self.sb, inode).unwrap_or_default(); + return Some(FsEntry { name, data }); + } + } +} + +fn find_inode_in_dir(image: &[u8], sb: Superblock, dir_inode: Inode, name: &str) -> Option { + if !is_dir(dir_inode.mode) { + return None; + } + let mut block_idx = 0usize; + let mut offset = 0usize; + let mut remaining_bytes = dir_inode.size as usize; + while remaining_bytes > 0 { + let block = data_block_number(image, sb, dir_inode, block_idx)?; + if block == 0 { + return None; + } + let data = block_slice(image, sb.block_size, block)?; + if offset >= data.len() { + block_idx += 1; + offset = 0; + continue; + } + let base = offset; + let inode_num = read_u32(data, base)?; + let rec_len = read_u16(data, base + 4)? as usize; + let name_len = *data.get(base + 6)? as usize; + if rec_len == 0 { + return None; + } + offset += rec_len; + remaining_bytes = remaining_bytes.saturating_sub(rec_len); + if inode_num == 0 { + continue; + } + let name_bytes = data.get(base + 8..base + 8 + name_len)?; + let entry_name = str::from_utf8(name_bytes).ok()?; + if entry_name == name { + return Some(inode_num); + } + } + None +} + +/// パスがディレクトリかどうかを確認する(rootfs優先、"."と"/"はルートとして扱う) +/// ファイルメタデータ: (inode_mode, size_bytes) +/// +/// - `inode_mode` は ext2 の inode mode フィールド(ファイル種別 + パーミッション) +/// - ファイルが存在しない場合は `None` +pub fn file_metadata(path: &str) -> Option<(u16, u64)> { + if !rootfs_image().is_empty() { + if let Some(m) = file_metadata_in(rootfs_image(), path) { + return Some(m); + } + } + file_metadata_in(ext2_image(), path) +} + +fn file_metadata_in(image: &[u8], path: &str) -> Option<(u16, u64)> { + let sb = superblock(image)?; + let normalized = path.trim_matches('/'); + if normalized.is_empty() || normalized == "." { + // ルートディレクトリ + let root = inode(image, sb, 2)?; + return Some((root.mode, 0)); + } + let mut current = inode(image, sb, 2)?; + let mut parts = normalized.split('/').filter(|p| !p.is_empty()).peekable(); + while let Some(part) = parts.next() { + if part == ".." || part == "." { + continue; + } + let inode_num = find_inode_in_dir(image, sb, current, part)?; + let next = inode(image, sb, inode_num)?; + if parts.peek().is_none() { + // 最終コンポーネント + return Some((next.mode, next.size as u64)); + } + current = next; + } + None +} + +pub fn is_directory(path: &str) -> bool { + if !rootfs_image().is_empty() && resolve_dir_inode_in(rootfs_image(), path).is_some() { + return true; + } + resolve_dir_inode_in(ext2_image(), path).is_some() +} + +/// ディレクトリのエントリ名一覧を返す("."と".."は除く、rootfs優先) +pub fn readdir_path(path: &str) -> Option> { + if !rootfs_image().is_empty() { + if let Some(names) = readdir_path_in(rootfs_image(), path) { + return Some(names); + } + } + readdir_path_in(ext2_image(), path) +} + +/// パスをディレクトリのinode番号に解決する("."と"/"はルート inode 2を返す) +fn resolve_dir_inode_in(image: &[u8], path: &str) -> Option { + let sb = superblock(image)?; + let normalized = path.trim_matches('/'); + // "." や "" はルートを指す + if normalized.is_empty() || normalized == "." { + let root = inode(image, sb, 2)?; + return if is_dir(root.mode) { Some(2) } else { None }; + } + let mut current_num = 2u32; + let mut current = inode(image, sb, current_num)?; + for part in normalized.split('/').filter(|p| !p.is_empty()) { + if part == "." { + continue; + } + if part == ".." { + // ".." はルートより上には行かない + continue; + } + if !is_dir(current.mode) { + return None; + } + let next_num = find_inode_in_dir(image, sb, current, part)?; + current = inode(image, sb, next_num)?; + current_num = next_num; + } + if is_dir(current.mode) { + Some(current_num) + } else { + None + } +} + +fn readdir_path_in(image: &[u8], path: &str) -> Option> { + let sb = superblock(image)?; + let dir_inode_num = resolve_dir_inode_in(image, path)?; + let dir_inode = inode(image, sb, dir_inode_num)?; + + let mut names = alloc::vec::Vec::new(); + let mut block_idx = 0usize; + let mut offset = 0usize; + let mut remaining_bytes = dir_inode.size as usize; + + while remaining_bytes > 0 { + let block_num = data_block_number(image, sb, dir_inode, block_idx)?; + if block_num == 0 { + break; + } + let data = block_slice(image, sb.block_size, block_num)?; + if offset >= data.len() { + block_idx += 1; + offset = 0; + continue; + } + let base = offset; + let entry_inode = read_u32(data, base)?; + let rec_len = read_u16(data, base + 4)? as usize; + let name_len = *data.get(base + 6)? as usize; + if rec_len == 0 { + break; + } + offset += rec_len; + remaining_bytes = remaining_bytes.saturating_sub(rec_len); + if entry_inode == 0 { + continue; + } + let name_bytes = data.get(base + 8..base + 8 + name_len)?; + let name = str::from_utf8(name_bytes).ok()?; + if name != "." && name != ".." { + names.push(alloc::string::String::from(name)); + } + } + Some(names) +} + +fn read_path_in(image: &[u8], path: &str) -> Option> { + let sb = superblock(image)?; + let mut current = inode(image, sb, 2)?; // root + + let mut parts = path.split('/').filter(|p| !p.is_empty()).peekable(); + parts.peek()?; + + while let Some(part) = parts.next() { + // ディレクトリトラバーサル防止: ".." および "." を拒否する (C-7修正) + if part == ".." || part == "." { + return None; + } + let is_last = parts.peek().is_none(); + let inode_num = find_inode_in_dir(image, sb, current, part)?; + let next_inode = inode(image, sb, inode_num)?; + if is_last { + if is_dir(next_inode.mode) { + return None; + } + return read_inode_data(image, sb, inode_num); + } + if !is_dir(next_inode.mode) { + return None; + } + current = next_inode; + } + None +} diff --git a/src/core/init/mod.rs b/src/core/init/mod.rs new file mode 100644 index 0000000..8801c22 --- /dev/null +++ b/src/core/init/mod.rs @@ -0,0 +1,81 @@ +//! 起動時に実行する初期化処理をまとめたモジュール + +use crate::{debug, interrupt, mem, task, util, BootInfo, MemoryRegion, Result}; + +pub mod fs; + +pub fn kinit(boot_info: &'static BootInfo) -> Result<&'static [MemoryRegion]> { + util::console::init(); + util::vga::init( + boot_info.framebuffer_addr, + boot_info.screen_width, + boot_info.screen_height, + boot_info.stride, + ); + + // CPU機能の初期化(SSE/FPU有効化) + crate::cpu::init(); + + let memory_map = unsafe { + core::slice::from_raw_parts( + boot_info.memory_map_addr as *const MemoryRegion, + boot_info.memory_map_len, + ) + }; + + crate::info!("Memory map has {} regions", memory_map.len()); + for (i, region) in memory_map.iter().enumerate() { + debug!( + " Region {}: {:#x} - {:#x} ({:?})", + i, + region.start, + region.start + region.len, + region.region_type + ); + if i < 5 { + crate::info!( + " Region {}: {:#x} - {:#x} ({:?})", + i, + region.start, + region.start + region.len, + region.region_type + ); + } + } + + // 先にフレームアロケータを初期化 + mem::init_frame_allocator(memory_map)?; + + // メモリ管理の初期化 + mem::init(boot_info)?; + + fs::init(); + crate::kmod::load_modules(); + if crate::kmod::fs::is_loaded() { + let rc = crate::kmod::fs::mount(0); + if rc != 0 { + crate::warn!("kmod: fs mount failed rc={}", rc); + } + } + + // MED-32修正: PIT初期化をCPU割り込み有効化より前に実行する + // 以前は enable() が init_pit() より先だったため、PIT未初期化状態でタイマー割り込みが + // 発生する可能性があった。正しい初期化順序: PIT→スケジューラ→タイマー→割り込み有効化 + interrupt::init_pit(); + task::init_scheduler(); + if !util::ps2mouse::init() { + crate::warn!("PS/2 mouse initialization failed"); + } + interrupt::enable_timer_interrupt(); + + unsafe { + x86_64::instructions::interrupts::enable(); + } + + // Initialize syscall MSRs (STAR/LSTAR/FMASK) + + // SYSCALL/SYSRET 命令サポートを初期化 + crate::syscall::syscall_entry::init_syscall(); + + Ok(memory_map) +} diff --git a/src/core/interrupt/idt.rs b/src/core/interrupt/idt.rs new file mode 100644 index 0000000..afc30f9 --- /dev/null +++ b/src/core/interrupt/idt.rs @@ -0,0 +1,1065 @@ +//! IDT (Interrupt Descriptor Table) 管理 +//! +//! IDTの初期化と例外ハンドラの定義 + +use crate::{debug, error, mem::gdt, syscall, warn}; +use spin::Once; +use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame}; +use x86_64::PrivilegeLevel; + +static IDT: Once = Once::new(); + +#[inline] +fn enter_from_user(stack_frame: &InterruptStackFrame) -> bool { + crate::syscall::syscall_entry::kpti_enter_for_trap( + stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3, + ) +} + +#[inline] +fn leave_to_user(entered_from_user: bool) { + crate::syscall::syscall_entry::kpti_leave_after_trap(entered_from_user); +} + +/// IDTを初期化 +pub fn init() { + debug!("Initializing IDT..."); + + let idt = IDT.call_once(|| { + let mut idt = InterruptDescriptorTable::new(); + + // CPU例外ハンドラ + idt.divide_error.set_handler_fn(divide_error_handler); + idt.debug.set_handler_fn(debug_handler); + idt.non_maskable_interrupt.set_handler_fn(nmi_handler); + idt.breakpoint.set_handler_fn(breakpoint_handler); + idt.overflow.set_handler_fn(overflow_handler); + idt.bound_range_exceeded + .set_handler_fn(bound_range_exceeded_handler); + idt.invalid_opcode.set_handler_fn(invalid_opcode_handler); + idt.device_not_available + .set_handler_fn(device_not_available_handler); + + // ダブルフォルトハンドラ(専用スタック使用) + unsafe { + idt.double_fault + .set_handler_fn(double_fault_handler) + .set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX); + } + + idt.invalid_tss.set_handler_fn(invalid_tss_handler); + idt.segment_not_present + .set_handler_fn(segment_not_present_handler); + idt.stack_segment_fault + .set_handler_fn(stack_segment_fault_handler); + idt.general_protection_fault + .set_handler_fn(general_protection_fault_handler); + idt.page_fault.set_handler_fn(page_fault_handler); + idt.x87_floating_point + .set_handler_fn(x87_floating_point_handler); + idt.alignment_check.set_handler_fn(alignment_check_handler); + idt.machine_check.set_handler_fn(machine_check_handler); + idt.simd_floating_point + .set_handler_fn(simd_floating_point_handler); + idt.virtualization.set_handler_fn(virtualization_handler); + + // ハードウェア割り込みハンドラ(32-47番) + idt[32].set_handler_fn(super::timer::timer_interrupt_handler); // Timer IRQ0 + idt[33].set_handler_fn(keyboard_interrupt_handler); // Keyboard IRQ1 (C-2修正) + idt[44].set_handler_fn(mouse_interrupt_handler); // Mouse IRQ12 (PS/2 AUX) + + // それ以外のハードウェア割り込みはとりあえずスタブ + for i in 34..48 { + if i == 44 { + continue; + } + idt[i].set_handler_fn(generic_interrupt_handler); + } + + // システムコール割り込み (0x80) + // naked functionなので、手動で設定 + unsafe { + let handler_addr = syscall::syscall_interrupt_handler as *const () as u64; + idt[0x80] + .set_handler_addr(x86_64::VirtAddr::new(handler_addr)) + .set_privilege_level(PrivilegeLevel::Ring3); + } + + // 48-255番も念のため設定(未使用の割り込みベクタ) + for i in 48..=255 { + if i == 0x80 { + continue; + } + idt[i].set_handler_fn(generic_interrupt_handler); + } + + idt + }); + + idt.load(); + + // IDTが正しくロードされたか確認 + use x86_64::instructions::tables::sidt; + let idtr = sidt(); + debug!( + "IDT loaded: base={:p}, limit={}", + idtr.base.as_ptr::(), + idtr.limit + ); +} + +/// CPU例外ハンドラ +/// +/// 一般的なCPU例外(例: ゼロ除算、無効命令など)を処理するためのハンドラ +/// +/// ## Arguments +/// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 +extern "x86-interrupt" fn divide_error_handler(stack_frame: InterruptStackFrame) { + let _entered_from_user = enter_from_user(&stack_frame); + let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; + error!( + "EXCEPTION: DIVIDE ERROR ({})", + if is_user_mode { + "USER MODE" + } else { + "KERNEL MODE" + } + ); + error!("{:#?}", stack_frame); + if is_user_mode { + error!("Terminating faulting user process"); + crate::task::scheduler::exit_current_process(-1); + } else { + halt_cpu(); + } +} + +/// デバッグ例外ハンドラ +/// +/// デバッグ例外は、ブレークポイントやシングルステップなどのデバッグイベントで発生する +/// +/// ## Arguments +/// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 +extern "x86-interrupt" fn debug_handler(stack_frame: InterruptStackFrame) { + let entered_from_user = enter_from_user(&stack_frame); + debug!("EXCEPTION: DEBUG"); + debug!("{:#?}", stack_frame); + leave_to_user(entered_from_user); +} + +/// NMI (Non-Maskable Interrupt) ハンドラ +/// +/// NMIはマスクできない割り込みで、通常はハードウェアの障害や緊急事態を知らせるために使用される +/// +/// ## Arguments +/// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 +extern "x86-interrupt" fn nmi_handler(stack_frame: InterruptStackFrame) { + let _entered_from_user = enter_from_user(&stack_frame); + error!("EXCEPTION: NON-MASKABLE INTERRUPT"); + warn!("{:#?}", stack_frame); + halt_cpu(); +} + +/// ブレークポイント例外ハンドラ +/// +/// ブレークポイント例外は、INT3命令によって発生する。デバッグ目的で使用する +/// +/// ## Arguments +/// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 +extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) { + let entered_from_user = enter_from_user(&stack_frame); + warn!("EXCEPTION: BREAKPOINT"); + debug!("{:#?}", stack_frame); + leave_to_user(entered_from_user); +} + +/// オーバーフロー例外ハンドラ +/// +/// オーバーフロー例外は、INTO命令によって発生する。算術演算の結果がオーバーフローした場合に使用される +/// +/// ## Arguments +/// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 +extern "x86-interrupt" fn overflow_handler(stack_frame: InterruptStackFrame) { + let _entered_from_user = enter_from_user(&stack_frame); + let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; + error!( + "EXCEPTION: OVERFLOW ({})", + if is_user_mode { + "USER MODE" + } else { + "KERNEL MODE" + } + ); + error!("{:#?}", stack_frame); + if is_user_mode { + error!("Terminating faulting user process"); + crate::task::scheduler::exit_current_process(-1); + } else { + halt_cpu(); + } +} + +/// BOUND RANGE EXCEEDED例外ハンドラ +/// +/// BOUND RANGE EXCEEDED例外は、BOUND命令によって発生する。配列の範囲外アクセスなどで使用される +/// +/// ## Arguments +/// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 +extern "x86-interrupt" fn bound_range_exceeded_handler(stack_frame: InterruptStackFrame) { + let _entered_from_user = enter_from_user(&stack_frame); + let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; + error!( + "EXCEPTION: BOUND RANGE EXCEEDED ({})", + if is_user_mode { + "USER MODE" + } else { + "KERNEL MODE" + } + ); + error!("{:#?}", stack_frame); + if is_user_mode { + error!("Terminating faulting user process"); + crate::task::scheduler::exit_current_process(-1); + } else { + halt_cpu(); + } +} + +fn read_phys_u8(phys_off: u64, phys_addr: u64) -> Option { + let virt = phys_addr.checked_add(phys_off)? as *const u8; + Some(unsafe { core::ptr::read_volatile(virt) }) +} + +fn read_phys_u64_le(phys_off: u64, phys_addr: u64) -> Option { + let mut bytes = [0u8; 8]; + for (i, b) in bytes.iter_mut().enumerate() { + let addr = phys_addr.checked_add(i as u64)?; + *b = read_phys_u8(phys_off, addr)?; + } + Some(u64::from_le_bytes(bytes)) +} + +fn read_page_table_entry(phys_off: u64, table_phys: u64, index: usize) -> Option { + let entry_addr = table_phys.checked_add((index as u64).checked_mul(8)?)?; + read_phys_u64_le(phys_off, entry_addr) +} + +fn log_page_table_entry(level: &str, index: usize, entry: u64) { + const ADDR_MASK: u64 = 0x000f_ffff_ffff_f000; + error!( + "{} entry {}: raw={:#018x}, addr={:#x}, P={}, W={}, U={}, PS={}, NX={}", + level, + index, + entry, + entry & ADDR_MASK, + (entry & (1 << 0)) != 0, + (entry & (1 << 1)) != 0, + (entry & (1 << 2)) != 0, + (entry & (1 << 7)) != 0, + (entry & (1 << 63)) != 0 + ); +} + +fn translate_virt_to_phys(l4_phys: u64, phys_off: u64, virt_addr: u64) -> Option { + use x86_64::VirtAddr; + + const ADDR_MASK: u64 = 0x000f_ffff_ffff_f000; + + let _ = VirtAddr::try_new(virt_addr).ok()?; + + let l4_idx = ((virt_addr >> 39) & 0x1ff) as usize; + let l3_idx = ((virt_addr >> 30) & 0x1ff) as usize; + let l2_idx = ((virt_addr >> 21) & 0x1ff) as usize; + let l1_idx = ((virt_addr >> 12) & 0x1ff) as usize; + + let e4 = read_page_table_entry(phys_off, l4_phys, l4_idx)?; + if (e4 & 1) == 0 { + return None; + } + + let l3_phys = e4 & ADDR_MASK; + let e3 = read_page_table_entry(phys_off, l3_phys, l3_idx)?; + if (e3 & 1) == 0 { + return None; + } + if (e3 & (1 << 7)) != 0 { + let base = e3 & 0x000f_ffff_c000_0000; + return Some(base | (virt_addr & 0x3fff_ffff)); + } + + let l2_phys = e3 & ADDR_MASK; + let e2 = read_page_table_entry(phys_off, l2_phys, l2_idx)?; + if (e2 & 1) == 0 { + return None; + } + if (e2 & (1 << 7)) != 0 { + let base = e2 & 0x000f_ffff_ffe0_0000; + return Some(base | (virt_addr & 0x1f_ffff)); + } + + let l1_phys = e2 & ADDR_MASK; + let e1 = read_page_table_entry(phys_off, l1_phys, l1_idx)?; + if (e1 & 1) == 0 { + return None; + } + + let base = e1 & ADDR_MASK; + Some(base | (virt_addr & 0xfff)) +} + +fn read_virtual_u8(l4_phys: u64, phys_off: u64, virt_addr: u64) -> Option { + let phys = translate_virt_to_phys(l4_phys, phys_off, virt_addr)?; + read_phys_u8(phys_off, phys) +} + +fn read_virtual_u64_le(l4_phys: u64, phys_off: u64, virt_addr: u64) -> Option { + let mut bytes = [0u8; 8]; + for (i, b) in bytes.iter_mut().enumerate() { + let addr = virt_addr.checked_add(i as u64)?; + *b = read_virtual_u8(l4_phys, phys_off, addr)?; + } + Some(u64::from_le_bytes(bytes)) +} + +fn dump_invalid_opcode_diagnostics(stack_frame: &InterruptStackFrame) { + use x86_64::registers::control::Cr3; + + const DUMP_BYTES: usize = 16; + const STACK_WORDS: usize = 8; + const ADDR_MASK: u64 = 0x000f_ffff_ffff_f000; + const UNREADABLE_BYTE: u8 = 0xff; + const UNREADABLE_WORD: u64 = u64::MAX; + + error!("===== INVALID OPCODE DEBUG DUMP BEGIN ====="); + error!("{:#?}", stack_frame); + + let current_tid = crate::task::current_thread_id(); + error!("Current context: tid={:?}", current_tid); + + let Some(phys_off) = crate::mem::paging::physical_memory_offset() else { + error!("Cannot dump address-space details: physical_memory_offset unavailable"); + error!("===== INVALID OPCODE DEBUG DUMP END ====="); + return; + }; + + let (cr3_frame, _) = Cr3::read(); + let l4_phys = cr3_frame.start_address().as_u64(); + error!("Active CR3 L4 physical address: {:#x}", l4_phys); + + let rip = stack_frame.instruction_pointer.as_u64(); + let rsp = stack_frame.stack_pointer.as_u64(); + + if let Some(pa) = translate_virt_to_phys(l4_phys, phys_off, rip) { + error!("RIP mapping: virt={:#x} -> phys={:#x}", rip, pa); + } else { + error!( + "RIP mapping: virt={:#x} is not mapped or non-canonical", + rip + ); + } + + let mut inst = [UNREADABLE_BYTE; DUMP_BYTES]; + for (i, b) in inst.iter_mut().enumerate() { + let Some(addr) = rip.checked_add(i as u64) else { + break; + }; + if let Some(v) = read_virtual_u8(l4_phys, phys_off, addr) { + *b = v; + } + } + error!( + "Instruction bytes @ {:#x}: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", + rip, + inst[0], inst[1], inst[2], inst[3], + inst[4], inst[5], inst[6], inst[7], + inst[8], inst[9], inst[10], inst[11], + inst[12], inst[13], inst[14], inst[15], + ); + + let l4_idx = ((rip >> 39) & 0x1ff) as usize; + let l3_idx = ((rip >> 30) & 0x1ff) as usize; + let l2_idx = ((rip >> 21) & 0x1ff) as usize; + let l1_idx = ((rip >> 12) & 0x1ff) as usize; + + if let Some(e4) = read_page_table_entry(phys_off, l4_phys, l4_idx) { + log_page_table_entry("P4", l4_idx, e4); + if (e4 & 1) != 0 { + let l3_phys = e4 & ADDR_MASK; + if let Some(e3) = read_page_table_entry(phys_off, l3_phys, l3_idx) { + log_page_table_entry("P3", l3_idx, e3); + if (e3 & 1) != 0 { + if (e3 & (1 << 7)) != 0 { + error!("P3 entry indicates 1GiB huge page mapping"); + } else { + let l2_phys = e3 & ADDR_MASK; + if let Some(e2) = read_page_table_entry(phys_off, l2_phys, l2_idx) { + log_page_table_entry("P2", l2_idx, e2); + if (e2 & 1) != 0 { + if (e2 & (1 << 7)) != 0 { + error!("P2 entry indicates 2MiB huge page mapping"); + } else { + let l1_phys = e2 & ADDR_MASK; + if let Some(e1) = + read_page_table_entry(phys_off, l1_phys, l1_idx) + { + log_page_table_entry("P1", l1_idx, e1); + } else { + error!("Failed to read P1 entry {}", l1_idx); + } + } + } else { + error!("P2 entry {} is not present", l2_idx); + } + } else { + error!("Failed to read P2 entry {}", l2_idx); + } + } + } else { + error!("P3 entry {} is not present", l3_idx); + } + } else { + error!("Failed to read P3 entry {}", l3_idx); + } + } else { + error!("P4 entry {} is not present", l4_idx); + } + } else { + error!("Failed to read P4 entry {}", l4_idx); + } + + let mut stack_words = [UNREADABLE_WORD; STACK_WORDS]; + for (i, w) in stack_words.iter_mut().enumerate() { + let Some(addr) = rsp.checked_add((i as u64) * 8) else { + break; + }; + if let Some(v) = read_virtual_u64_le(l4_phys, phys_off, addr) { + *w = v; + } + } + error!( + "Stack @ RSP {:#x}: {:#018x} {:#018x} {:#018x} {:#018x} {:#018x} {:#018x} {:#018x} {:#018x}", + rsp, + stack_words[0], stack_words[1], stack_words[2], stack_words[3], + stack_words[4], stack_words[5], stack_words[6], stack_words[7], + ); + + for (i, &candidate) in stack_words.iter().enumerate() { + if (0x4000_0000u64..0x5000_0000u64).contains(&candidate) { + let func_va = candidate.checked_add(0x40); + match func_va.and_then(|addr| read_virtual_u64_le(l4_phys, phys_off, addr)) { + Some(func_ptr) => { + error!( + "Possible FILE at stack[{}] {:#x}: funcptr[+0x40] = {:#x}", + i, candidate, func_ptr + ); + } + None => { + error!( + "Possible FILE at stack[{}] {:#x}: funcptr[+0x40] unreadable", + i, candidate + ); + } + } + + let mut bytes = [UNREADABLE_BYTE; DUMP_BYTES]; + for (j, b) in bytes.iter_mut().enumerate() { + if let Some(addr) = candidate.checked_add(j as u64) { + if let Some(v) = read_virtual_u8(l4_phys, phys_off, addr) { + *b = v; + } + } + } + error!( + "Bytes @ {:#x}: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", + candidate, + bytes[0], bytes[1], bytes[2], bytes[3], + bytes[4], bytes[5], bytes[6], bytes[7], + bytes[8], bytes[9], bytes[10], bytes[11], + bytes[12], bytes[13], bytes[14], bytes[15], + ); + } + } + + error!("===== INVALID OPCODE DEBUG DUMP END ====="); +} + +/// 無効命令例外ハンドラ +/// +/// 無効命令例外は、CPUが認識できない命令が実行されたときに発生する。ユーザーモードで発生した場合はプロセスを終了させ、カーネルモードで発生した場合はシステム全体を停止する +/// +/// ## Arguments +/// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 +extern "x86-interrupt" fn invalid_opcode_handler(stack_frame: InterruptStackFrame) { + let _entered_from_user = enter_from_user(&stack_frame); + // ユーザーモードかチェック(code_segmentのRPLビットを確認) + let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; + + error!( + "EXCEPTION: INVALID OPCODE ({})", + if is_user_mode { + "USER MODE" + } else { + "KERNEL MODE" + } + ); + if is_user_mode { + error!("User-mode invalid opcode diagnostics are redacted in this path"); + } else { + dump_invalid_opcode_diagnostics(&stack_frame); + } + + if is_user_mode { + error!("Terminating faulting user process"); + crate::task::scheduler::exit_current_process(-1); + } else { + halt_cpu(); + } +} + +/// デバイス利用不可例外ハンドラ +/// +/// デバイス利用不可例外は、FPUやSIMD命令を使用しようとしたときに、対応するデバイスが利用できない場合に発生する。通常はFPUの初期化が必要な場合に発生することが多い +/// +/// ## Arguments +/// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 +extern "x86-interrupt" fn device_not_available_handler(stack_frame: InterruptStackFrame) { + let _entered_from_user = enter_from_user(&stack_frame); + let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; + error!( + "EXCEPTION: DEVICE NOT AVAILABLE ({})", + if is_user_mode { + "USER MODE" + } else { + "KERNEL MODE" + } + ); + error!("{:#?}", stack_frame); + if is_user_mode { + error!("Terminating faulting user process"); + crate::task::scheduler::exit_current_process(-1); + } else { + halt_cpu(); + } +} + +/// ダブルフォルト例外ハンドラ +/// +/// ダブルフォルトは、例外が発生した際にさらに例外が発生した場合に発生する。通常はスタックオーバーフローや重大なシステムエラーが原因で発生することが多い。 +/// +/// ## Arguments +/// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 +/// - `error_code`: ダブルフォルトのエラーコード(通常は0だが、特定の条件下で値が設定されることがある) +extern "x86-interrupt" fn double_fault_handler( + stack_frame: InterruptStackFrame, + error_code: u64, +) -> ! { + error!("EXCEPTION: DOUBLE FAULT"); + error!("Error code: {:#x}", error_code); + error!("{:#?}", stack_frame); + halt_forever(); +} + +/// TSS無効例外ハンドラ +/// +/// TSS無効例外は、タスクスイッチングや特定のスタック操作が行われた際に、TSSが無効である場合に発生する。通常はTSSの設定ミスや不正なタスクスイッチングが原因で発生することが多い。 +/// +/// ## Arguments +/// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 +/// - `error_code`: TSS無効例外のエラーコード(通常は0だが、特定の条件下で値が設定されることがある) +extern "x86-interrupt" fn invalid_tss_handler(stack_frame: InterruptStackFrame, error_code: u64) { + let _entered_from_user = enter_from_user(&stack_frame); + let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; + error!( + "EXCEPTION: INVALID TSS ({})", + if is_user_mode { + "USER MODE" + } else { + "KERNEL MODE" + } + ); + error!("Error code: {:#x}", error_code); + error!("{:#?}", stack_frame); + if is_user_mode { + error!("Terminating faulting user process"); + crate::task::scheduler::exit_current_process(-1); + } else { + halt_cpu(); + } +} + +/// セグメント不存在例外ハンドラ +/// +/// セグメント不存在例外は、セグメントレジスタが無効なセグメントを指している場合に発生する。通常はGDTやLDTの設定ミスが原因で発生することが多い。 +/// +/// ## Arguments +/// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 +/// - `error_code`: セグメント不存在例外のエラーコード(通常は0だが、特定の条件下で値が設定されることがある) +extern "x86-interrupt" fn segment_not_present_handler( + stack_frame: InterruptStackFrame, + error_code: u64, +) { + let _entered_from_user = enter_from_user(&stack_frame); + let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; + error!( + "EXCEPTION: SEGMENT NOT PRESENT ({})", + if is_user_mode { + "USER MODE" + } else { + "KERNEL MODE" + } + ); + error!("Error code: {:#x}", error_code); + error!("{:#?}", stack_frame); + if is_user_mode { + error!("Terminating faulting user process"); + crate::task::scheduler::exit_current_process(-1); + } else { + halt_cpu(); + } +} + +/// スタックセグメントフォルト例外ハンドラ +/// +/// スタックセグメントフォルトは、スタックセグメントにアクセスしようとした際に、スタックセグメントが無効である場合に発生する。通常はスタックオーバーフローや不正なスタック操作が原因で発生することが多い。 +/// +/// ## Arguments +/// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 +/// - `error_code`: スタックセグメントフォルトのエラーコード(通常は0だが、特定の条件下で値が設定されることがある) +extern "x86-interrupt" fn stack_segment_fault_handler( + stack_frame: InterruptStackFrame, + error_code: u64, +) { + let _entered_from_user = enter_from_user(&stack_frame); + let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; + + error!( + "EXCEPTION: STACK SEGMENT FAULT ({})", + if is_user_mode { + "USER MODE" + } else { + "KERNEL MODE" + } + ); + error!("Error code: {:#x}", error_code); + warn!("{:#?}", stack_frame); + + if is_user_mode { + error!("Terminating faulting user process"); + crate::task::scheduler::exit_current_process(-1); + } else { + halt_cpu(); + } +} + +/// 一般保護例外ハンドラ +/// +/// 一般保護例外は、セグメント違反やアクセス違反などの保護違反が発生した場合に発生する。ユーザーモードで発生した場合はプロセスを終了させ、カーネルモードで発生した場合はシステム全体を停止する。 +/// +/// ## Arguments +/// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 +/// - `error_code`: 一般保護例外のエラーコード(エラーコードのビットフィールドには、外部割り込みか、どのテーブルからの例外かなどの情報が含まれる) +extern "x86-interrupt" fn general_protection_fault_handler( + stack_frame: InterruptStackFrame, + error_code: u64, +) { + let _entered_from_user = enter_from_user(&stack_frame); + let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; + + error!( + "EXCEPTION: GENERAL PROTECTION FAULT ({})", + if is_user_mode { + "USER MODE" + } else { + "KERNEL MODE" + } + ); + error!("Error code: {:#x}", error_code); + + // エラーコードの詳細を解析 + let external = (error_code & 0x1) != 0; + let table = (error_code >> 1) & 0x3; + let index = (error_code >> 3) & 0x1FFF; + + error!( + " External: {}, Table: {} ({}), Index: {}", + external, + table, + match table { + 0 => "GDT", + 1 => "IDT", + 2 | 3 => "LDT", + _ => "Unknown", + }, + index + ); + + warn!("{:#?}", stack_frame); + + if is_user_mode { + // Dump detailed virtual->physical diagnostics using the active CR3 + dump_invalid_opcode_diagnostics(&stack_frame); + error!("Terminating faulting user process"); + crate::task::scheduler::exit_current_process(-1); + } else { + halt_cpu(); + } +} + + +/// ページフォルト例外ハンドラ +/// +/// ページフォルトは、仮想メモリ管理に関連する例外で、アクセス違反やページの不在などが原因で発生する。ユーザーモードで発生した場合はプロセスを終了させ、カーネルモードで発生した場合はシステム全体を停止する。 +/// +/// ## Arguments +/// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 +/// - `error_code`: ページフォルトのエラーコード(エラーコードのビットフィールドには、ページが存在しないか、書き込みアクセスか、ユーザーモードかなどの情報が含まれる) +extern "x86-interrupt" fn page_fault_handler( + stack_frame: InterruptStackFrame, + error_code: x86_64::structures::idt::PageFaultErrorCode, +) { + use x86_64::registers::control::Cr2; + use x86_64::VirtAddr; + + let faulting_addr = Cr2::read().unwrap_or(VirtAddr::new(0)); + let is_user_mode = error_code.contains(x86_64::structures::idt::PageFaultErrorCode::USER_MODE); + let entered_from_user = crate::syscall::syscall_entry::kpti_enter_for_trap(is_user_mode); + + error!( + "EXCEPTION: PAGE FAULT ({})", + if is_user_mode { + "USER MODE" + } else { + "KERNEL MODE" + } + ); + error!("Accessed address: {:#x}", faulting_addr.as_u64()); + error!("Error code: {:?}", error_code); + error!( + " Present: {}, Write: {}, User: {}, Reserved: {}, Instruction: {}", + error_code.contains(x86_64::structures::idt::PageFaultErrorCode::PROTECTION_VIOLATION), + error_code.contains(x86_64::structures::idt::PageFaultErrorCode::CAUSED_BY_WRITE), + is_user_mode, + error_code.contains(x86_64::structures::idt::PageFaultErrorCode::MALFORMED_TABLE), + error_code.contains(x86_64::structures::idt::PageFaultErrorCode::INSTRUCTION_FETCH) + ); + + let translated = if is_user_mode { + crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id())) + .and_then(|pid| crate::task::with_process(pid, |p| p.page_table()).flatten()) + .and_then(|pt| crate::mem::paging::virt_to_phys_in_table(pt, faulting_addr.as_u64())) + .map(x86_64::PhysAddr::new) + } else { + crate::mem::paging::translate_addr(faulting_addr) + }; + if let Some(phys) = translated { + error!( + " Virtual {:#x} is mapped to physical {:#x}", + faulting_addr.as_u64(), + phys.as_u64() + ); + } else { + error!(" Virtual {:#x} is NOT mapped", faulting_addr.as_u64()); + } + + if is_user_mode { + if let Some(tid) = crate::task::current_thread_id() { + if let Some((pid, name)) = crate::task::with_thread(tid, |t| { + let pid = t.process_id(); + let name = crate::task::with_process(pid, |p| { + let mut s = alloc::string::String::new(); + s.push_str(p.name()); + s + }) + .unwrap_or_else(|| alloc::string::String::from("")); + (pid, name) + }) { + error!( + "Faulting user context: pid={:?}, tid={:?}, process='{}', rip={:#x}, rsp={:#x}", + pid, + tid, + name, + stack_frame.instruction_pointer.as_u64(), + stack_frame.stack_pointer.as_u64() + ); + } + } + + // 保護違反(既マップページへの不正アクセス)でなければスタック拡張を試みる + let is_protection_violation = + error_code.contains(x86_64::structures::idt::PageFaultErrorCode::PROTECTION_VIOLATION); + if !is_protection_violation { + if try_grow_user_stack(faulting_addr.as_u64()) { + leave_to_user(entered_from_user); + return; // スタック拡張成功 → 命令を再試行 + } + } + + error!("Terminating faulting user process"); + debug!("{:#?}", stack_frame); + crate::task::scheduler::exit_current_process(-1); + } else { + // カーネルモードでのページフォルト: システム全体を停止 + error!("FATAL: Page fault in kernel mode!"); + error!("{:#?}", stack_frame); + error!("Please report this to https://github.com/tas0dev/mochiOS/issues with the above log details. :("); + halt_cpu(); + } +} + +/// ユーザースタックの自動拡張を試みる。 +/// fault_addr がスタック下端の直下にある場合、新しいページをマップして true を返す。 +/// 最大スタックサイズは 8 MiB。 +fn try_grow_user_stack(fault_addr: u64) -> bool { + const MAX_STACK_SIZE: u64 = 8 * 1024 * 1024; // 8 MiB + + let tid = match crate::task::current_thread_id() { + Some(t) => t, + None => return false, + }; + let pid = match crate::task::with_thread(tid, |t| t.process_id()) { + Some(p) => p, + None => return false, + }; + let (stack_bottom, stack_top, page_table) = + match crate::task::with_process(pid, |p| (p.stack_bottom(), p.stack_top(), p.page_table())) + { + Some(v) => v, + None => return false, + }; + let page_table = match page_table { + Some(pt) => pt, + None => return false, + }; + if stack_bottom == 0 || stack_top == 0 { + crate::error!("[STACK_GROW] FAILED: uninitialized stack - pid={}, stack_bottom={:#x}, stack_top={:#x}", pid.as_u64(), stack_bottom, stack_top); + return false; + } + // フォルトアドレスは現在のスタック下端より下でなければならない + if fault_addr >= stack_bottom { + return false; + } + // 最大スタックサイズを超えて伸ばさない + let min_allowed = stack_bottom.saturating_sub(MAX_STACK_SIZE); + if fault_addr < min_allowed { + crate::error!( + "Stack overflow: fault at {:#x}, stack_bottom={:#x}, min allowed {:#x}", + fault_addr, + stack_bottom, + min_allowed + ); + return false; + } + // フォルトページから現在の下端まで一括マップ(通常は1ページだけ) + let new_page = (fault_addr / 4096) * 4096; + let map_size = stack_bottom - new_page; + if crate::mem::paging::map_and_copy_segment_to( + page_table, + new_page, + 0, + map_size, + &[], + true, + false, + ) + .is_ok() + { + crate::task::with_process_mut(pid, |p| p.set_stack_bottom(new_page)); + crate::debug!("Stack grown: {:#x} -> {:#x}", stack_bottom, new_page); + true + } else { + false + } +} + +/// x87浮動小数点例外ハンドラ +/// +/// x87浮動小数点例外は、x87 FPU命令の実行中にエラーが発生した場合に発生する。通常はFPUの状態が不正な場合や、無効な操作が行われた場合に発生することが多い。 +/// ユーザーモードで発生した場合はプロセスを終了させ、カーネルモードで発生した場合はシステム全体を停止する。 +/// +/// ## Arguments +/// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 +extern "x86-interrupt" fn x87_floating_point_handler(stack_frame: InterruptStackFrame) { + let _entered_from_user = enter_from_user(&stack_frame); + let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; + error!( + "EXCEPTION: X87 FLOATING POINT ({})", + if is_user_mode { + "USER MODE" + } else { + "KERNEL MODE" + } + ); + error!("{:#?}", stack_frame); + if is_user_mode { + error!("Terminating faulting user process"); + crate::task::scheduler::exit_current_process(-1); + } else { + halt_cpu(); + } +} + +/// アライメントチェック例外ハンドラ +/// +/// アライメントチェック例外は、特定のデータアクセスが適切にアライメントされていない場合に発生する。通常は、CPUが要求するアライメント要件を満たさないメモリアクセスが原因で発生することが多い。 +/// ユーザーモードで発生した場合はプロセスを終了させ、カーネルモードで発生した場合はシステム全体を停止する。 +/// +/// ## Arguments +/// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 +/// - `error_code`: アライメントチェック例外のエラーコード(エラーコードのビットフィールドには、ユーザーモードか、外部割り込みかなどの情報が含まれる) +extern "x86-interrupt" fn alignment_check_handler( + stack_frame: InterruptStackFrame, + error_code: u64, +) { + let _entered_from_user = enter_from_user(&stack_frame); + let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; + error!( + "EXCEPTION: ALIGNMENT CHECK ({})", + if is_user_mode { + "USER MODE" + } else { + "KERNEL MODE" + } + ); + error!("Error code: {:#x}", error_code); + error!("{:#?}", stack_frame); + if is_user_mode { + error!("Terminating faulting user process"); + crate::task::scheduler::exit_current_process(-1); + } else { + halt_cpu(); + } +} + +/// マシンチェック例外ハンドラ +/// +/// マシンチェック例外は、ハードウェアの障害や重大なエラーが発生した場合に発生する。通常はCPUやメモリの障害、電源の問題などが原因で発生することが多い。 +/// +/// ## Arguments +/// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 +extern "x86-interrupt" fn machine_check_handler(stack_frame: InterruptStackFrame) -> ! { + error!("EXCEPTION: MACHINE CHECK"); + error!("{:#?}", stack_frame); + halt_forever(); +} + +/// SIMD浮動小数点例外ハンドラ +/// SIMD浮動小数点例外は、SIMD命令の実行中にエラーが発生した場合に発生する。通常は、SIMDレジスタの状態が不正な場合や、無効な操作が行われた場合に発生することが多い。 +/// ユーザーモードで発生した場合はプロセスを終了させ、カーネルモードで発生した場合はシステム全体を停止する。 +/// +/// ## Arguments +/// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 +extern "x86-interrupt" fn simd_floating_point_handler(stack_frame: InterruptStackFrame) { + let _entered_from_user = enter_from_user(&stack_frame); + let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; + error!( + "EXCEPTION: SIMD FLOATING POINT ({})", + if is_user_mode { + "USER MODE" + } else { + "KERNEL MODE" + } + ); + error!("{:#?}", stack_frame); + if is_user_mode { + error!("Terminating faulting user process"); + crate::task::scheduler::exit_current_process(-1); + } else { + halt_cpu(); + } +} + +/// 仮想化例外ハンドラ +/// 仮想化例外は、仮想化機能を使用している環境で、仮想化関連のエラーが発生した場合に発生する。通常は、仮想化機能の設定ミスや、仮想化環境でサポートされていない操作が原因で発生することが多い。 +/// ユーザーモードで発生した場合はプロセスを終了させ、カーネルモードで発生した場合はシステム全体を停止する。 +/// +/// ## Arguments +/// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 +extern "x86-interrupt" fn virtualization_handler(stack_frame: InterruptStackFrame) { + let _entered_from_user = enter_from_user(&stack_frame); + let is_user_mode = stack_frame.code_segment.rpl() == PrivilegeLevel::Ring3; + error!( + "EXCEPTION: VIRTUALIZATION ({})", + if is_user_mode { + "USER MODE" + } else { + "KERNEL MODE" + } + ); + error!("{:#?}", stack_frame); + if is_user_mode { + error!("Terminating faulting user process"); + crate::task::scheduler::exit_current_process(-1); + } else { + halt_cpu(); + } +} + +/// キーボード割り込みハンドラ (IRQ1 / ベクタ 33) +/// +/// IRQ1 をIDTに登録せずに放置するとキーストロークのたびに #GP が発生し +/// OS全体が停止する (C-2修正)。このハンドラはスキャンコードを読み捨て EOI を送る。 +extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStackFrame) { + let entered_from_user = enter_from_user(&_stack_frame); + let scancode: u8 = unsafe { + let mut port = x86_64::instructions::port::Port::::new(0x60); + port.read() + }; + crate::util::ps2kbd::push_scancode(scancode); + // マスターPICにEOIを送信 (IRQ1はマスターPICが担当) + unsafe { + super::pic::PIC_MASTER.end_of_interrupt(); + } + leave_to_user(entered_from_user); +} + +/// マウス割り込みハンドラ (IRQ12 / ベクタ 44) +extern "x86-interrupt" fn mouse_interrupt_handler(_stack_frame: InterruptStackFrame) { + let entered_from_user = enter_from_user(&_stack_frame); + let byte: u8 = unsafe { + let mut port = x86_64::instructions::port::Port::::new(0x60); + port.read() + }; + crate::util::ps2mouse::push_byte(byte); + // IRQ12 はスレーブPIC配下なので、スレーブ→マスターの順でEOIを送る + super::send_eoi(44); + leave_to_user(entered_from_user); +} + +/// 一般的な割り込みハンドラ(スタブ) +/// +/// 一般的なハードウェア割り込み(例: キーボード、マウス、ネットワークカードなど)を処理するためのスタブハンドラ +/// とりあえず、割り込みが発生したことをログに出力し、EOIを送信するだけの簡単な実装 +/// +/// ## Arguments +/// - `stack_frame`: 割り込み発生時のCPU状態を表す構造体 +/// +/// このハンドラは、将来的に各デバイスに対応した具体的な処理を実装するためのプレースホルダとして使用される予定 +extern "x86-interrupt" fn generic_interrupt_handler(_stack_frame: InterruptStackFrame) { + let entered_from_user = enter_from_user(&_stack_frame); + debug!("INTERRUPT: GENERIC"); + // マスターPICのみにEOIを送信する (LOW-01) + // このハンドラはどのIRQから呼ばれるか不明のため、IRQ 0-7 (マスターのみ) を想定して + // スレーブPICへの不正なEOI送信によるスプリアス割り込みを防ぐ。 + // IRQ 8-15 が必要なデバイスは専用ハンドラで両PICにEOIを送る。 + unsafe { + super::pic::PIC_MASTER.end_of_interrupt(); + } + leave_to_user(entered_from_user); +} + +/// CPU割り込みを無効化してシステムを停止 +fn halt_cpu() { + x86_64::instructions::interrupts::disable(); + loop { + x86_64::instructions::hlt(); + } +} + +/// CPU割り込みを無効化してシステムを停止(戻らない) +fn halt_forever() -> ! { + x86_64::instructions::interrupts::disable(); + loop { + x86_64::instructions::hlt(); + } +} diff --git a/src/interrupt/mod.rs b/src/core/interrupt/mod.rs similarity index 95% rename from src/interrupt/mod.rs rename to src/core/interrupt/mod.rs index a95f53f..159fced 100644 --- a/src/interrupt/mod.rs +++ b/src/core/interrupt/mod.rs @@ -4,8 +4,9 @@ pub mod idt; pub mod pic; -pub mod timer; pub mod spinlock; +mod syscall; +pub mod timer; pub use idt::init as init_idt; pub use pic::{init as init_pic, send_eoi}; diff --git a/src/interrupt/pic.rs b/src/core/interrupt/pic.rs similarity index 86% rename from src/interrupt/pic.rs rename to src/core/interrupt/pic.rs index c4e787f..75a0d5a 100644 --- a/src/interrupt/pic.rs +++ b/src/core/interrupt/pic.rs @@ -14,27 +14,38 @@ pub struct Pic { impl Pic { /// End of Interrupt信号を送信 + /// + /// # Safety + /// 呼び出し側は、PICポートアクセスが現在の実行環境で有効であることを保証する必要がある。 pub unsafe fn end_of_interrupt(&self) { use x86_64::instructions::port::Port; Port::new(self.command).write(0x20u8); } } +/// マスタPICとスレーブPICの定義 pub const PIC_MASTER: Pic = Pic { + /// 割込みベクタオフセット offset: 32, + /// コマンドポート command: 0x20, + /// データポート data: 0x21, }; +/// スレーブPIC pub const PIC_SLAVE: Pic = Pic { + /// 割込みベクタオフセット offset: 40, + /// コマンドポート command: 0xa0, + /// データポート data: 0xa1, }; /// PICを初期化 pub fn init() { - debug!("Initializing PIC (8259A)..."); + debug!("Initializing PIC..."); unsafe { use x86_64::instructions::port::Port; diff --git a/src/interrupt/spinlock.rs b/src/core/interrupt/spinlock.rs similarity index 70% rename from src/interrupt/spinlock.rs rename to src/core/interrupt/spinlock.rs index 664fbda..0f4a95d 100644 --- a/src/interrupt/spinlock.rs +++ b/src/core/interrupt/spinlock.rs @@ -11,7 +11,9 @@ use core::sync::atomic::{AtomicBool, Ordering}; /// ロック取得時に割込みを無効化し、解放時に復元することで /// デッドロックを防止する pub struct SpinLock { + /// ロック状態を表すフラグ locked: AtomicBool, + /// 保護されるデータ data: UnsafeCell, } @@ -20,6 +22,12 @@ unsafe impl Send for SpinLock {} impl SpinLock { /// 新しいスピンロックを作成 + /// + /// ## Arguments + /// - `data`: ロックで保護されるデータ + /// + /// ## Returns + /// - `SpinLock`: 新しいスピンロックインスタンス pub const fn new(data: T) -> Self { Self { locked: AtomicBool::new(false), @@ -31,6 +39,12 @@ impl SpinLock { /// /// ロック取得時に割込みフラグの状態を保存し、 /// 割込みを無効化する + /// + /// ## Arguments + /// - `&self`: スピンロックの参照 + /// + /// ## Returns + /// - `SpinLockGuard<'_, T>`: ロックガード。ドロップ時にロックを解放し、割込み状態を復元する pub fn lock(&self) -> SpinLockGuard<'_, T> { // 現在の割込みフラグを保存 let interrupt_enabled = x86_64::instructions::interrupts::are_enabled(); @@ -59,6 +73,12 @@ impl SpinLock { /// ロックを試行(割込みを無効化) /// /// ロックが既に取得されている場合はNoneを返す + /// + /// ## Arguments + /// - `&self`: スピンロックの参照 + /// + /// ## Returns + /// - `Option>`: ロックガード。ロックが取得できた場合はSome、そうでない場合はNone pub fn try_lock(&self) -> Option> { let interrupt_enabled = x86_64::instructions::interrupts::are_enabled(); @@ -85,16 +105,37 @@ impl SpinLock { /// 内部データへの可変参照を取得(unsafe) /// /// 呼び出し側は、他のスレッドがデータにアクセスしていないことを保証する必要がある + /// + /// ## Arguments + /// - `&self`: スピンロックの参照 + /// + /// ## Returns + /// - `&mut T`: 内部データへの可変参照 + /// + /// # Safety + /// 呼び出し側は、現在このロックを保持しているか、他スレッドから同時アクセスされないことを保証する必要がある。 pub unsafe fn force_unlock(&self) { self.locked.store(false, Ordering::Release); } + + /// 内部データへの不変ポインタを取得 + /// + /// データ配置アドレスが必要な用途向け。排他制御は呼び出し側で担保すること。 + pub fn as_ptr(&self) -> *const T { + self.data.get() as *const T + } } /// スピンロックガード /// /// ドロップ時に自動的にロックを解放し、割込み状態を復元する +/// +/// ## Lifetime Parameters +/// - `'a`: スピンロックのライフタイム pub struct SpinLockGuard<'a, T> { + /// 保護されるスピンロックへの参照 lock: &'a SpinLock, + /// ロック取得時の割込み状態 interrupt_enabled: bool, } diff --git a/src/core/interrupt/syscall.rs b/src/core/interrupt/syscall.rs new file mode 100644 index 0000000..6d38932 --- /dev/null +++ b/src/core/interrupt/syscall.rs @@ -0,0 +1,51 @@ +use crate::mem::gdt; +use core::arch::asm; + +// MSR addresses +const IA32_STAR: u32 = 0xC000_0081; +const IA32_LSTAR: u32 = 0xC000_0082; +const IA32_FMASK: u32 = 0xC000_0084; + +/// MSRに値を書き込む +/// +/// ## Arguments +/// - `msr`: MSRのアドレス +/// - `value`: 書き込む値 +/// +/// ## Safety +/// - `msr`は有効なMSRアドレスでなければならない +unsafe fn wrmsr(msr: u32, value: u64) { + let low = value as u32; + let high = (value >> 32) as u32; + asm!( + "wrmsr", + in("ecx") msr, + in("eax") low, + in("edx") high, + options(nostack, preserves_flags), + ); +} + +/// システムコールのMSRを初期化する +pub fn init_syscall() { + unsafe { + let kernel_cs = gdt::kernel_code_selector() as u64; + let user_cs = gdt::user_code_selector() as u64; + // STAR: [63:48]=user_cs, [47:32]=kernel_cs + let star = (user_cs << 48) | (kernel_cs << 32); + wrmsr(IA32_STAR, star); + + // LSTAR: システムコールエントリポイントのアドレス + extern "C" { + fn syscall_entry(); + } + let addr = syscall_entry as *const () as usize as u64; + wrmsr(IA32_LSTAR, addr); + + // FMASK: システムコール実行時にクリアするフラグマスク。ここではIFをクリアして割り込みを禁止する。 + let fmask: u64 = (1 << 9); // clear IF + wrmsr(IA32_FMASK, fmask); + } +} + +// Note: syscall_entryはsyscall/syscall_entry.rsで実際のハンドラとして定義されている。 diff --git a/src/interrupt/timer.rs b/src/core/interrupt/timer.rs similarity index 65% rename from src/interrupt/timer.rs rename to src/core/interrupt/timer.rs index 157d425..4021737 100644 --- a/src/interrupt/timer.rs +++ b/src/core/interrupt/timer.rs @@ -2,7 +2,7 @@ //! //! PIT (Programmable Interval Timer) の管理とタイマー割込みハンドラ -use crate::{debug, interrupt::spinlock}; +use crate::debug; use core::sync::atomic::{AtomicU64, Ordering}; use x86_64::structures::idt::InterruptStackFrame; @@ -10,18 +10,42 @@ use x86_64::structures::idt::InterruptStackFrame; static TIMER_TICKS: AtomicU64 = AtomicU64::new(0); /// タイマー割り込みハンドラ(IRQ0) +/// +/// ## Arguments +/// - `_stack_frame`: 割り込み発生時のスタックフレーム pub extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: InterruptStackFrame) { + let entered_from_user = crate::syscall::syscall_entry::kpti_enter_for_trap( + _stack_frame.code_segment.rpl() == x86_64::PrivilegeLevel::Ring3, + ); + // タイマーカウンタを増加 - let _ticks = TIMER_TICKS.fetch_add(1, Ordering::Relaxed); + let ticks = TIMER_TICKS + .fetch_add(1, Ordering::Relaxed) + .saturating_add(1); + crate::syscall::time::wake_due_sleepers(ticks); + crate::syscall::process::wake_due_futex_waiters(ticks); - // 割り込みコンテキストではログ出力を避ける(デッドロックを防止) - // TODO: 割り込み安全なロギング機構を実装 + // スケジューラのティックを実行 + let should_schedule = crate::task::scheduler_tick(); // End of Interrupt (EOI) 信号をPICに送信 super::send_eoi(32); + + // タイムスライスが尽きた場合はプリエンプト + // switch_context がカーネルスタック状態を保存するため、 + // タイマーハンドラの iretq で自動的にユーザー/カーネルモードに戻る + if should_schedule { + crate::task::schedule_and_switch(); + } + + // ユーザーから入ってきた場合は、復帰先スレッドに応じたユーザーCR3へ戻す + crate::syscall::syscall_entry::kpti_leave_after_trap(entered_from_user); } /// 現在のタイマーティック数を取得 +/// +/// ## Returns +/// - タイマーティック数(100回 = 1秒) pub fn get_ticks() -> u64 { TIMER_TICKS.load(Ordering::Relaxed) } @@ -92,9 +116,12 @@ pub fn enable_timer_interrupt() { unsafe { use x86_64::instructions::port::Port; - // PIC master のIRQ0のマスクを解除(ビット0を0にする) - // 他の割り込みは全てマスク(0xfe = 11111110) - Port::::new(0x21).write(0xfe); + // Master: IRQ0(timer), IRQ1(keyboard), IRQ2(cascade) を許可 + // 1111_1000 = 0xF8 + Port::::new(0x21).write(0xf8); + // Slave: IRQ12(PS/2 mouse) を許可(スレーブ内ではIRQ4) + // 1110_1111 = 0xEF + Port::::new(0xa1).write(0xef); // IO待機 for _ in 0..1000 { diff --git a/src/core/kernel.ld b/src/core/kernel.ld new file mode 100644 index 0000000..f2cf135 --- /dev/null +++ b/src/core/kernel.ld @@ -0,0 +1,32 @@ +ENTRY(kernel_entry) + +SECTIONS +{ + . = 0x4000000; + + .text : { + __text_start = .; + *(.text .text.*) + __text_end = .; + } + + .rodata : ALIGN(4096) { + *(.rodata .rodata.*) + } + + .data : ALIGN(4096) { + *(.data .data.*) + } + + .bss : ALIGN(4096) { + _bss_start = .; + *(.bss .bss.*) + *(COMMON) + _bss_end = .; + } + + /DISCARD/ : { + *(.eh_frame) + *(.note .note.*) + } +} diff --git a/src/core/kernel.rs b/src/core/kernel.rs new file mode 100644 index 0000000..7d2fb4a --- /dev/null +++ b/src/core/kernel.rs @@ -0,0 +1,91 @@ +use crate::result::handle_kernel_error; +use crate::result::{Kernel, Process}; +use crate::syscall::exec::exec_kernel_with_name; +use crate::util::log::LogLevel; +use crate::{debug, info, sprintln, vprintln}; +use crate::{init::kinit, task, util, BootInfo, MemoryRegion, Result}; + +const KERNEL_THREAD_STACK_SIZE: usize = 4096 * 8; + +#[repr(align(16))] +struct KernelStack([u8; KERNEL_THREAD_STACK_SIZE]); + +static mut KERNEL_THREAD_STACK: KernelStack = KernelStack([0; KERNEL_THREAD_STACK_SIZE]); + +/// カーネルメイン関数 +fn kernel_main() -> ! { + util::log::set_level(LogLevel::Info); + debug!("Kernel started"); + + // core.serviceのみ起動(他のサービスはcore.serviceが管理) + info!("Starting core.service"); + let manager_pid = exec_kernel_with_name("core.service", "core.service"); + if manager_pid != 0 + && task::with_process(task::ProcessId::from_u64(manager_pid), |_| ()).is_some() + { + crate::syscall::exec::register_service_manager_pid(manager_pid); + } else { + crate::warn!( + "Failed to register core.service as service manager (ret={:#x})", + manager_pid + ); + } + + // カーネルはアイドル状態に入る + info!("Kernel initialization complete. Entering idle loop..."); + loop { + x86_64::instructions::hlt(); + } +} + +/// カーネルエントリポイント(kernel binary から呼ばれる) +pub fn kernel_entry(boot_info: &'static BootInfo) -> ! { + let memory_map = match kinit(boot_info) { + Ok(map) => map, + Err(e) => { + handle_kernel_error(e); + halt_forever(); + } + }; + + create_kernel_proc(boot_info, memory_map).unwrap_or_else(|e| { + handle_kernel_error(e); + halt_forever(); + }); + task::start_scheduling(); +} + +/// カーネルメインプロセスの作成 +fn create_kernel_proc( + boot_info: &'static BootInfo, + memory_map: &'static [MemoryRegion], +) -> Result<()> { + let kernel_process = task::Process::new("kernel", task::PrivilegeLevel::Core, None, 0); + let kernel_pid = kernel_process.id(); + + if task::add_process(kernel_process).is_none() { + return Err(Kernel::Process(Process::MaxProcessesReached)); + } + + let stack_addr = unsafe { (&raw const KERNEL_THREAD_STACK as *const u8) as u64 }; + let kernel_thread = task::Thread::new( + kernel_pid, + "core", + kernel_main, + stack_addr, + KERNEL_THREAD_STACK_SIZE, + ); + + if task::add_thread(kernel_thread).is_none() { + return Err(Kernel::Process(Process::MaxProcessesReached)); + } + + Ok(()) +} + +/// システムを無限ループで停止 +fn halt_forever() -> ! { + loop { + x86_64::instructions::hlt(); + } +} diff --git a/src/core/kmod/disk.rs b/src/core/kmod/disk.rs new file mode 100644 index 0000000..641a1ac --- /dev/null +++ b/src/core/kmod/disk.rs @@ -0,0 +1,38 @@ +use core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU16, Ordering}; + +#[repr(C)] +pub struct McxDiskOps { + pub probe: extern "C" fn() -> i32, +} + +static LOADED: AtomicBool = AtomicBool::new(false); +static VERSION: AtomicU16 = AtomicU16::new(0); +static DISK_OPS_PTR: AtomicPtr = AtomicPtr::new(core::ptr::null_mut()); + +pub fn register(ops: *const McxDiskOps, version: u16) -> bool { + if ops.is_null() { + return false; + } + DISK_OPS_PTR.store(ops as *mut McxDiskOps, Ordering::Release); + VERSION.store(version, Ordering::Release); + LOADED.store(true, Ordering::Release); + true +} + +pub fn is_loaded() -> bool { + LOADED.load(Ordering::Acquire) +} + +#[allow(dead_code)] +pub fn version() -> u16 { + VERSION.load(Ordering::Acquire) +} + +#[allow(dead_code)] +pub fn probe() -> i32 { + let ops = DISK_OPS_PTR.load(Ordering::Acquire); + if ops.is_null() { + return -38; + } + unsafe { ((*ops).probe)() } +} diff --git a/src/core/kmod/fs.rs b/src/core/kmod/fs.rs new file mode 100644 index 0000000..a6c38f8 --- /dev/null +++ b/src/core/kmod/fs.rs @@ -0,0 +1,180 @@ +use alloc::vec; +use alloc::vec::Vec; +use core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU16, Ordering}; + +use super::{McxBuffer, McxFsOps, McxPath, MODULE_MAX_READ_BYTES}; + +static LOADED: AtomicBool = AtomicBool::new(false); +static MOUNTED: AtomicBool = AtomicBool::new(false); +static VERSION: AtomicU16 = AtomicU16::new(0); +static FS_OPS_PTR: AtomicPtr = AtomicPtr::new(core::ptr::null_mut()); + +pub fn register(ops: *const McxFsOps, version: u16) -> bool { + if ops.is_null() { + return false; + } + // Disable SMAP/SMEP while reading module-provided ops struct + let _smap_guard = crate::cpu::SmapSmepGuard::new(); + let ops_ref = unsafe { &*ops }; + if (ops_ref.mount as usize) == 0 + || (ops_ref.read as usize) == 0 + || (ops_ref.stat as usize) == 0 + || (ops_ref.readdir as usize) == 0 + { + return false; + } + FS_OPS_PTR.store(ops as *mut McxFsOps, Ordering::Release); + VERSION.store(version, Ordering::Release); + LOADED.store(true, Ordering::Release); + true +} + +pub fn is_loaded() -> bool { + LOADED.load(Ordering::Acquire) +} + +#[allow(dead_code)] +pub fn is_mounted() -> bool { + MOUNTED.load(Ordering::Acquire) +} + +pub fn mount(device_id: u32) -> i32 { + let ops = FS_OPS_PTR.load(Ordering::Acquire); + if ops.is_null() { + return -38; + } + // Disable SMAP/SMEP while dereferencing ops in module memory + let _smap_guard = crate::cpu::SmapSmepGuard::new(); + if (unsafe { (*ops).mount } as usize) == 0 { + return -38; + } + let rc = unsafe { ((*ops).mount)(device_id) }; + if rc == 0 { + MOUNTED.store(true, Ordering::Release); + } + rc +} + +pub fn read_all(path: &str) -> Option> { + let ops = FS_OPS_PTR.load(Ordering::Acquire); + if ops.is_null() { + return crate::init::fs::read(path); + } + + // Disable SMAP/SMEP while calling into module ops + let _smap_guard = crate::cpu::SmapSmepGuard::new(); + + let mut out = Vec::new(); + let path_bytes = path.as_bytes(); + let path_arg = McxPath { + ptr: path_bytes.as_ptr(), + len: path_bytes.len(), + }; + let mut offset: u64 = 0; + let mut chunk = vec![0u8; 4096]; + + loop { + let mut nread: usize = 0; + let rc = unsafe { + ((*ops).read)( + path_arg, + offset, + McxBuffer { + ptr: chunk.as_mut_ptr(), + len: chunk.len(), + }, + &mut nread as *mut usize, + ) + }; + if rc != 0 { + if rc == -2 { + return None; + } + if out.is_empty() { + return crate::init::fs::read(path); + } + return Some(out); + } + if nread == 0 { + break; + } + if nread > chunk.len() { + return None; + } + out.extend_from_slice(&chunk[..nread]); + offset = offset.saturating_add(nread as u64); + if out.len() > MODULE_MAX_READ_BYTES { + return None; + } + } + Some(out) +} + +pub fn file_metadata(path: &str) -> Option<(u16, u64)> { + let ops = FS_OPS_PTR.load(Ordering::Acquire); + if ops.is_null() { + return None; + } + // Disable SMAP/SMEP while calling into module ops + let _smap_guard = crate::cpu::SmapSmepGuard::new(); + + let path_bytes = path.as_bytes(); + let path_arg = McxPath { + ptr: path_bytes.as_ptr(), + len: path_bytes.len(), + }; + let mut mode: u16 = 0; + let mut size: u64 = 0; + let rc = unsafe { ((*ops).stat)(path_arg, &mut mode as *mut u16, &mut size as *mut u64) }; + if rc != 0 { + return None; + } + Some((mode, size)) +} + +pub fn is_directory(path: &str) -> bool { + file_metadata(path) + .map(|(mode, _)| (mode & 0xF000) == 0x4000) + .unwrap_or(false) +} + +pub fn readdir_path(path: &str) -> Option> { + let ops = FS_OPS_PTR.load(Ordering::Acquire); + if ops.is_null() { + return None; + } + // Disable SMAP/SMEP while calling into module ops + let _smap_guard = crate::cpu::SmapSmepGuard::new(); + + let path_bytes = path.as_bytes(); + let path_arg = McxPath { + ptr: path_bytes.as_ptr(), + len: path_bytes.len(), + }; + let mut buf = vec![0u8; 16 * 1024]; + let mut out_len: usize = 0; + let rc = unsafe { + ((*ops).readdir)( + path_arg, + McxBuffer { + ptr: buf.as_mut_ptr(), + len: buf.len(), + }, + &mut out_len as *mut usize, + ) + }; + if rc != 0 || out_len > buf.len() { + return None; + } + let bytes = &buf[..out_len]; + let mut out = Vec::new(); + for raw in bytes.split(|&b| b == b'\n') { + if raw.is_empty() { + continue; + } + if let Ok(s) = core::str::from_utf8(raw) { + out.push(alloc::string::String::from(s)); + } + } + Some(out) +} diff --git a/src/core/kmod/mod.rs b/src/core/kmod/mod.rs new file mode 100644 index 0000000..48792ca --- /dev/null +++ b/src/core/kmod/mod.rs @@ -0,0 +1,451 @@ +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use core::convert::TryInto; +use core::sync::atomic::{AtomicU64, Ordering}; + +pub mod disk; +pub mod fs; + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct McxBuffer { + pub ptr: *mut u8, + pub len: usize, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct McxPath { + pub ptr: *const u8, + pub len: usize, +} + +#[repr(C)] +pub struct McxFsOps { + pub mount: extern "C" fn(device_id: u32) -> i32, + pub read: + extern "C" fn(path: McxPath, offset: u64, buf: McxBuffer, out_read: *mut usize) -> i32, + pub stat: extern "C" fn(path: McxPath, out_mode: *mut u16, out_size: *mut u64) -> i32, + pub readdir: extern "C" fn(path: McxPath, buf: McxBuffer, out_len: *mut usize) -> i32, +} + +pub const MODULE_MAX_READ_BYTES: usize = 64 * 1024 * 1024; + +const CEXT_MAGIC: &[u8; 4] = b"MCEX"; +const CEXT_FIXED_HEADER_SIZE: usize = 32; +const PT_LOAD: u32 = 1; +const SHT_SYMTAB: u32 = 2; +const SHT_DYNSYM: u32 = 11; +const SHF_ALLOC: u64 = 0x2; +const SHT_RELA: u32 = 4; +const R_X86_64_RELATIVE: u32 = 8; +const ET_DYN: u16 = 3; +const MODULE_LOAD_BASE_START: u64 = 0x0000_6000_0000_0000; +const MODULE_LOAD_GUARD: u64 = 0x20_0000; // 2MiB guard +static NEXT_MODULE_LOAD_BASE: AtomicU64 = AtomicU64::new(MODULE_LOAD_BASE_START); + +type FsInitFn = unsafe extern "C" fn() -> *const McxFsOps; +type DiskInitFn = unsafe extern "C" fn() -> *const disk::McxDiskOps; + +struct CextHeader { + module_version: u16, + name_len: usize, + dep_count: usize, + header_size: usize, + elf_size: usize, +} + +struct CextMeta { + name: String, + deps: Vec, + module_version: u16, + elf: Vec, +} + +pub fn load_modules() { + let Some(entries) = crate::init::fs::readdir_path("/Modules") else { + crate::info!("kmod: /Modules is empty"); + return; + }; + + let mut module_paths: Vec = entries + .into_iter() + .filter(|name| name.ends_with(".cext")) + .map(|name| alloc::format!("/Modules/{}", name)) + .collect(); + module_paths.sort(); + + for path in module_paths { + let Some(bytes) = crate::init::fs::read(&path) else { + crate::warn!("kmod: failed to read {}", path); + continue; + }; + let Some(meta) = parse_cext(&bytes) else { + crate::warn!("kmod: invalid cext {}", path); + continue; + }; + + let mut missing_dep = false; + for dep in &meta.deps { + if dep == "disk" && !disk::is_loaded() { + crate::warn!("kmod: skip {} (disk not loaded)", meta.name); + missing_dep = true; + break; + } + } + if missing_dep { + continue; + } + + match meta.name.as_str() { + "disk" => { + if let Some(addr) = load_elf_symbol(&meta.elf, "mochi_module_init") { + let init: DiskInitFn = unsafe { core::mem::transmute(addr) }; + let ops = unsafe { init() }; + if disk::register(ops, meta.module_version) { + crate::info!("kmod: loaded disk.cext v{}", meta.module_version); + } else { + crate::warn!("kmod: disk init returned null ops"); + } + } else { + crate::warn!("kmod: mochi_module_init not found in disk.cext"); + } + } + "fs" => { + if let Some(addr) = load_elf_symbol(&meta.elf, "mochi_module_init") { + let init: FsInitFn = unsafe { core::mem::transmute(addr) }; + let ops = unsafe { init() }; + if fs::register(ops, meta.module_version) { + crate::info!("kmod: loaded fs.cext v{}", meta.module_version); + } else { + crate::warn!("kmod: fs init returned null ops"); + } + } else { + crate::warn!("kmod: mochi_module_init not found in fs.cext"); + } + } + other => { + crate::warn!("kmod: unknown module {}", other); + } + } + } +} + +fn parse_cext(bytes: &[u8]) -> Option { + let header = parse_header(bytes)?; + if header.header_size > bytes.len() { + return None; + } + + let mut cursor = CEXT_FIXED_HEADER_SIZE; + let name_end = cursor.checked_add(header.name_len)?; + let name = core::str::from_utf8(bytes.get(cursor..name_end)?).ok()?; + cursor = name_end; + + let mut deps = Vec::with_capacity(header.dep_count); + for _ in 0..header.dep_count { + let dep_len = read_u16(bytes, cursor)? as usize; + cursor = cursor.checked_add(2)?; + let dep_end = cursor.checked_add(dep_len)?; + let dep = core::str::from_utf8(bytes.get(cursor..dep_end)?).ok()?; + deps.push(dep.to_string()); + cursor = dep_end; + } + if cursor != header.header_size { + return None; + } + let elf_start = header.header_size; + let elf_end = elf_start.checked_add(header.elf_size)?; + if elf_end > bytes.len() { + return None; + } + + Some(CextMeta { + name: name.to_string(), + deps, + module_version: header.module_version, + elf: bytes[elf_start..elf_end].to_vec(), + }) +} + +fn parse_header(bytes: &[u8]) -> Option { + if bytes.len() < CEXT_FIXED_HEADER_SIZE { + return None; + } + if bytes.get(0..4)? != CEXT_MAGIC { + return None; + } + let abi = read_u16(bytes, 4)?; + if abi != 1 { + return None; + } + let module_version = read_u16(bytes, 6)?; + let name_len = read_u16(bytes, 8)? as usize; + let dep_count = read_u16(bytes, 10)? as usize; + let header_size = read_u32(bytes, 12)? as usize; + let elf_size = read_u64(bytes, 16)? as usize; + if header_size < CEXT_FIXED_HEADER_SIZE { + return None; + } + + Some(CextHeader { + module_version, + name_len, + dep_count, + header_size, + elf_size, + }) +} + +#[inline] +fn read_u16(bytes: &[u8], offset: usize) -> Option { + let raw = bytes.get(offset..offset + 2)?; + Some(u16::from_le_bytes([raw[0], raw[1]])) +} + +#[inline] +fn read_u32(bytes: &[u8], offset: usize) -> Option { + let raw = bytes.get(offset..offset + 4)?; + Some(u32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]])) +} + +#[inline] +fn read_u64(bytes: &[u8], offset: usize) -> Option { + let raw = bytes.get(offset..offset + 8)?; + Some(u64::from_le_bytes([ + raw[0], raw[1], raw[2], raw[3], raw[4], raw[5], raw[6], raw[7], + ])) +} + +fn load_elf_symbol(elf: &[u8], symbol_name: &str) -> Option { + let eh = crate::elf::loader::parse_elf_header(elf)?; + let loaded = load_elf_image(elf, &eh)?; + apply_relocations(elf, &eh, loaded.base, loaded.min_vaddr, loaded.max_vaddr)?; + find_symbol_runtime_addr(elf, &eh, symbol_name, loaded.base, loaded.min_vaddr) +} + +struct LoadedElf { + base: u64, + min_vaddr: u64, + max_vaddr: u64, +} + +#[inline] +fn align_up_4k(v: u64) -> Option { + v.checked_add(0xfff).map(|x| x & !0xfff) +} + +fn alloc_module_base(span: u64) -> Option { + let size = align_up_4k(span)?; + let step = size.checked_add(MODULE_LOAD_GUARD)?; + let mut cur = NEXT_MODULE_LOAD_BASE.load(Ordering::Relaxed); + loop { + let next = cur.checked_add(step)?; + match NEXT_MODULE_LOAD_BASE.compare_exchange(cur, next, Ordering::AcqRel, Ordering::Relaxed) + { + Ok(_) => return Some(cur), + Err(actual) => cur = actual, + } + } +} + +fn load_elf_image(elf: &[u8], eh: &crate::elf::loader::Elf64Ehdr) -> Option { + let phoff = eh.e_phoff as usize; + let phentsize = eh.e_phentsize as usize; + let phnum = eh.e_phnum as usize; + if phoff == 0 || phentsize == 0 || phnum == 0 { + return None; + } + + let mut min_vaddr = u64::MAX; + let mut max_vaddr = 0u64; + + for i in 0..phnum { + let off = phoff.checked_add(i.checked_mul(phentsize)?)?; + let ph = crate::elf::loader::parse_phdr(elf, off)?; + if ph.p_type != PT_LOAD || ph.p_memsz == 0 { + continue; + } + min_vaddr = min_vaddr.min(ph.p_vaddr); + max_vaddr = max_vaddr.max(ph.p_vaddr.checked_add(ph.p_memsz)?); + } + if min_vaddr == u64::MAX || max_vaddr <= min_vaddr { + return None; + } + let is_dyn = eh.e_type == ET_DYN; + let base = if is_dyn { + alloc_module_base(max_vaddr.checked_sub(min_vaddr)?)? + } else { + 0 + }; + let vaddr_bias = if is_dyn { + base.checked_sub(min_vaddr)? + } else { + 0 + }; + + for i in 0..phnum { + let off = phoff.checked_add(i.checked_mul(phentsize)?)?; + let ph = crate::elf::loader::parse_phdr(elf, off)?; + if ph.p_type != PT_LOAD || ph.p_memsz == 0 { + continue; + } + let writable = (ph.p_flags & 0x2) != 0; + let executable = (ph.p_flags & 0x1) != 0; + let filesz = usize::try_from(ph.p_filesz).ok()?; + let memsz = usize::try_from(ph.p_memsz).ok()?; + if filesz > memsz { + return None; + } + let src_off = usize::try_from(ph.p_offset).ok()?; + let src_end = src_off.checked_add(filesz)?; + if src_end > elf.len() { + return None; + } + let src = &elf[src_off..src_end]; + let seg_vaddr = ph.p_vaddr.checked_add(vaddr_bias)?; + crate::mem::paging::map_and_copy_segment( + seg_vaddr, + ph.p_filesz, + ph.p_memsz, + src, + writable, + executable, + ) + .ok()?; + } + + Some(LoadedElf { + // ET_DYN は再配置先ベース、ET_EXEC はリンクアドレス固定運用。 + base, + min_vaddr, + max_vaddr, + }) +} + +fn apply_relocations( + elf: &[u8], + eh: &crate::elf::loader::Elf64Ehdr, + base: u64, + min_vaddr: u64, + max_vaddr: u64, +) -> Option<()> { + let shoff = eh.e_shoff as usize; + let shentsz = eh.e_shentsize as usize; + let shnum = eh.e_shnum as usize; + if shoff == 0 || shentsz == 0 || shnum == 0 { + return Some(()); + } + if shoff.checked_add(shentsz.checked_mul(shnum)?)? > elf.len() { + return None; + } + + for i in 0..shnum { + let sh_off = shoff + i * shentsz; + let sh_type = read_u32(elf, sh_off + 4)?; + let sh_flags = read_u64(elf, sh_off + 8)?; + if sh_type != SHT_RELA || (sh_flags & SHF_ALLOC) == 0 { + continue; + } + let rela_off = usize::try_from(read_u64(elf, sh_off + 24)?).ok()?; + let rela_size = usize::try_from(read_u64(elf, sh_off + 32)?).ok()?; + let rela_entsize = usize::try_from(read_u64(elf, sh_off + 56)?).ok()?; + if rela_entsize == 0 || rela_entsize < 24 || rela_size % rela_entsize != 0 { + return None; + } + let rela_end = rela_off.checked_add(rela_size)?; + if rela_end > elf.len() { + return None; + } + let count = rela_size / rela_entsize; + for r in 0..count { + let ent = rela_off + r * rela_entsize; + let r_offset = read_u64(elf, ent)?; + let r_info = read_u64(elf, ent + 8)?; + let r_addend = i64::from_le_bytes(elf.get(ent + 16..ent + 24)?.try_into().ok()?); + let r_type = (r_info & 0xffff_ffff) as u32; + if r_type != R_X86_64_RELATIVE { + continue; + } + let reloc_vaddr = r_offset; + let reloc_end = reloc_vaddr.checked_add(8)?; + if reloc_vaddr < min_vaddr || reloc_end > max_vaddr { + return None; + } + let dst = base.checked_add(reloc_vaddr.checked_sub(min_vaddr)?)? as *mut u64; + let value_i128 = base as i128 + r_addend as i128 - min_vaddr as i128; + if value_i128 < 0 || value_i128 > u64::MAX as i128 { + return None; + } + let value = value_i128 as u64; + unsafe { + core::ptr::write_unaligned(dst, value); + } + } + } + + Some(()) +} + +fn find_symbol_runtime_addr( + elf: &[u8], + eh: &crate::elf::loader::Elf64Ehdr, + symbol_name: &str, + base: u64, + min_vaddr: u64, +) -> Option { + let shoff = eh.e_shoff as usize; + let shentsz = eh.e_shentsize as usize; + let shnum = eh.e_shnum as usize; + if shoff == 0 || shentsz == 0 || shnum == 0 { + return None; + } + + for si in 0..shnum { + let sh_off = shoff + si * shentsz; + let sh_type = read_u32(elf, sh_off + 4)?; + if sh_type != SHT_SYMTAB && sh_type != SHT_DYNSYM { + continue; + } + let symtab_offset = usize::try_from(read_u64(elf, sh_off + 24)?).ok()?; + let symtab_size = usize::try_from(read_u64(elf, sh_off + 32)?).ok()?; + let sh_link = read_u32(elf, sh_off + 40)? as usize; + let symtab_entsize = usize::try_from(read_u64(elf, sh_off + 56)?).ok()?; + if symtab_entsize < 24 || symtab_size == 0 { + continue; + } + if sh_link >= shnum { + continue; + } + let link_sh_off = shoff + sh_link * shentsz; + let strtab_offset = usize::try_from(read_u64(elf, link_sh_off + 24)?).ok()?; + let strtab_size = usize::try_from(read_u64(elf, link_sh_off + 32)?).ok()?; + let nsyms = symtab_size / symtab_entsize; + for i_sym in 0..nsyms { + let sym_off = symtab_offset + i_sym * symtab_entsize; + let st_name = read_u32(elf, sym_off)? as usize; + let st_value = read_u64(elf, sym_off + 8)?; + if st_name >= strtab_size { + continue; + } + let name_off = strtab_offset + st_name; + if name_off >= elf.len() { + continue; + } + let mut end = name_off; + while end < elf.len() && elf[end] != 0 { + end += 1; + } + let Ok(name_str) = core::str::from_utf8(&elf[name_off..end]) else { + continue; + }; + if name_str == symbol_name { + if base == 0 { + return Some(st_value); + } + return base.checked_add(st_value.checked_sub(min_vaddr)?); + } + } + } + None +} diff --git a/src/lib.rs b/src/core/lib.rs similarity index 52% rename from src/lib.rs rename to src/core/lib.rs index feaf1c0..c15d001 100644 --- a/src/lib.rs +++ b/src/core/lib.rs @@ -1,33 +1,75 @@ #![no_std] #![feature(abi_x86_interrupt)] +#![feature(alloc_error_handler)] #![allow(unused)] #![deny(clippy::unwrap_used)] #![deny(clippy::expect_used)] +#![deny(clippy::panic)] + +#[cfg(feature = "kcfi")] +compile_error!( + "feature `kcfi` is intentionally gated off: the current mochiOS build does not have a \ + verified Rust/LLVM KCFI pipeline for this freestanding x86_64 kernel. Leaving it \ + selectable without end-to-end verification would be unsound." +); + +#[cfg(feature = "cet-ibt")] +compile_error!( + "feature `cet-ibt` is intentionally gated off: hand-written syscall/interrupt/trampoline \ + assembly has not yet been fully annotated and inspected for ENDBR64 compliance." +); + +#[cfg(feature = "cet-shadow-stack")] +compile_error!( + "feature `cet-shadow-stack` is intentionally gated off: kernel shadow-stack allocation, \ + context-switch save/restore, and signal integration are not yet complete." +); + +extern crate alloc; /// エラー型定義 -pub mod error; +pub mod result; + +/// 監査ログ +pub mod audit; /// 割込み管理 pub mod interrupt; /// カーネル本体 pub mod kernel; +pub mod kmod; /// メモリ管理、GDT、TSSを含む pub mod mem; +/// ELF周り +pub mod elf; + /// パニックハンドラ pub mod panic; /// タスク管理 pub mod task; +/// システムコール +pub mod syscall; + +/// 起動時初期化 +pub mod init; + /// ユーティリティモジュール pub mod util; -pub use error::{KernelError, Result}; +/// CPU機能の初期化 +pub mod cpu; +/// per-CPU状態管理 +pub mod percpu; + pub use kernel::kernel_entry; +pub use result::{Kernel, Result}; +/// デバイス情報 #[repr(C)] pub struct BootInfo { /// 物理メモリオフセット @@ -48,6 +90,16 @@ pub struct BootInfo { pub memory_map_len: usize, /// メモリマップの各エントリサイズ pub memory_map_entry_size: usize, + /// カーネルアロケータの制御構造体へのアドレス(kernel binaryが起動時に設定) + pub kernel_heap_addr: u64, + /// initfs イメージの物理アドレス(ブートローダーが設定) + pub initfs_addr: u64, + /// initfs イメージのサイズ(バイト) + pub initfs_size: usize, + /// rootfs (ext2) イメージの物理アドレス(通常は0。必要なら別経路で設定) + pub rootfs_addr: u64, + /// rootfs イメージのサイズ(バイト。通常は0) + pub rootfs_size: usize, } /// メモリ領域の種類 diff --git a/src/core/mem/allocator.rs b/src/core/mem/allocator.rs new file mode 100644 index 0000000..d3f5fab --- /dev/null +++ b/src/core/mem/allocator.rs @@ -0,0 +1,78 @@ +use linked_list_allocator::LockedHeap; +use x86_64::{ + structures::paging::{ + mapper::MapToError, FrameAllocator, Mapper, Page, PageTableFlags, Size4KiB, + }, + VirtAddr, +}; + +/// 仮想アドレス空間のどこからヒープを開始するか +pub const HEAP_START: usize = 0x_4444_4444_0000; +/// ヒープのサイズ +pub const HEAP_SIZE: usize = 32 * 1024 * 1024; // 32 MiB + +/// ヒープを初期化 +/// +/// ## Arguments +/// - `mapper`: 仮想アドレスと物理アドレスのマッピングを管理するオブジェクト +/// - `frame_allocator`: 物理フレームの割り当てを管理するオブジェクト +/// - `heap_allocator_ptr`: ヒープアロケータのロックされたヒープへのポインタ +/// +/// ## Returns +/// - `Ok(())` ヒープの初期化に成功した場合 +/// - `Err(MapToError)` マッピングのエラーが発生した場合 +pub fn init_heap( + mapper: &mut impl Mapper, + frame_allocator: &mut impl FrameAllocator, + heap_allocator_ptr: u64, +) -> Result<(), MapToError> { + let page_range = { + let heap_start = VirtAddr::new(HEAP_START as u64); + let heap_end = heap_start + HEAP_SIZE as u64 - 1u64; + let heap_start_page = Page::containing_address(heap_start); + let heap_end_page = Page::containing_address(heap_end); + Page::range_inclusive(heap_start_page, heap_end_page) + }; + + // ヒープの仮想アドレス空間を物理フレームにマッピング + for page in page_range { + let frame = frame_allocator + .allocate_frame() + .ok_or(MapToError::FrameAllocationFailed)?; + // カーネルヒープは実行不可(W^X: NO_EXECUTE でコード実行を防ぐ) + let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE | PageTableFlags::NO_EXECUTE; + unsafe { + mapper.map_to(page, frame, flags, frame_allocator)?.flush(); + } + } + + // ヒープアロケータを初期化 + unsafe { + let allocator = &mut *(heap_allocator_ptr as *mut LockedHeap); + allocator.lock().init(HEAP_START as *mut u8, HEAP_SIZE); + } + + Ok(()) +} + +#[alloc_error_handler] +fn alloc_error_handler(layout: alloc::alloc::Layout) -> ! { + crate::warn!("allocation error: {:?}", layout); + // スケジューラが動作中でカレントスレッドがあれば、そのプロセスを終了して回復を試みる + if crate::task::scheduler::is_scheduler_enabled() && crate::task::current_thread_id().is_some() + { + crate::warn!("OOM: terminating current process to recover"); + crate::task::scheduler::exit_current_process(-1); + } + // 回復不能: 割り込みを無効化してシステムを停止 + #[cfg(target_arch = "x86_64")] + unsafe { + x86_64::instructions::interrupts::disable(); + } + loop { + #[cfg(target_arch = "x86_64")] + unsafe { + core::arch::asm!("hlt"); + } + } +} diff --git a/src/core/mem/frame.rs b/src/core/mem/frame.rs new file mode 100644 index 0000000..cd1d9bd --- /dev/null +++ b/src/core/mem/frame.rs @@ -0,0 +1,286 @@ +//! 物理フレームアロケータ +//! +//! 4KBページ単位で物理メモリを管理 + +use crate::{ + result::{Kernel, Memory, Result}, + MemoryRegion, MemoryType, +}; +use spin::Mutex; +use x86_64::{ + structures::paging::{FrameAllocator, PhysFrame, Size4KiB}, + PhysAddr, +}; + +/// グローバルフレームアロケータ +pub static FRAME_ALLOCATOR: Mutex> = Mutex::new(None); + +/// ビットマップベースのフレームアロケータ +/// +/// 解放済みフレームはフレーム自身の先頭8バイトにリンクリストのnextポインタを +/// 埋め込むことで上限なしに再利用できる。 +pub struct BitmapFrameAllocator { + /// メモリマップ + memory_map: &'static [MemoryRegion], + /// バンプアロケータの次フレームインデックス + next_frame: usize, + /// 解放済みフレームのフリーリスト先頭(物理アドレス、0 = 空) + free_list_head: u64, + /// HHDM オフセット(phys → virt 変換用) + phys_offset: u64, +} + +impl BitmapFrameAllocator { + /// 新しいフレームアロケータを作成 + pub fn new(memory_map: &'static [MemoryRegion], phys_offset: u64) -> Self { + Self { + memory_map, + next_frame: 0x100000 / 4096, // 1MB から開始(低位メモリ予約領域をスキップ) + free_list_head: 0, + phys_offset, + } + } + + fn is_usable_frame_addr(&self, phys_addr: u64) -> bool { + self.memory_map.iter().any(|r| { + r.region_type == MemoryType::Usable + && phys_addr >= r.start + && phys_addr < r.start + r.len + }) + } + + pub fn deallocate_frame(&mut self, frame: PhysFrame) -> bool { + let phys_addr = frame.start_address().as_u64(); + if phys_addr & 0xfff != 0 || !self.is_usable_frame_addr(phys_addr) { + return false; + } + if self.phys_offset == 0 { + return false; + } + // フレームの先頭8バイトに現在の free_list_head を書き込んでリストに繋ぐ + let virt_ptr = (phys_addr + self.phys_offset) as *mut u64; + unsafe { *virt_ptr = self.free_list_head }; + self.free_list_head = phys_addr; + true + } + + /// 使用可能な物理メモリの総量を計算(バイト) + pub fn usable_memory(&self) -> u64 { + self.memory_map + .iter() + .filter(|r| r.region_type == MemoryType::Usable) + .map(|r| r.len) + .sum() + } + + /// 使用可能なフレーム数を計算 + pub fn usable_frames(&self) -> usize { + (self.usable_memory() / 4096) as usize + } + + fn usable_frames_iter(&self) -> impl Iterator + '_ { + self.memory_map + .iter() + .filter(|r| r.region_type == MemoryType::Usable) + .flat_map(|r| { + let start_addr = r.start; + let end_addr = r.start + r.len; + let start_frame = start_addr / 4096; + let end_frame = end_addr / 4096; + (start_frame..end_frame) + .map(|f| PhysFrame::containing_address(PhysAddr::new(f * 4096))) + }) + } +} + +unsafe impl FrameAllocator for BitmapFrameAllocator { + fn allocate_frame(&mut self) -> Option { + // フリーリストから再利用 + if self.free_list_head != 0 && self.phys_offset != 0 { + let phys = self.free_list_head; + let virt_ptr = (phys + self.phys_offset) as *mut u64; + self.free_list_head = unsafe { *virt_ptr }; + unsafe { *virt_ptr = 0 }; // nextp をゼロクリア + return Some(PhysFrame::containing_address(PhysAddr::new(phys))); + } + + // バンプアロケータから新規割り当て + let mut f = self.next_frame as u64; + let max_frame = self + .memory_map + .iter() + .map(|r| (r.start + r.len) / 4096) + .max() + .unwrap_or(0); + + while f <= max_frame { + let phys_addr = f * 4096; + let mut usable = false; + + for r in self.memory_map.iter() { + if r.region_type != MemoryType::Usable { + continue; + } + if phys_addr >= r.start && phys_addr < r.start + r.len { + usable = true; + break; + } + } + + if usable { + self.next_frame = (f + 1) as usize; + return Some(PhysFrame::containing_address(PhysAddr::new(phys_addr))); + } + f += 1; + } + None + } +} + +/// フレームアロケータを初期化 +pub fn init(memory_map: &'static [MemoryRegion]) { + let allocator = BitmapFrameAllocator::new(memory_map, 0); + *FRAME_ALLOCATOR.lock() = Some(allocator); +} + +/// ページングが初期化された後に HHDM オフセットをセット +pub fn set_phys_offset(offset: u64) { + if let Some(alloc) = FRAME_ALLOCATOR.lock().as_mut() { + alloc.phys_offset = offset; + } +} + +/// フレームを割り当て +pub fn allocate_frame() -> Result { + FRAME_ALLOCATOR + .lock() + .as_mut() + .and_then(|a| a.allocate_frame()) + .ok_or(Kernel::Memory(Memory::OutOfMemory)) +} + +/// フレームを解放 +pub fn deallocate_frame(frame: PhysFrame) -> Result<()> { + let mut guard = FRAME_ALLOCATOR.lock(); + let allocator = guard.as_mut().ok_or(Kernel::Memory(Memory::OutOfMemory))?; + if allocator.deallocate_frame(frame) { + Ok(()) + } else { + Err(Kernel::Memory(Memory::InvalidAddress)) + } +} + +/// 使用可能なメモリ情報を取得 +pub fn get_memory_info() -> Option<(u64, usize)> { + FRAME_ALLOCATOR + .lock() + .as_ref() + .map(|a| (a.usable_memory(), a.usable_frames())) +} + +/// 指定した物理アドレスがアロケータ管理対象の Usable フレームか判定 +pub fn is_usable_physical_address(phys_addr: u64) -> bool { + let guard = FRAME_ALLOCATOR.lock(); + let Some(alloc) = guard.as_ref() else { + return false; + }; + alloc.is_usable_frame_addr(phys_addr) +} + +// ACPI reclaimable / bootloader reclaimable は将来通常RAMとして回収されうるため、 +// 既定では MMIO 許可対象から除外する(必要なら明示設定で有効化する)。 +const ALLOW_RECLAIMABLE_MMIO: bool = false; + +fn mmio_region_type_allowed(region_type: MemoryType) -> bool { + match region_type { + MemoryType::BadMemory => { + // 不良メモリへの MMIO マップは常に拒否する。 + false + } + MemoryType::Reserved | MemoryType::AcpiNvs | MemoryType::Framebuffer => true, + MemoryType::AcpiReclaimable | MemoryType::BootloaderReclaimable => ALLOW_RECLAIMABLE_MMIO, + _ => false, + } +} + +/// MMIO として扱ってよい物理アドレス範囲か判定 +/// +/// 許可条件: +/// - 範囲が非Usable領域 (Reserved/AcpiNvs/Framebuffer) に完全に含まれる、または +/// - 範囲が UEFI メモリマップのどの領域とも重ならない (= 高位 PCI MMIO ホール) +/// +/// 拒否条件: +/// - Usable RAM やカーネル所有領域と少しでも重なる +pub fn is_allowed_mmio_range(start_phys: u64, size: u64) -> bool { + if size == 0 { + return false; + } + let end_phys = match start_phys.checked_add(size - 1) { + Some(v) => v, + None => return false, + }; + + let guard = FRAME_ALLOCATOR.lock(); + let Some(alloc) = guard.as_ref() else { + return false; + }; + + let mut overlaps_any = false; + let mut fully_contained_in_allowed = false; + for r in alloc.memory_map.iter() { + if r.len == 0 { + continue; + } + let region_end = match r.start.checked_add(r.len - 1) { + Some(v) => v, + None => continue, + }; + let overlaps = start_phys <= region_end && end_phys >= r.start; + if !overlaps { + continue; + } + overlaps_any = true; + if !mmio_region_type_allowed(r.region_type) { + return false; + } + if start_phys >= r.start && end_phys <= region_end { + fully_contained_in_allowed = true; + } + } + + if fully_contained_in_allowed { + return true; + } + // メモリマップに記述のない領域 = PCI MMIO ホール (高位 BAR など) は許可 + !overlaps_any +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn mmio_region_type_policy_allowed_cases() { + for t in [ + MemoryType::Reserved, + MemoryType::AcpiNvs, + MemoryType::Framebuffer, + ] { + assert!(mmio_region_type_allowed(t)); + } + } + + #[test] + fn mmio_region_type_policy_denied_cases() { + for t in [ + MemoryType::Usable, + MemoryType::AcpiReclaimable, + MemoryType::BootloaderReclaimable, + MemoryType::BadMemory, + MemoryType::KernelStack, + MemoryType::PageTable, + ] { + assert!(!mmio_region_type_allowed(t)); + } + } +} diff --git a/src/core/mem/gdt.rs b/src/core/mem/gdt.rs new file mode 100644 index 0000000..5fde5a9 --- /dev/null +++ b/src/core/mem/gdt.rs @@ -0,0 +1,186 @@ +//! GDT管理モジュール +//! +//! Global Descriptor Tableを管理 + +use crate::info; +use crate::mem::tss; +use core::arch::asm; +use spin::Once; +use x86_64::instructions::tables::load_tss; +use x86_64::structures::gdt::{Descriptor, GlobalDescriptorTable, SegmentSelector}; + +/// ダブルフォルト用ISTインデックス(TSSと同じ値を使用) +pub const DOUBLE_FAULT_IST_INDEX: u16 = tss::DOUBLE_FAULT_IST_INDEX; + +static GDT: Once<(GlobalDescriptorTable, Selectors)> = Once::new(); + +fn halt_on_missing_gdt(which: &'static str) -> ! { + crate::audit::log(crate::audit::AuditEventKind::Fault, which); + crate::warn!("GDT not initialized"); + x86_64::instructions::interrupts::disable(); + loop { + x86_64::instructions::hlt(); + } +} + +/// GDTセレクタ +#[allow(unused)] +struct Selectors { + /// カーネルコードセグメントセレクタ + code_selector: SegmentSelector, + /// カーネルデータセグメントセレクタ + data_selector: SegmentSelector, + /// ユーザーモード用コードセグメントセレクタ + user_code_selector: SegmentSelector, + /// ユーザーモード用データセグメントセレクタ + user_data_selector: SegmentSelector, + /// TSSセレクタ + tss_selector: SegmentSelector, +} + +/// カーネルのコードセグメントセレクタを取得 (GDT初期化に内部使用) +pub fn init() { + info!("Initializing GDT..."); + crate::debug!("About to init TSS"); + + // TSSを初期化 + let tss = tss::init(); + crate::debug!("TSS initialized"); + + // GDTを初期化 + let (gdt, selectors) = GDT.call_once(|| { + crate::debug!("Creating GDT table"); + let mut gdt = GlobalDescriptorTable::new(); + let code_selector = gdt.append(Descriptor::kernel_code_segment()); + let data_selector = gdt.append(Descriptor::kernel_data_segment()); + let user_data_selector = gdt.append(Descriptor::user_data_segment()); + let user_code_selector = gdt.append(Descriptor::user_code_segment()); + let tss_selector = gdt.append(Descriptor::tss_segment(tss)); + + info!("GDT entries created:"); + info!(" Code selector: {:?}", code_selector); + info!(" Data selector: {:?}", data_selector); + info!(" User data selector: {:?}", user_data_selector); + info!(" User code selector: {:?}", user_code_selector); + info!(" TSS selector: {:?}", tss_selector); + + ( + gdt, + Selectors { + code_selector, + data_selector, + user_code_selector, + user_data_selector, + tss_selector, + }, + ) + }); + crate::debug!("GDT created"); + + unsafe { + // GDTをロード + crate::debug!("Loading GDT"); + gdt.load(); + crate::debug!("GDT loaded"); + + // Boot services終了後はカーネルのセグメントに切り替え + crate::debug!("Setting CS"); + set_cs(selectors.code_selector); + crate::debug!("CS set, setting data segments"); + set_data_segments(selectors.data_selector); + crate::debug!("Data segments set"); + + // TSSをロード + crate::debug!("Loading TSS"); + load_tss(selectors.tss_selector); + crate::debug!("TSS loaded"); + // Ensure user data descriptor has D/B cleared for long mode (avoid GPF on iretq) + // We modify the loaded GDT in-place: clear bit 54 (D/B) of the descriptor. + // ただし、SMAP有効環境ではこのメモリアクセスが違反になるため、スキップする。 + if !crate::cpu::is_smap_enabled() { + crate::debug!("Modifying GDT descriptor for user data segment"); + let mut gdtr: [u8; 10] = [0; 10]; + asm!("sgdt [{}]", in(reg) &mut gdtr, options(nostack)); + let base = u64::from_le_bytes([ + gdtr[2], gdtr[3], gdtr[4], gdtr[5], gdtr[6], gdtr[7], gdtr[8], gdtr[9], + ]); + let user_ds_index = selectors.user_data_selector.0 as usize >> 3; + let desc_ptr = (base + (user_ds_index * 8) as u64) as *mut u64; + let old = core::ptr::read_volatile(desc_ptr); + // clear D/B bit (bit 54) + let new = old & !(1u64 << 54); + core::ptr::write_volatile(desc_ptr, new); + crate::debug!("GDT descriptor modified"); + } else { + crate::debug!("Skipping GDT descriptor modification (SMAP enabled)"); + } + } + + info!("GDT loaded with TSS"); +} + +/// ユーザーモード用コードセレクタ(RPL=3)を返す +pub fn user_code_selector() -> u16 { + GDT.get() + .map(|g| g.1.user_code_selector.0) + .unwrap_or_else(|| halt_on_missing_gdt("gdt user_code_selector unavailable")) +} + +/// ユーザーモード用データセレクタ(RPL=3)を返す +pub fn user_data_selector() -> u16 { + GDT.get() + .map(|g| g.1.user_data_selector.0) + .unwrap_or_else(|| halt_on_missing_gdt("gdt user_data_selector unavailable")) +} + +/// カーネル用コードセレクタを返す +pub fn kernel_code_selector() -> u16 { + GDT.get() + .map(|g| g.1.code_selector.0) + .unwrap_or_else(|| halt_on_missing_gdt("gdt kernel_code_selector unavailable")) +} + +/// カーネル用データセレクタを返す +pub fn kernel_data_selector() -> u16 { + GDT.get() + .map(|g| g.1.data_selector.0) + .unwrap_or_else(|| halt_on_missing_gdt("gdt kernel_data_selector unavailable")) +} + +#[allow(unused)] +/// データセグメントレジスタを設定 +/// +/// ## Arguments +/// - `selector`: 設定するセグメントセレクタ +/// +/// ## Safety +/// - 呼び出し前にGDTが正しく初期化されている必要がある +unsafe fn set_data_segments(selector: SegmentSelector) { + asm!( + "mov ds, {0:x}", + "mov es, {0:x}", + "mov fs, {0:x}", + "mov gs, {0:x}", + "mov ss, {0:x}", + in(reg) selector.0, + options(nostack, preserves_flags) + ); +} + +#[allow(unused)] +/// コードセグメントを設定 +/// +/// ## Arguments +/// - `selector`: 設定するセグメントセレクタ +unsafe fn set_cs(selector: SegmentSelector) { + asm!( + "push {sel}", + "lea {tmp}, [rip + 2f]", + "push {tmp}", + "retfq", + "2:", + sel = in(reg) u64::from(selector.0), + tmp = lateout(reg) _, + options(preserves_flags) + ); +} diff --git a/src/core/mem/mod.rs b/src/core/mem/mod.rs new file mode 100644 index 0000000..2dc46d4 --- /dev/null +++ b/src/core/mem/mod.rs @@ -0,0 +1,135 @@ +//! メモリ管理モジュール +//! +//! GDT、TSS、ページング、フレームアロケータ + +use crate::{debug, info, interrupt, MemoryRegion, Result}; + +pub mod allocator; +pub mod frame; +pub mod gdt; +pub mod paging; +pub mod tss; +pub(crate) mod user; + +/// メモリの初期化 +/// +/// ## Arguments +/// - `boot_info`: ブートローダから渡される情報構造体 +pub fn init(boot_info: &'static crate::BootInfo) -> Result<()> { + info!("Initializing memory..."); + crate::debug!("About to disable interrupts"); + + x86_64::instructions::interrupts::disable(); + crate::debug!("Interrupts disabled, temporarily disabling SMAP"); + + let smap_was_enabled = crate::cpu::is_smap_enabled(); + if smap_was_enabled { + unsafe { + crate::cpu::disable_smap(); + } + crate::debug!("SMAP temporarily disabled"); + } + + gdt::init(); + crate::debug!("GDT initialized, initializing IDT"); + interrupt::init_idt(); + crate::debug!("IDT initialized, initializing paging"); + + paging::init(boot_info)?; + crate::debug!("Paging initialized, initializing PAGE_TABLE"); + + let smap_was_enabled_for_paging = crate::cpu::is_smap_enabled(); + let smep_was_enabled_for_paging = unsafe { + let cr4 = x86_64::registers::control::Cr4::read(); + cr4.contains(x86_64::registers::control::Cr4Flags::SUPERVISOR_MODE_EXECUTION_PROTECTION) + }; + + if smap_was_enabled_for_paging { + unsafe { + crate::cpu::disable_smap(); + } + crate::debug!("SMAP temporarily disabled for PAGE_TABLE init"); + } + if smep_was_enabled_for_paging { + unsafe { + let mut cr4 = x86_64::registers::control::Cr4::read(); + cr4.remove(x86_64::registers::control::Cr4Flags::SUPERVISOR_MODE_EXECUTION_PROTECTION); + x86_64::registers::control::Cr4::write(cr4); + } + crate::debug!("SMEP temporarily disabled for PAGE_TABLE init"); + } + + paging::init_page_table()?; + crate::debug!("PAGE_TABLE initialized"); + + // Keep SMAP/SMEP disabled during heap initialization + crate::info!("Keeping SMAP/SMEP disabled during kernel initialization"); + + let mut page_table_lock = paging::PAGE_TABLE.lock(); + let page_table = match page_table_lock.as_mut() { + Some(p) => p, + None => { + crate::warn!("PAGE_TABLE not initialized"); + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "memory init missing page table", + ); + return Err(crate::Kernel::Memory(crate::result::Memory::NotMapped)); + } + }; + let mut frame_alloc_lock = frame::FRAME_ALLOCATOR.lock(); + let frame_alloc = match frame_alloc_lock.as_mut() { + Some(fa) => fa, + None => { + crate::warn!("FRAME_ALLOCATOR not initialized"); + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "memory init missing frame allocator", + ); + return Err(crate::Kernel::Memory(crate::result::Memory::OutOfMemory)); + } + }; + crate::debug!("Locks acquired, initializing heap"); + if let Err(e) = allocator::init_heap( + &mut *page_table, + &mut *frame_alloc, + boot_info.kernel_heap_addr, + ) { + crate::warn!("Heap initialization failed: {:?}", e); + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "memory init heap initialization failed", + ); + return Err(crate::Kernel::Memory(crate::result::Memory::InvalidAddress)); + } + + crate::debug!("Heap initialized, disabling PIT"); + // PITを停止してからPICを初期化 + interrupt::disable_pit(); + crate::debug!("PIT disabled, initializing PIC"); + interrupt::init_pic(); + + debug!("Memory initialized"); + Ok(()) +} + +/// メモリマップを設定してフレームアロケータを初期化 +/// +/// ## Arguments +/// - `memory_map`: ブートローダから渡されるメモリマップ +/// +/// ## Returns +/// - `Result<()>`: 成功すればOk、失敗すればErr +pub fn init_frame_allocator(memory_map: &'static [MemoryRegion]) -> Result<()> { + frame::init(memory_map); + + if let Some((total, frames)) = frame::get_memory_info() { + debug!( + "Physical memory: {} MB ({} frames)", + total / 1024 / 1024, + frames + ); + } + + Ok(()) +} diff --git a/src/core/mem/paging.rs b/src/core/mem/paging.rs new file mode 100644 index 0000000..2b7b359 --- /dev/null +++ b/src/core/mem/paging.rs @@ -0,0 +1,1990 @@ +//! ページング管理モジュール +//! +//! 仮想メモリとページテーブル管理 + +use crate::info; +use crate::mem::frame; +use crate::result::{Kernel, Memory, Result}; +use spin::Mutex; + +use x86_64::registers::control::{Cr3, Cr3Flags}; +use x86_64::{ + registers::control::{Cr0, Cr0Flags}, + structures::paging::{ + FrameAllocator, Mapper, OffsetPageTable, Page, PageTable, PageTableFlags, PhysFrame, + Size4KiB, + }, + VirtAddr, +}; + +/// アクティブなページテーブルへのグローバル参照と物理メモリオフセット +pub static PAGE_TABLE: Mutex>> = Mutex::new(None); +/// 物理メモリオフセット(init時に設定) - 仮想アドレス = 物理アドレス + オフセット +pub static PHYS_OFFSET: Mutex> = Mutex::new(None); +/// カーネルの元のL4ページテーブルの物理アドレス(init時に設定) +pub static KERNEL_L4_PHYS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0); +/// x86-64 canonical ユーザー空間上限 +const USER_SPACE_END: u64 = 0x0000_7FFF_FFFF_FFFF; + +#[cfg(target_os = "uefi")] +#[used] +#[unsafe(no_mangle)] +#[unsafe(link_section = ".text$A")] +static __MOCHIOS_TEXT_START_MARKER: u8 = 0; +#[cfg(target_os = "uefi")] +#[used] +#[unsafe(no_mangle)] +#[unsafe(link_section = ".text$Z")] +static __MOCHIOS_TEXT_END_MARKER: u8 = 0; + +#[cfg(not(target_os = "uefi"))] +unsafe extern "C" { + static __text_start: u8; + static __text_end: u8; +} + +#[cfg(target_os = "uefi")] +fn kernel_text_range() -> (u64, u64) { + ( + core::ptr::addr_of!(__MOCHIOS_TEXT_START_MARKER) as u64, + core::ptr::addr_of!(__MOCHIOS_TEXT_END_MARKER) as u64, + ) +} + +#[cfg(not(target_os = "uefi"))] +fn kernel_text_range() -> (u64, u64) { + unsafe { + ( + core::ptr::addr_of!(__text_start) as u64, + core::ptr::addr_of!(__text_end) as u64, + ) + } +} + +fn protect_kernel_text_pages(page_table: &mut OffsetPageTable<'static>) { + let (text_start, text_end) = kernel_text_range(); + if text_end <= text_start { + crate::warn!( + "Invalid .text range: start={:#x}, end={:#x}", + text_start, + text_end + ); + return; + } + + let start_page = Page::::containing_address(VirtAddr::new(text_start)); + let end_page = Page::::containing_address(VirtAddr::new(text_end - 1)); + + for page in Page::::range_inclusive(start_page, end_page) { + unsafe { + let _ = page_table + .update_flags(page, PageTableFlags::PRESENT) + .map(|flush| flush.flush()); + } + } +} + +/// ページングシステムを初期化 +/// +/// ## Arguments +/// - `boot_info`: ブートローダーから提供される情報(メモリマップ、物理メモリオフセットなど) +pub fn init(boot_info: &'static crate::BootInfo) -> Result<()> { + info!("Initializing paging..."); + + let physical_memory_offset = boot_info.physical_memory_offset; + + // 現在のページテーブル情報を記録 + let (old_l4_frame, _) = x86_64::registers::control::Cr3::read(); + let old_l4_phys = old_l4_frame.start_address().as_u64(); + + crate::info!("Current L4 table phys: {:#x}", old_l4_phys); + + // グローバル状態を設定 + *PHYS_OFFSET.lock() = Some(physical_memory_offset); + KERNEL_L4_PHYS.store(old_l4_phys, core::sync::atomic::Ordering::Relaxed); + + // フレームアロケータに HHDM オフセットを伝えてフリーリストを有効化 + super::frame::set_phys_offset(physical_memory_offset); + + crate::info!("Paging initialized (deferring PAGE_TABLE setup)."); + + Ok(()) +} + +/// ページテーブルを遅延初期化する +pub fn init_page_table() -> Result<()> { + crate::info!("Initializing PAGE_TABLE..."); + + let phys_offset = *PHYS_OFFSET.lock(); + let phys_offset = match phys_offset { + Some(off) => off, + None => { + crate::warn!("PHYS_OFFSET not set"); + return Err(Kernel::Memory(Memory::NotMapped)); + } + }; + + let (old_l4_frame, _) = x86_64::registers::control::Cr3::read(); + let old_l4_phys = old_l4_frame.start_address().as_u64(); + + crate::info!("Bootloader L4 table at phys {:#x}", old_l4_phys); + + // 新しい L4 テーブルをメモリに割り当てる + let new_l4_frame = { + let mut allocator_guard = frame::FRAME_ALLOCATOR.lock(); + if let Some(alloc) = allocator_guard.as_mut() { + alloc.allocate_frame() + } else { + None + } + } + .ok_or(Kernel::Memory(Memory::OutOfMemory))?; + + let new_l4_phys = new_l4_frame.start_address().as_u64(); + + crate::info!("Allocated new L4 table at phys {:#x}", new_l4_phys); + + // ブートローダーの L4 テーブルから新しい L4 テーブルにコピー(読み込みは可能) + unsafe { + let old_l4_virt = old_l4_phys + phys_offset; + let new_l4_virt = new_l4_phys + phys_offset; + + crate::debug!( + "Copying L4 table from virt {:#x} to {:#x}", + old_l4_virt, + new_l4_virt + ); + + // ページテーブルのサイズは 4KB (512 entries * 8 bytes) + core::ptr::copy_nonoverlapping(old_l4_virt as *const u8, new_l4_virt as *mut u8, 4096); + + crate::info!("L4 table copied successfully"); + } + + // 新しい L4 テーブルにアクティブに切り替える + unsafe { + let new_l4_flags = Cr3Flags::empty(); + x86_64::registers::control::Cr3::write(new_l4_frame, new_l4_flags); + crate::info!("CR3 switched to new L4 table at phys {:#x}", new_l4_phys); + } + + // ブートローダーの L4 テーブルを記録 + KERNEL_L4_PHYS.store(new_l4_phys, core::sync::atomic::Ordering::Release); + + // 新しい L4 テーブルでページテーブルを作成 + let page_table = unsafe { + let new_l4_virt = new_l4_phys + phys_offset; + let l4_table = &mut *(new_l4_virt as *mut PageTable); + OffsetPageTable::new(l4_table, VirtAddr::new(phys_offset)) + }; + + // PAGE_TABLE を設定 + *PAGE_TABLE.lock() = Some(page_table); + + crate::info!("PAGE_TABLE initialized with new L4 table successfully."); + + Ok(()) +} + +/// アクティブなレベル4ページテーブルへの参照を取得 +/// +/// ## Arguments +/// - `physical_memory_offset`: カーネルが使用する物理メモリオフセット(仮想アドレス = 物理アドレス + オフセット) +/// +/// ## Returns +/// アクティブなレベル4ページテーブルへのミュータブル参照 +unsafe fn active_level_4_table(physical_memory_offset: u64) -> &'static mut PageTable { + use x86_64::registers::control::Cr3; + + let (level_4_table_frame, _) = Cr3::read(); + let phys = level_4_table_frame.start_address(); + let virt = VirtAddr::new(phys.as_u64() + physical_memory_offset); + let page_table_ptr: *mut PageTable = virt.as_mut_ptr(); + + &mut *page_table_ptr +} + +/// ページをマップ +/// +/// ## Arguments +/// - `page`: マップする仮想ページ +/// - `frame`: マップ先の物理フレーム +/// - `flags`: ページテーブルエントリのフラグ(例: PRESENT, WRITABLE, USER_ACCESSIBLEなど) +/// +/// ## Returns +/// 成功した場合は `Ok(())`、失敗した場合はエラーを返す +pub fn map_page(page: Page, frame: PhysFrame, flags: PageTableFlags) -> Result<()> { + let mut page_table_lock = PAGE_TABLE.lock(); + let page_table = page_table_lock + .as_mut() + .ok_or(Kernel::Memory(Memory::NotMapped))?; + + let mut allocator_lock = frame::FRAME_ALLOCATOR.lock(); + let allocator = allocator_lock + .as_mut() + .ok_or(Kernel::Memory(Memory::OutOfMemory))?; + + // すでにマップされている場合はアンマップする (Identity Mappingとの競合回避) + use x86_64::structures::paging::mapper::Translate; + if page_table.translate_page(page).is_ok() { + if let Ok((_, flush)) = page_table.unmap(page) { + flush.flush(); + } + } + + unsafe { + page_table + .map_to(page, frame, flags, allocator) + .map_err(|_| Kernel::Memory(Memory::InvalidAddress))? + .flush(); + } + + Ok(()) +} + +/// 仮想アドレスを物理アドレスに変換 +/// +/// ## Arguments +/// - `addr`: 変換する仮想アドレス +/// +/// ## Returns +/// 変換された物理アドレス、または変換できない場合は `None` +pub fn translate_addr(addr: VirtAddr) -> Option { + use x86_64::structures::paging::mapper::Translate; + + let page_table = PAGE_TABLE.lock(); + page_table.as_ref()?.translate_addr(addr) +} + +/// 指定したページテーブル上で仮想アドレスを物理アドレスへ変換する +pub fn translate_addr_in_table( + table_phys: u64, + addr: VirtAddr, +) -> Option<(PhysAddr, PageTableFlags)> { + // Ensure SMAP/SMEP disabled while dereferencing HHDM pointers + let _smap_guard = crate::cpu::SmapSmepGuard::new(); + + let phys_off = physical_memory_offset()?; + if (table_phys & 0xfff) != 0 { + return None; + } + + let l4_vaddr = table_phys.checked_add(phys_off)?; + let l4 = unsafe { &*(l4_vaddr as *const PageTable) }; + let l4i = addr.p4_index(); + let l4e = &l4[l4i]; + if l4e.is_unused() || !l4e.flags().contains(PageTableFlags::PRESENT) { + return None; + } + + let l3_vaddr = l4e.addr().as_u64().checked_add(phys_off)?; + let l3 = unsafe { &*(l3_vaddr as *const PageTable) }; + let l3i = addr.p3_index(); + let l3e = &l3[l3i]; + let l3f = l3e.flags(); + if l3e.is_unused() || !l3f.contains(PageTableFlags::PRESENT) { + return None; + } + if l3f.contains(PageTableFlags::HUGE_PAGE) { + let page_off = addr.as_u64() & ((1u64 << 30) - 1); + return Some(( + PhysAddr::new(l3e.addr().as_u64().checked_add(page_off)?), + l3f, + )); + } + + let l2_vaddr = l3e.addr().as_u64().checked_add(phys_off)?; + let l2 = unsafe { &*(l2_vaddr as *const PageTable) }; + let l2i = addr.p2_index(); + let l2e = &l2[l2i]; + let l2f = l2e.flags(); + if l2e.is_unused() || !l2f.contains(PageTableFlags::PRESENT) { + return None; + } + if l2f.contains(PageTableFlags::HUGE_PAGE) { + let page_off = addr.as_u64() & ((1u64 << 21) - 1); + return Some(( + PhysAddr::new(l2e.addr().as_u64().checked_add(page_off)?), + l2f, + )); + } + + let l1_vaddr = l2e.addr().as_u64().checked_add(phys_off)?; + let l1 = unsafe { &*(l1_vaddr as *const PageTable) }; + let l1i = addr.p1_index(); + let l1e = &l1[l1i]; + let l1f = l1e.flags(); + if l1e.is_unused() || !l1f.contains(PageTableFlags::PRESENT) { + return None; + } + + let page_off = addr.as_u64() & 0xfff; + Some(( + PhysAddr::new(l1e.addr().as_u64().checked_add(page_off)?), + l1f, + )) +} + +/// 指定したページテーブル上の仮想アドレスからu64値を読み出す +pub fn read_u64_in_table(table_phys: u64, vaddr: u64) -> Option { + // Ensure SMAP/SMEP disabled while dereferencing HHDM pointer + let _smap_guard = crate::cpu::SmapSmepGuard::new(); + + let phys_off = physical_memory_offset()?; + let (phys, _) = translate_addr_in_table(table_phys, VirtAddr::new(vaddr))?; + let ptr = phys.as_u64().checked_add(phys_off)? as *const u64; + Some(unsafe { core::ptr::read_unaligned(ptr) }) +} + +/// 物理メモリオフセットを取得 +/// +/// ## Returns +/// カーネルが使用する物理メモリオフセット(仮想アドレス = 物理アドレス + オフセット) +pub fn physical_memory_offset() -> Option { + *PHYS_OFFSET.lock() +} + +fn user_page_flags_in_table(table_phys: u64, page_addr: u64) -> Option { + // Ensure SMAP/SMEP disabled while dereferencing HHDM pointers + let _smap_guard = crate::cpu::SmapSmepGuard::new(); + + let phys_off = physical_memory_offset()?; + if (table_phys & 0xfff) != 0 { + return None; + } + + let l4_vaddr = table_phys.checked_add(phys_off)?; + let l4 = unsafe { &*(l4_vaddr as *const PageTable) }; + let l4i = ((page_addr >> 39) & 0x1ff) as usize; + if l4i >= 256 { + return None; + } + let l4e = &l4[l4i]; + let l4f = l4e.flags(); + if l4e.is_unused() + || !l4f.contains(PageTableFlags::PRESENT) + || !l4f.contains(PageTableFlags::USER_ACCESSIBLE) + { + return None; + } + + let l3_vaddr = l4e.addr().as_u64().checked_add(phys_off)?; + let l3 = unsafe { &*(l3_vaddr as *const PageTable) }; + let l3i = ((page_addr >> 30) & 0x1ff) as usize; + let l3e = &l3[l3i]; + let l3f = l3e.flags(); + if l3e.is_unused() + || !l3f.contains(PageTableFlags::PRESENT) + || !l3f.contains(PageTableFlags::USER_ACCESSIBLE) + { + return None; + } + if l3f.contains(PageTableFlags::HUGE_PAGE) { + return Some(l3f); + } + + let l2_vaddr = l3e.addr().as_u64().checked_add(phys_off)?; + let l2 = unsafe { &*(l2_vaddr as *const PageTable) }; + let l2i = ((page_addr >> 21) & 0x1ff) as usize; + let l2e = &l2[l2i]; + let l2f = l2e.flags(); + if l2e.is_unused() + || !l2f.contains(PageTableFlags::PRESENT) + || !l2f.contains(PageTableFlags::USER_ACCESSIBLE) + { + return None; + } + if l2f.contains(PageTableFlags::HUGE_PAGE) { + return Some(l2f); + } + + let l1_vaddr = l2e.addr().as_u64().checked_add(phys_off)?; + let l1 = unsafe { &*(l1_vaddr as *const PageTable) }; + let l1i = ((page_addr >> 12) & 0x1ff) as usize; + let l1e = &l1[l1i]; + let l1f = l1e.flags(); + if l1e.is_unused() + || !l1f.contains(PageTableFlags::PRESENT) + || !l1f.contains(PageTableFlags::USER_ACCESSIBLE) + { + return None; + } + Some(l1f) +} + +fn page_is_user_mapped_in_table(table_phys: u64, page_addr: u64) -> bool { + user_page_flags_in_table(table_phys, page_addr).is_some_and(|flags| { + flags.contains(PageTableFlags::PRESENT) && flags.contains(PageTableFlags::USER_ACCESSIBLE) + }) +} + +fn translate_user_addr_in_table( + table_phys: u64, + addr: u64, + require_writable: bool, +) -> Option<(u64, usize)> { + if addr > USER_SPACE_END { + return None; + } + let page_base = addr & !0xfffu64; + let page_off = (addr & 0xfff) as usize; + let flags = user_page_flags_in_table(table_phys, page_base)?; + if !flags.contains(PageTableFlags::PRESENT) || !flags.contains(PageTableFlags::USER_ACCESSIBLE) + { + return None; + } + if require_writable && !flags.contains(PageTableFlags::WRITABLE) { + return None; + } + let (phys, _) = translate_addr_in_table(table_phys, VirtAddr::new(addr))?; + Some((phys.as_u64(), page_off)) +} + +#[inline] +fn nospec_usercopy_barrier() { + #[cfg(target_arch = "x86_64")] + unsafe { + core::arch::asm!("lfence", options(nomem, nostack, preserves_flags)); + } +} + +pub fn copy_from_user_in_table(table_phys: u64, src_ptr: u64, dst: &mut [u8]) -> Result<()> { + use core::sync::atomic::{compiler_fence, Ordering}; + + if dst.is_empty() { + return Ok(()); + } + let phys_off = physical_memory_offset().ok_or(Kernel::Memory(Memory::NotMapped))?; + // Disable SMAP/SMEP while performing the actual kernel-side copies + // so dereferencing (phys + phys_off) is allowed. + let _smap_guard = crate::cpu::SmapSmepGuard::new(); + + let len = dst.len() as u64; + let end = src_ptr + .checked_add(len.saturating_sub(1)) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + if src_ptr == 0 || src_ptr > USER_SPACE_END || end > USER_SPACE_END { + return Err(Kernel::Memory(Memory::InvalidAddress)); + } + + compiler_fence(Ordering::SeqCst); + nospec_usercopy_barrier(); + + let mut copied = 0usize; + while copied < dst.len() { + let cur = src_ptr + .checked_add(copied as u64) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + let (phys, page_off) = translate_user_addr_in_table(table_phys, cur, false) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + nospec_usercopy_barrier(); + let chunk = core::cmp::min(4096usize.saturating_sub(page_off), dst.len() - copied); + let src = phys + .checked_add(phys_off) + .ok_or(Kernel::Memory(Memory::InvalidAddress))? as *const u8; + unsafe { + core::ptr::copy_nonoverlapping(src, dst[copied..].as_mut_ptr(), chunk); + } + copied += chunk; + } + + Ok(()) +} + +pub fn copy_to_user_in_table(table_phys: u64, dst_ptr: u64, src: &[u8]) -> Result<()> { + use core::sync::atomic::{compiler_fence, Ordering}; + + if src.is_empty() { + return Ok(()); + } + let phys_off = physical_memory_offset().ok_or(Kernel::Memory(Memory::NotMapped))?; + // Disable SMAP/SMEP while performing the actual kernel-side copies + // so dereferencing (phys + phys_off) is allowed. + let _smap_guard = crate::cpu::SmapSmepGuard::new(); + + let len = src.len() as u64; + let end = dst_ptr + .checked_add(len.saturating_sub(1)) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + if dst_ptr == 0 || dst_ptr > USER_SPACE_END || end > USER_SPACE_END { + return Err(Kernel::Memory(Memory::InvalidAddress)); + } + + compiler_fence(Ordering::SeqCst); + nospec_usercopy_barrier(); + + let mut copied = 0usize; + while copied < src.len() { + let cur = dst_ptr + .checked_add(copied as u64) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + let (phys, page_off) = translate_user_addr_in_table(table_phys, cur, true) + .ok_or(Kernel::Memory(Memory::PermissionDenied))?; + nospec_usercopy_barrier(); + let chunk = core::cmp::min(4096usize.saturating_sub(page_off), src.len() - copied); + let dst = phys + .checked_add(phys_off) + .ok_or(Kernel::Memory(Memory::InvalidAddress))? as *mut u8; + unsafe { + core::ptr::copy_nonoverlapping(src[copied..].as_ptr(), dst, chunk); + } + copied += chunk; + } + + Ok(()) +} + +/// 指定したページテーブルでユーザー範囲がすべて有効にマップされているか確認する +pub fn is_user_range_mapped_in_table(table_phys: u64, addr: u64, len: u64) -> bool { + if addr > USER_SPACE_END { + return false; + } + if len == 0 { + return true; + } + + let end_inclusive = match addr.checked_add(len.saturating_sub(1)) { + Some(v) if v <= USER_SPACE_END => v, + _ => return false, + }; + + let mut page_addr = addr & !0xfffu64; + let end_page = end_inclusive & !0xfffu64; + loop { + if !page_is_user_mapped_in_table(table_phys, page_addr) { + return false; + } + if page_addr == end_page { + return true; + } + page_addr = match page_addr.checked_add(4096) { + Some(v) => v, + None => return false, + }; + } +} + +/// 指定した仮想アドレス範囲にセグメントをマップしてコピーする +/// +/// データはカーネルの恒等マッピング(phys = virt)経由で物理フレームに直接書き込む。 +/// +/// ## Arguments +/// - `vaddr`: セグメントの開始仮想アドレス +/// - `filesz`: セグメントのファイルサイズ(ELFヘッダのp_filesz) +/// - `memsz`: セグメントのメモリサイズ(ELFヘッダのp_memsz) +/// - `src`: セグメントのデータが格納されたバッファ +/// - `writable`: セグメントをRWXのどれでマップするか +/// - `executable`: セグメントをRWXのどれでマップするか +/// +/// ## Returns +/// 成功した場合は `Ok(())`、失敗した場合はエラー +pub fn map_and_copy_segment( + vaddr: u64, + filesz: u64, + memsz: u64, + src: &[u8], + writable: bool, + executable: bool, +) -> Result<()> { + use crate::mem::frame; + use crate::result::{Kernel, Memory}; + + let phys_off = physical_memory_offset().ok_or(Kernel::Memory(Memory::NotMapped))?; + if memsz == 0 { + return if filesz == 0 { + Ok(()) + } else { + Err(Kernel::InvalidParam) + }; + } + if memsz < filesz || (filesz as usize) > src.len() { + return Err(Kernel::InvalidParam); + } + let file_end = vaddr + .checked_add(filesz) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + let mem_end = vaddr + .checked_add(memsz) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + let start = vaddr & !0xfffu64; + let end = mem_end + .checked_add(0xfff) + .map(|v| v & !0xfffu64) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + + let mut page_addr = start; + while page_addr < end { + let page = Page::containing_address(VirtAddr::new(page_addr)); + let phys_frame_addr; + + // Check if page is already mapped + let is_mapped = translate_addr(VirtAddr::new(page_addr)).is_some(); + + if is_mapped { + // Already mapped. Ensure it is writable for loading. + phys_frame_addr = translate_addr(VirtAddr::new(page_addr)) + .ok_or(Kernel::Memory(Memory::InvalidAddress))? + .as_u64(); + + // Temporarily map as writable for loading, but preserve execute permission + // to avoid conflicts with final flags + let flags = PageTableFlags::PRESENT + | PageTableFlags::USER_ACCESSIBLE + | PageTableFlags::WRITABLE; + // Don't set NO_EXECUTE during loading - we'll set it in the final flag update if needed + + if let Some(ref mut pt) = PAGE_TABLE.lock().as_mut() { + unsafe { + // Update flags ignoring error (e.g. if already same) + let _ = pt.update_flags(page, flags).map(|f| f.flush()); + } + } + + crate::debug!( + "reusing mapped page {:#x} -> phys {:#x}", + page_addr, + phys_frame_addr + ); + } else { + // Not mapped, allocate new frame + let frame = frame::allocate_frame()?; + + // Setup flags: PRESENT + USER + WRITABLE + let flags = PageTableFlags::PRESENT + | PageTableFlags::USER_ACCESSIBLE + | PageTableFlags::WRITABLE; + + crate::debug!( + "about to map page {:#x} -> frame {:#x}, flags={:?}, writable={}", + page_addr, + frame.start_address().as_u64(), + flags, + writable + ); + map_page(page, frame, flags)?; + phys_frame_addr = frame.start_address().as_u64(); + crate::debug!( + "mapped page {:#x} -> phys {:#x}", + page_addr, + phys_frame_addr + ); + } + + let page_start = page_addr; + let page_end = page_addr + 4096; + let copy_start = core::cmp::max(page_start, vaddr); + let copy_end = core::cmp::min(page_end, file_end); + if copy_start < copy_end { + let src_off = (copy_start - vaddr) as usize; + let len = (copy_end - copy_start) as usize; + let offset_into_page = (copy_start - page_start); + let dst_virt_addr = page_start + offset_into_page; + let dst_virt = dst_virt_addr as *mut u8; + crate::debug!( + "copying {} bytes to virt {:#x} (phys {:#x})", + len, + dst_virt_addr, + phys_frame_addr + offset_into_page + ); + unsafe { + core::ptr::copy_nonoverlapping(src.as_ptr().add(src_off), dst_virt, len); + } + } + if page_start < mem_end { + let zero_start = core::cmp::max(page_start, file_end); + let zero_end = core::cmp::min(page_end, mem_end); + if zero_start < zero_end { + let offset_into_page = (zero_start - page_start); + let dst_virt_addr = page_start + offset_into_page; + let dst_virt = dst_virt_addr as *mut u8; + let len = (zero_end - zero_start) as usize; + crate::debug!( + "zeroing {} bytes at virt {:#x} (phys {:#x})", + len, + dst_virt_addr, + phys_frame_addr + offset_into_page + ); + unsafe { core::ptr::write_bytes(dst_virt, 0, len) }; + } + } + // セグメントのコピーと初期化が完了したら、最終的なフラグを設定 + if let Some(ref mut pt) = PAGE_TABLE.lock().as_mut() { + let mut new_flags = PageTableFlags::PRESENT | PageTableFlags::USER_ACCESSIBLE; + + if writable { + new_flags |= PageTableFlags::WRITABLE; + } + + // NX (No-Execute) bit: set it for non-executable pages + if !executable { + new_flags |= PageTableFlags::NO_EXECUTE; + } + + crate::debug!( + "Updating page {:#x} flags: writable={}, executable={}, flags={:?}", + page_addr, + writable, + executable, + new_flags + ); + + unsafe { + // まず既存のマッピングを解除 + if let Ok((_, flush)) = pt.unmap(page) { + flush.flush(); + } + + // 同じ物理フレームに新しいフラグで再マップ + let phys_frame = PhysFrame::containing_address(PhysAddr::new(phys_frame_addr)); + { + let mut alloc_lock = frame::FRAME_ALLOCATOR.lock(); + let alloc_ref = alloc_lock + .as_mut() + .ok_or(Kernel::Memory(Memory::OutOfMemory))?; + match pt.map_to(page, phys_frame, new_flags, alloc_ref) { + Ok(flush) => flush.flush(), + Err(e) => crate::warn!("Failed to remap page {:#x}: {:?}", page_addr, e), + } + } + } + } + page_addr += 4096; + } + + Ok(()) +} + +pub use x86_64::PhysAddr; + +fn clone_kernel_l1_table_without_user_entries(src_l1_phys: u64, phys_off: u64) -> Result { + let src_l1 = unsafe { &*((src_l1_phys + phys_off) as *const PageTable) }; + let new_l1_frame = frame::allocate_frame()?; + let new_l1_phys = new_l1_frame.start_address().as_u64(); + let new_l1 = unsafe { &mut *((new_l1_phys + phys_off) as *mut PageTable) }; + new_l1.zero(); + + for i in 0..512 { + let entry = src_l1[i].clone(); + let flags = entry.flags(); + if entry.is_unused() || !flags.contains(PageTableFlags::PRESENT) { + continue; + } + if flags.contains(PageTableFlags::USER_ACCESSIBLE) { + continue; + } + new_l1[i] = entry; + } + + Ok(new_l1_phys) +} + +fn clone_kernel_l2_table_without_user_entries(src_l2_phys: u64, phys_off: u64) -> Result { + let src_l2 = unsafe { &*((src_l2_phys + phys_off) as *const PageTable) }; + let new_l2_frame = frame::allocate_frame()?; + let new_l2_phys = new_l2_frame.start_address().as_u64(); + let new_l2 = unsafe { &mut *((new_l2_phys + phys_off) as *mut PageTable) }; + new_l2.zero(); + + for i in 0..512 { + let entry = src_l2[i].clone(); + let flags = entry.flags(); + if entry.is_unused() || !flags.contains(PageTableFlags::PRESENT) { + continue; + } + + if flags.contains(PageTableFlags::HUGE_PAGE) { + if !flags.contains(PageTableFlags::USER_ACCESSIBLE) { + new_l2[i] = entry; + } + continue; + } + + let new_l1_phys = + clone_kernel_l1_table_without_user_entries(entry.addr().as_u64(), phys_off)?; + new_l2[i].set_addr(PhysAddr::new(new_l1_phys), flags); + } + + Ok(new_l2_phys) +} + +/// ユーザープロセス用の新しいL4ページテーブルを作成する +/// +/// カーネルのページテーブル階層を部分的にコピーして、カーネルメモリには +/// アクセス可能だがユーザー空間は空(プロセス固有)の新しいページテーブルを作成する。 +/// +/// アドレス空間レイアウト(phys_off=0, identity mapping): +/// - 0x200000 (L4[0]→L3[0]→L2[1]) : ユーザーコード(プロセス固有) +/// - 0x179d... (L4[0]→L3[0]→L2[188-189]): カーネルスタック(共有) +/// - 0x139... (L4[0]→L3[0]→L2[458]): カーネルコード(共有) +/// - 0x7FFF_FFF0_0000 (L4[255]): ユーザースタック(プロセス固有) +/// +/// ## Returns +/// 新しいL4テーブルの物理アドレス +pub fn create_user_page_table() -> Result { + let phys_off = physical_memory_offset().ok_or(Kernel::Memory(Memory::NotMapped))?; + + // カーネルの「元の」L4テーブルを使用する(syscall中はCR3がユーザープロセスのテーブルなため) + let kernel_l4_phys = KERNEL_L4_PHYS.load(core::sync::atomic::Ordering::Relaxed); + if kernel_l4_phys == 0 { + return Err(Kernel::Memory(Memory::NotMapped)); + } + let kernel_l4 = unsafe { &*((kernel_l4_phys + phys_off) as *const PageTable) }; + + // 新しいL4フレームを確保してゼロ初期化 + let new_l4_frame = frame::allocate_frame()?; + let new_l4_phys = new_l4_frame.start_address().as_u64(); + let new_l4 = unsafe { &mut *((new_l4_phys + phys_off) as *mut PageTable) }; + new_l4.zero(); + + // KPTI強化: L4[0](低位512GiB)のみ最小限コピーする。 + // これにより上位L4エントリを通じた広域カーネルマッピングをユーザーテーブルから除外する。 + // 実際のユーザー領域は exec/mmap 時に個別マップされる。 + if !kernel_l4[0].is_unused() { + let kernel_l3_phys = kernel_l4[0].addr().as_u64(); + let kernel_l3 = unsafe { &*((kernel_l3_phys + phys_off) as *const PageTable) }; + + let new_l3_frame = frame::allocate_frame()?; + let new_l3_phys = new_l3_frame.start_address().as_u64(); + let new_l3 = unsafe { &mut *((new_l3_phys + phys_off) as *mut PageTable) }; + new_l3.zero(); + + // L3[0]: 最初の1GB(カーネルコード・スタックとユーザーコードが混在) + if !kernel_l3[0].is_unused() { + let kernel_l2_phys = kernel_l3[0].addr().as_u64(); + let new_l2_phys = clone_kernel_l2_table_without_user_entries(kernel_l2_phys, phys_off)?; + + new_l3[0].set_addr(PhysAddr::new(new_l2_phys), kernel_l3[0].flags()); + } + + new_l4[0].set_addr(PhysAddr::new(new_l3_phys), kernel_l4[0].flags()); + } + + // 0x800000 アドレス(ユーザーコード領域)用に新しい L3/L2/L1 テーブルを事前に割り当てる + let user_l3_phys = new_l4[0].addr().as_u64(); + if user_l3_phys != 0 { + let user_l3 = unsafe { &mut *((user_l3_phys + phys_off) as *mut PageTable) }; + + let new_l2_frame = frame::allocate_frame()?; + let new_l2_phys = new_l2_frame.start_address().as_u64(); + let new_l2 = unsafe { &mut *((new_l2_phys + phys_off) as *mut PageTable) }; + new_l2.zero(); + + // ブートローダーのL2テーブルからカーネル領域エントリをコピー + if !user_l3[0].is_unused() { + let old_l2_phys = user_l3[0].addr().as_u64(); + let old_l2 = unsafe { &*((old_l2_phys + phys_off) as *const PageTable) }; + + for i in 0..512 { + let entry = old_l2[i].clone(); + let flags = entry.flags(); + // カーネル領域(USER_ACCESSIBLEでない)のみコピー + if !entry.is_unused() && !flags.contains(PageTableFlags::USER_ACCESSIBLE) { + new_l2[i] = entry; + } + } + } + + // L2[4] エントリ(0x800000-0x9FFFFF)に新しいL1テーブルをセット + // NOTE: L2 エントリは 2MiB 単位なので、0x800000 >> 21 == 4 + let new_l1_frame = frame::allocate_frame()?; + let new_l1_phys = new_l1_frame.start_address().as_u64(); + let new_l1 = unsafe { &mut *((new_l1_phys + phys_off) as *mut PageTable) }; + new_l1.zero(); + + new_l2[4].set_addr( + PhysAddr::new(new_l1_phys), + PageTableFlags::PRESENT | PageTableFlags::WRITABLE | PageTableFlags::USER_ACCESSIBLE, + ); + + // L3[0] を新しいL2テーブルに切り替え + user_l3[0].set_addr( + PhysAddr::new(new_l2_phys), + PageTableFlags::PRESENT | PageTableFlags::WRITABLE, + ); + } + + Ok(new_l4_phys) +} + +/// 既存のユーザーページテーブルをフルコピーして新しいページテーブルを返す +/// +/// - カーネル共有マッピングは `create_user_page_table()` により初期化 +/// - USER_ACCESSIBLE な4KiBページを新規フレームへコピー +pub fn clone_user_page_table(src_table_phys: u64) -> Result { + use x86_64::structures::paging::PageTableFlags as Flags; + + struct DstTableGuard(Option); + impl DstTableGuard { + fn disarm(&mut self) { + self.0 = None; + } + } + impl Drop for DstTableGuard { + fn drop(&mut self) { + if let Some(phys) = self.0 { + let _ = destroy_user_page_table(phys); + } + } + } + + let phys_off = physical_memory_offset().ok_or(Kernel::Memory(Memory::NotMapped))?; + // Disable SMAP/SMEP while walking and copying user page tables + let _smap_guard = crate::cpu::SmapSmepGuard::new(); + + let dst_table_phys = create_user_page_table()?; + let mut dst_guard = DstTableGuard(Some(dst_table_phys)); + + let src_l4 = unsafe { &*((src_table_phys + phys_off) as *const PageTable) }; + let dst_l4 = unsafe { &mut *((dst_table_phys + phys_off) as *mut PageTable) }; + let mut dst_pt = unsafe { OffsetPageTable::new(dst_l4, VirtAddr::new(phys_off)) }; + + for l4i in 0usize..256 { + let l4e = &src_l4[l4i]; + if l4e.is_unused() || !l4e.flags().contains(Flags::PRESENT) { + continue; + } + let src_l3 = unsafe { &*((l4e.addr().as_u64() + phys_off) as *const PageTable) }; + for l3i in 0usize..512 { + let l3e = &src_l3[l3i]; + if l3e.is_unused() || !l3e.flags().contains(Flags::PRESENT) { + continue; + } + if l3e.flags().contains(Flags::HUGE_PAGE) { + continue; + } + let src_l2 = unsafe { &*((l3e.addr().as_u64() + phys_off) as *const PageTable) }; + for l2i in 0usize..512 { + let l2e = &src_l2[l2i]; + if l2e.is_unused() || !l2e.flags().contains(Flags::PRESENT) { + continue; + } + if l2e.flags().contains(Flags::HUGE_PAGE) { + continue; + } + let src_l1 = unsafe { &*((l2e.addr().as_u64() + phys_off) as *const PageTable) }; + for l1i in 0usize..512 { + let pte = &src_l1[l1i]; + if pte.is_unused() { + continue; + } + let src_flags = pte.flags(); + if !src_flags.contains(Flags::PRESENT) + || !src_flags.contains(Flags::USER_ACCESSIBLE) + { + continue; + } + + let vaddr = ((l4i as u64) << 39) + | ((l3i as u64) << 30) + | ((l2i as u64) << 21) + | ((l1i as u64) << 12); + let page = Page::::containing_address(VirtAddr::new(vaddr)); + + let new_frame = frame::allocate_frame()?; + let mut dst_flags = Flags::PRESENT | Flags::USER_ACCESSIBLE; + if src_flags.contains(Flags::WRITABLE) { + dst_flags |= Flags::WRITABLE; + } + if src_flags.contains(Flags::NO_EXECUTE) { + dst_flags |= Flags::NO_EXECUTE; + } + + unsafe { + let mut alloc_lock = frame::FRAME_ALLOCATOR.lock(); + let alloc_ref = alloc_lock + .as_mut() + .ok_or(Kernel::Memory(Memory::OutOfMemory))?; + match dst_pt.map_to(page, new_frame, dst_flags, alloc_ref) { + Ok(flush) => flush.ignore(), + Err( + x86_64::structures::paging::mapper::MapToError::PageAlreadyMapped( + _, + ), + ) => { + let (old_frame, flush) = dst_pt + .unmap(page) + .map_err(|_| Kernel::Memory(Memory::InvalidAddress))?; + flush.ignore(); + let _ = frame::deallocate_frame(old_frame); + let mut alloc_lock2 = frame::FRAME_ALLOCATOR.lock(); + let alloc_ref2 = alloc_lock2 + .as_mut() + .ok_or(Kernel::Memory(Memory::OutOfMemory))?; + dst_pt + .map_to(page, new_frame, dst_flags, alloc_ref2) + .map_err(|_| Kernel::Memory(Memory::InvalidAddress))? + .ignore(); + } + Err(_) => return Err(Kernel::Memory(Memory::InvalidAddress)), + } + } + + let src_ptr = (pte.addr().as_u64() + phys_off) as *const u8; + let dst_ptr = (new_frame.start_address().as_u64() + phys_off) as *mut u8; + unsafe { + core::ptr::copy_nonoverlapping(src_ptr, dst_ptr, 4096); + } + } + } + } + } + + dst_guard.disarm(); + Ok(dst_table_phys) +} + +/// ユーザーページテーブル用のL3/L2/L1階層を指定アドレスまで事前作成 +/// +/// テンポラリマッピング経由でユーザーL4にアクセスし、必要なL3/L2/L1テーブルを作成 +fn ensure_user_page_table_hierarchy(temp_kern_virt: u64, vaddr: u64, phys_off: u64) -> Result<()> { + use crate::result::{Kernel, Memory}; + use x86_64::structures::paging::PageTableFlags as Flags; + + let l4_index = ((vaddr >> 39) & 0x1ff) as usize; + let l3_index = ((vaddr >> 30) & 0x1ff) as usize; + let l2_index = ((vaddr >> 21) & 0x1ff) as usize; + + // テンポラリマッピング経由でL4にアクセス + let l4 = unsafe { &mut *(temp_kern_virt as *mut PageTable) }; + + // L3テーブルをチェック/作成 + if l4[l4_index].is_unused() { + crate::debug!("Creating new L3 table for L4[{}]", l4_index); + let l3_frame = { + let mut alloc = frame::FRAME_ALLOCATOR.lock(); + alloc + .as_mut() + .ok_or(Kernel::Memory(Memory::OutOfMemory))? + .allocate_frame() + .ok_or(Kernel::Memory(Memory::OutOfMemory))? + }; + let l3_phys = l3_frame.start_address().as_u64(); + + // L3をカーネル仮想空間でゼロ初期化 + let l3_virt = l3_phys + phys_off; + unsafe { + core::ptr::write_bytes(l3_virt as *mut PageTable, 0, 1); + } + + // ユーザーL4にL3を記録 + l4[l4_index].set_addr( + x86_64::PhysAddr::new(l3_phys), + Flags::PRESENT | Flags::WRITABLE | Flags::USER_ACCESSIBLE, + ); + } else { + let mut flags = + l4[l4_index].flags() | Flags::PRESENT | Flags::WRITABLE | Flags::USER_ACCESSIBLE; + flags.remove(Flags::NO_EXECUTE); + let addr = l4[l4_index].addr(); + l4[l4_index].set_addr(addr, flags); + } + + // L2テーブルをチェック/作成 + let l3_phys = l4[l4_index].addr().as_u64(); + let l3_virt = l3_phys + phys_off; + let l3 = unsafe { &mut *(l3_virt as *mut PageTable) }; + + if l3[l3_index].is_unused() { + crate::debug!("Creating new L2 table for L3[{}]", l3_index); + let l2_frame = { + let mut alloc = frame::FRAME_ALLOCATOR.lock(); + alloc + .as_mut() + .ok_or(Kernel::Memory(Memory::OutOfMemory))? + .allocate_frame() + .ok_or(Kernel::Memory(Memory::OutOfMemory))? + }; + let l2_phys = l2_frame.start_address().as_u64(); + + // L2をカーネル仮想空間でゼロ初期化 + let l2_virt = l2_phys + phys_off; + unsafe { + core::ptr::write_bytes(l2_virt as *mut PageTable, 0, 1); + } + + // ユーザーL3にL2を記録 + l3[l3_index].set_addr( + x86_64::PhysAddr::new(l2_phys), + Flags::PRESENT | Flags::WRITABLE | Flags::USER_ACCESSIBLE, + ); + } else { + let mut flags = + l3[l3_index].flags() | Flags::PRESENT | Flags::WRITABLE | Flags::USER_ACCESSIBLE; + flags.remove(Flags::NO_EXECUTE); + let addr = l3[l3_index].addr(); + l3[l3_index].set_addr(addr, flags); + } + + // L1テーブルをチェック/作成(対象 L2 エントリのみ) + let l2_phys = l3[l3_index].addr().as_u64(); + let l2_virt = l2_phys + phys_off; + let l2 = unsafe { &mut *(l2_virt as *mut PageTable) }; + + let l2_flags = l2[l2_index].flags(); + if l2[l2_index].is_unused() || l2_flags.contains(Flags::HUGE_PAGE) { + let l1_frame = { + let mut alloc = frame::FRAME_ALLOCATOR.lock(); + alloc + .as_mut() + .ok_or(Kernel::Memory(Memory::OutOfMemory))? + .allocate_frame() + .ok_or(Kernel::Memory(Memory::OutOfMemory))? + }; + let l1_phys = l1_frame.start_address().as_u64(); + + let l1_virt = l1_phys + phys_off; + unsafe { + core::ptr::write_bytes(l1_virt as *mut PageTable, 0, 1); + } + + l2[l2_index].set_addr( + x86_64::PhysAddr::new(l1_phys), + Flags::PRESENT | Flags::WRITABLE | Flags::USER_ACCESSIBLE, + ); + } else { + let mut flags = + l2[l2_index].flags() | Flags::PRESENT | Flags::WRITABLE | Flags::USER_ACCESSIBLE; + flags.remove(Flags::NO_EXECUTE); + let addr = l2[l2_index].addr(); + l2[l2_index].set_addr(addr, flags); + } + + Ok(()) +} + +/// 指定したページテーブル(物理アドレス)にセグメントをマップしてコピーする +/// +/// データはカーネルの恒等マッピング(phys = virt)経由で物理フレームに直接書き込む。 +/// フラッシュはカレントCR3に対しては不要なため `.ignore()` を使う。 +/// +/// ## Arguments +/// - `table_phys`: マップ先のページテーブルの物理アドレス +/// - `vaddr`: セグメントの開始仮想アドレス +/// - `filesz`: セグメントのファイルサイズ(ELFヘッダのp_filesz) +/// - `memsz`: セグメントのメモリサイズ(ELFヘッダのp_memsz) +/// - `src`: セグメントのデータが格納されたバッファ +/// - `writable`: セグメントをRWXのどれでマップするか +/// - `executable`: セグメントをRWXのどれでマップするか +/// +/// ## Returns +/// 成功した場合は `Ok(())`、失敗した場合はエラー +pub fn map_and_copy_segment_to( + table_phys: u64, + vaddr: u64, + filesz: u64, + memsz: u64, + src: &[u8], + writable: bool, + executable: bool, +) -> Result<()> { + use crate::result::{Kernel, Memory}; + use x86_64::structures::paging::PageTableFlags as Flags; + + if writable && executable { + return Err(Kernel::Memory(Memory::PermissionDenied)); + } + + let phys_off = physical_memory_offset().ok_or(Kernel::Memory(Memory::NotMapped))?; + // Disable SMAP/SMEP while manipulating user page tables and copying segments. + // This guard restores the previous state on drop. + let _smap_guard = crate::cpu::SmapSmepGuard::new(); + if memsz == 0 { + return if filesz == 0 { + Ok(()) + } else { + Err(Kernel::InvalidParam) + }; + } + if memsz < filesz || (filesz as usize) > src.len() { + return Err(Kernel::InvalidParam); + } + let file_end = vaddr + .checked_add(filesz) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + let mem_end = vaddr + .checked_add(memsz) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + + // ユーザーL4テーブルをカーネルのページテーブルに一時的にマップ + // これにより、新規割り当てのテーブルフレームへのアクセスが可能になる + let temp_kern_virt = 0xFFFF_8000_0000_0000u64; + crate::debug!( + "Attempting temporary kernel map for user L4 at phys {:#x}", + table_phys + ); + + let mut page_table_lock = PAGE_TABLE.lock(); + let kernel_pt = page_table_lock + .as_mut() + .ok_or(Kernel::Memory(Memory::NotMapped))?; + + let temp_page = Page::::containing_address(VirtAddr::new(temp_kern_virt)); + let user_l4_frame = PhysFrame::containing_address(x86_64::PhysAddr::new(table_phys)); + + // カーネルのページテーブルにユーザーL4テーブルをテンポラリマップ + unsafe { + let mut frame_alloc = frame::FRAME_ALLOCATOR.lock(); + if let Some(alloc_ref) = frame_alloc.as_mut() { + match kernel_pt.map_to( + temp_page, + user_l4_frame, + Flags::PRESENT | Flags::WRITABLE | Flags::NO_EXECUTE, + alloc_ref, + ) { + Ok(_) => crate::debug!("Temporary kernel map succeeded for user L4"), + Err(e) => { + crate::debug!("Temporary kernel map failed: {:?}", e); + return Err(Kernel::Memory(Memory::InvalidAddress)); + } + } + } + } + drop(page_table_lock); + crate::debug!( + "Using temp kernel virt {:#x} to access user L4", + temp_kern_virt + ); + + // ヘルパー関数で必要なL3/L2/L1を事前作成 + ensure_user_page_table_hierarchy(temp_kern_virt, vaddr, phys_off)?; + + // メモリ範囲全体をカバーするために、メモリの終了アドレスもカバー + ensure_user_page_table_hierarchy(temp_kern_virt, mem_end - 1, phys_off)?; + + crate::debug!("Page table hierarchy pre-created"); + + // ユーザーL4/L3/L2/L1テーブルは全て作成済み。ここからページをマップする。 + // 直接物理ページテーブルを操作(テンポラリマッピング経由) + + let mut final_flags = Flags::PRESENT | Flags::USER_ACCESSIBLE; + if writable { + final_flags |= Flags::WRITABLE; + } + if executable { + // Executable segment: don't set NO_EXECUTE (allow instruction fetch) + } else { + // Non-executable segment: set NO_EXECUTE + final_flags |= Flags::NO_EXECUTE; + } + + let start = vaddr & !0xfffu64; + let end = mem_end + .checked_add(0xfff) + .map(|v| v & !0xfffu64) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + + let mut page_addr = start; + while page_addr < end { + let l4_index = ((page_addr >> 39) & 0x1ff) as usize; + let l3_index = ((page_addr >> 30) & 0x1ff) as usize; + let l2_index = ((page_addr >> 21) & 0x1ff) as usize; + let l1_index = ((page_addr >> 12) & 0x1ff) as usize; + + // 指定ページのためにページテーブル階層を確実に作成 + //(セグメントが L2/L1 境界を跨ぐと中間の L1 が作成されていない可能性があるため) + ensure_user_page_table_hierarchy(temp_kern_virt, page_addr, phys_off)?; + + let frame = { + let mut alloc = frame::FRAME_ALLOCATOR.lock(); + alloc + .as_mut() + .ok_or(Kernel::Memory(Memory::OutOfMemory))? + .allocate_frame() + .ok_or(Kernel::Memory(Memory::OutOfMemory))? + }; + let mut phys_frame_addr = frame.start_address().as_u64(); + + // フレームを先にゼロ初期化(BSS領域のため) + unsafe { + core::ptr::write_bytes((phys_frame_addr + phys_off) as *mut u8, 0, 4096); + } + + // 直接ページテーブルにエントリを作成(テンポラリマッピング経由で) + + + crate::debug!( + "Mapping page {:#x} (L4[{}]->L3[{}]->L2[{}]->L1[{}])", + page_addr, + l4_index, + l3_index, + l2_index, + l1_index + ); + + // テンポラリマッピング経由で L4 → L3 → L2 → L1 をたどる + let l4 = unsafe { &mut *(temp_kern_virt as *mut PageTable) }; + let l3_phys = l4[l4_index].addr().as_u64(); + if l3_phys == 0 { + crate::error!("L3 not allocated at L4[{}]", l4_index); + return Err(Kernel::Memory(Memory::InvalidAddress)); + } + + let l3 = unsafe { &mut *((l3_phys + phys_off) as *mut PageTable) }; + let l2_phys = l3[l3_index].addr().as_u64(); + if l2_phys == 0 { + crate::error!("L2 not allocated at L3[{}]", l3_index); + return Err(Kernel::Memory(Memory::InvalidAddress)); + } + + let l2 = unsafe { &mut *((l2_phys + phys_off) as *mut PageTable) }; + let l1_phys = l2[l2_index].addr().as_u64(); + if l1_phys == 0 { + crate::error!("L1 not allocated at L2[{}]", l2_index); + return Err(Kernel::Memory(Memory::InvalidAddress)); + } + + let l1 = unsafe { &mut *((l1_phys + phys_off) as *mut PageTable) }; + + // L1 エントリをセット + if l1[l1_index].is_unused() { + l1[l1_index].set_addr(x86_64::PhysAddr::new(phys_frame_addr), final_flags); + } else { + let existing_addr = l1[l1_index].addr().as_u64(); + let existing_flags = l1[l1_index].flags(); + if !existing_flags.contains(Flags::USER_ACCESSIBLE) { + let merged = if existing_flags.contains(Flags::NO_EXECUTE) { + final_flags + } else { + final_flags & !Flags::NO_EXECUTE + }; + l1[l1_index].set_addr(x86_64::PhysAddr::new(phys_frame_addr), merged); + } else { + let existing_exec = !existing_flags.contains(Flags::NO_EXECUTE); + let existing_write = existing_flags.contains(Flags::WRITABLE); + let new_exec = !final_flags.contains(Flags::NO_EXECUTE); + let new_write = final_flags.contains(Flags::WRITABLE); + if (existing_exec && new_write) || (existing_write && new_exec) { + frame::deallocate_frame(frame); + return Err(Kernel::Memory(Memory::PermissionDenied)); + } + let merged = if !existing_flags.contains(Flags::NO_EXECUTE) { + final_flags & !Flags::NO_EXECUTE + } else { + final_flags + }; + l1[l1_index].set_addr(x86_64::PhysAddr::new(existing_addr), merged); + frame::deallocate_frame(frame); + phys_frame_addr = existing_addr; + } + } + + // ELFデータを物理フレームに直接書き込む + let page_start = page_addr; + let page_end = page_addr + 4096; + let copy_start = core::cmp::max(page_start, vaddr); + let copy_end = core::cmp::min(page_end, file_end); + if copy_start < copy_end { + let src_off = (copy_start - vaddr) as usize; + let dst_off = (copy_start - page_start) as usize; + let len = (copy_end - copy_start) as usize; + unsafe { + core::ptr::copy_nonoverlapping( + src.as_ptr().add(src_off), + (phys_frame_addr + phys_off + dst_off as u64) as *mut u8, + len, + ); + } + } + + page_addr += 4096; + } + + // テンポラリマッピングをアンマップしてTLBフラッシュ + let temp_page = Page::::containing_address(VirtAddr::new(0xFFFF_8000_0000_0000u64)); + if let Some(kernel_pt) = PAGE_TABLE.lock().as_mut() { + if let Ok((_, flush)) = kernel_pt.unmap(temp_page) { + flush.flush(); + } + } + + Ok(()) +} +/// +/// `mprotect` 用の helper で、既存マッピングを維持したまま +/// WRITABLE / NO_EXECUTE を更新する。W+X は拒否する。 +pub fn protect_user_range_in_table( + table_phys: u64, + addr: u64, + len: u64, + present: bool, + writable: bool, + executable: bool, +) -> Result<()> { + use crate::result::{Kernel, Memory}; + use x86_64::structures::paging::PageTableFlags as Flags; + + if present && writable && executable { + return Err(Kernel::Memory(Memory::PermissionDenied)); + } + if len == 0 { + return Ok(()); + } + if addr == 0 || addr > USER_SPACE_END { + return Err(Kernel::Memory(Memory::InvalidAddress)); + } + let end_inclusive = addr + .checked_add(len.saturating_sub(1)) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + if end_inclusive > USER_SPACE_END { + return Err(Kernel::Memory(Memory::InvalidAddress)); + } + + let phys_off = physical_memory_offset().ok_or(Kernel::Memory(Memory::NotMapped))?; + // Ensure SMAP/SMEP disabled while dereferencing HHDM pointers + let _smap_guard = crate::cpu::SmapSmepGuard::new(); + let start = addr & !0xfffu64; + let end = end_inclusive + .checked_add(0x1000) + .map(|v| v & !0xfffu64) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + let l4 = unsafe { &mut *((table_phys + phys_off) as *mut PageTable) }; + let mut pt = unsafe { OffsetPageTable::new(l4, VirtAddr::new(phys_off)) }; + let (current_cr3, _) = Cr3::read(); + let current_table = current_cr3.start_address().as_u64(); + + let mut page_addr = start; + while page_addr < end { + let page = Page::::containing_address(VirtAddr::new(page_addr)); + let existing_flags = user_page_flags_in_table(table_phys, page_addr) + .ok_or(Kernel::Memory(Memory::NotMapped))?; + if !existing_flags.contains(Flags::PRESENT) + || !existing_flags.contains(Flags::USER_ACCESSIBLE) + { + return Err(Kernel::Memory(Memory::NotMapped)); + } + + let mut new_flags = existing_flags; + new_flags + .remove(Flags::PRESENT | Flags::USER_ACCESSIBLE | Flags::WRITABLE | Flags::NO_EXECUTE); + if present { + new_flags |= Flags::PRESENT | Flags::USER_ACCESSIBLE; + if writable { + new_flags |= Flags::WRITABLE; + } + if !executable { + new_flags |= Flags::NO_EXECUTE; + } + } + + let flush = unsafe { + pt.update_flags(page, new_flags) + .map_err(|_| Kernel::Memory(Memory::InvalidAddress))? + }; + if current_table == table_phys { + flush.flush(); + } else { + flush.ignore(); + } + page_addr = page_addr + .checked_add(4096) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + } + + Ok(()) +} + +/// 指定したページテーブルでユーザー範囲をアンマップし、対応フレームを解放する +pub fn unmap_range_in_table(table_phys: u64, addr: u64, length: u64) -> Result<()> { + if length == 0 { + return Ok(()); + } + let phys_off = physical_memory_offset().ok_or(Kernel::Memory(Memory::NotMapped))?; + // Ensure SMAP/SMEP disabled while dereferencing HHDM pointers + let _smap_guard = crate::cpu::SmapSmepGuard::new(); + let end_raw = addr + .checked_add(length) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + let start = addr & !0xfffu64; + let end = end_raw + .checked_add(0xfff) + .map(|v| v & !0xfffu64) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + + let l4 = unsafe { &mut *((table_phys + phys_off) as *mut PageTable) }; + let mut pt = unsafe { OffsetPageTable::new(l4, VirtAddr::new(phys_off)) }; + + let mut page_addr = start; + while page_addr < end { + if !page_is_user_mapped_in_table(table_phys, page_addr) { + page_addr += 4096; + continue; + } + let page = Page::::containing_address(VirtAddr::new(page_addr)); + if let Ok((frame, flush)) = pt.unmap(page) { + flush.ignore(); + let _ = frame::deallocate_frame(frame); + } + page_addr += 4096; + } + Ok(()) +} + +/// アンマップするが、フレームの解放は行わない(フレーム所有権を移すときに使用) +pub fn unmap_range_in_table_preserve_frames(table_phys: u64, addr: u64, length: u64) -> Result<()> { + if length == 0 { + return Ok(()); + } + let phys_off = physical_memory_offset().ok_or(Kernel::Memory(Memory::NotMapped))?; + // Ensure SMAP/SMEP disabled while dereferencing HHDM pointers + let _smap_guard = crate::cpu::SmapSmepGuard::new(); + let end_raw = addr + .checked_add(length) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + let start = addr & !0xfffu64; + let end = end_raw + .checked_add(0xfff) + .map(|v| v & !0xfffu64) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + + let l4 = unsafe { &mut *((table_phys + phys_off) as *mut PageTable) }; + let mut pt = unsafe { OffsetPageTable::new(l4, VirtAddr::new(phys_off)) }; + + let mut page_addr = start; + while page_addr < end { + if !page_is_user_mapped_in_table(table_phys, page_addr) { + page_addr += 4096; + continue; + } + let page = Page::::containing_address(VirtAddr::new(page_addr)); + if let Ok((_, flush)) = pt.unmap(page) { + // do not deallocate the physical frame; ownership transferred + flush.ignore(); + } + page_addr += 4096; + } + Ok(()) +} + +fn deallocate_4k_frame_by_phys(frame_phys: u64) { + let frame = PhysFrame::::containing_address(PhysAddr::new(frame_phys)); + let _ = frame::deallocate_frame(frame); +} + +fn destroy_user_l1_table(l1_phys: u64, phys_off: u64) { + let l1 = unsafe { &mut *((l1_phys + phys_off) as *mut PageTable) }; + for i in 0..512 { + let entry = l1[i].clone(); + let flags = entry.flags(); + if entry.is_unused() + || !flags.contains(PageTableFlags::PRESENT) + || !flags.contains(PageTableFlags::USER_ACCESSIBLE) + { + continue; + } + deallocate_4k_frame_by_phys(entry.addr().as_u64()); + l1[i].set_unused(); + } + deallocate_4k_frame_by_phys(l1_phys); +} + +fn destroy_user_l2_table(l2_phys: u64, phys_off: u64) { + let l2 = unsafe { &mut *((l2_phys + phys_off) as *mut PageTable) }; + for i in 0..512 { + let entry = l2[i].clone(); + let flags = entry.flags(); + if entry.is_unused() + || !flags.contains(PageTableFlags::PRESENT) + || !flags.contains(PageTableFlags::USER_ACCESSIBLE) + { + continue; + } + if flags.contains(PageTableFlags::HUGE_PAGE) { + l2[i].set_unused(); + continue; + } + destroy_user_l1_table(entry.addr().as_u64(), phys_off); + l2[i].set_unused(); + } + deallocate_4k_frame_by_phys(l2_phys); +} + +fn destroy_user_l3_table(l3_phys: u64, phys_off: u64) { + let l3 = unsafe { &mut *((l3_phys + phys_off) as *mut PageTable) }; + for i in 0..512 { + let entry = l3[i].clone(); + let flags = entry.flags(); + if entry.is_unused() + || !flags.contains(PageTableFlags::PRESENT) + || !flags.contains(PageTableFlags::USER_ACCESSIBLE) + { + continue; + } + if flags.contains(PageTableFlags::HUGE_PAGE) { + l3[i].set_unused(); + continue; + } + destroy_user_l2_table(entry.addr().as_u64(), phys_off); + l3[i].set_unused(); + } + deallocate_4k_frame_by_phys(l3_phys); +} + +/// 失敗したfork/exec経路のロールバック用に、ユーザーページテーブルを破棄する +pub fn destroy_user_page_table(table_phys: u64) -> Result<()> { + if table_phys == 0 || (table_phys & 0xfff) != 0 { + return Err(Kernel::InvalidParam); + } + let phys_off = physical_memory_offset().ok_or(Kernel::Memory(Memory::NotMapped))?; + // Ensure SMAP/SMEP disabled while dereferencing HHDM pointers + let _smap_guard = crate::cpu::SmapSmepGuard::new(); + let l4 = unsafe { &mut *((table_phys + phys_off) as *mut PageTable) }; + + for i in 0usize..256 { + let entry = l4[i].clone(); + let flags = entry.flags(); + if entry.is_unused() + || !flags.contains(PageTableFlags::PRESENT) + || !flags.contains(PageTableFlags::USER_ACCESSIBLE) + { + continue; + } + if flags.contains(PageTableFlags::HUGE_PAGE) { + l4[i].set_unused(); + continue; + } + destroy_user_l3_table(entry.addr().as_u64(), phys_off); + l4[i].set_unused(); + } + + deallocate_4k_frame_by_phys(table_phys); + Ok(()) +} + +/// 物理アドレス範囲をユーザープロセスのページテーブルにマップする +/// +/// フレームバッファなどの MMIO 領域をユーザー空間へ公開するために使用する。 +/// 新規フレームは割り当てず、指定された物理アドレスのページをそのままマップする。 +/// +/// ## Arguments +/// * `table_phys` - ユーザープロセスの L4 ページテーブルの物理アドレス +/// * `virt_addr` - マップ先の仮想アドレス (4KiB アライン済み) +/// * `phys_addr` - マップ元の物理アドレス (4KiB アライン済み) +/// * `size` - マップするサイズ (バイト単位) +pub fn map_physical_range_to_user( + table_phys: u64, + virt_addr: u64, + phys_addr: u64, + size: u64, +) -> Result<()> { + use crate::result::{Kernel, Memory}; + use x86_64::structures::paging::PageTableFlags as Flags; + + let phys_off = physical_memory_offset().ok_or(Kernel::Memory(Memory::NotMapped))?; + // Ensure SMAP/SMEP disabled while dereferencing HHDM pointers + let _smap_guard = crate::cpu::SmapSmepGuard::new(); + if size == 0 { + return Ok(()); + } + + let l4 = unsafe { &mut *((table_phys + phys_off) as *mut PageTable) }; + let mut pt = unsafe { OffsetPageTable::new(l4, VirtAddr::new(phys_off)) }; + + let flags = Flags::PRESENT | Flags::WRITABLE | Flags::USER_ACCESSIBLE | Flags::NO_EXECUTE; + + let virt_start = virt_addr & !0xfffu64; + let phys_start = phys_addr & !0xfffu64; + let total_pages = size.checked_add(0xfff).map(|v| v >> 12).unwrap_or(0); + + for i in 0..total_pages { + let page = Page::::containing_address(VirtAddr::new(virt_start + i * 4096)); + let frame = PhysFrame::containing_address(PhysAddr::new(phys_start + i * 4096)); + + let map_result = unsafe { + let mut alloc_lock = frame::FRAME_ALLOCATOR.lock(); + let alloc_ref = alloc_lock + .as_mut() + .ok_or(Kernel::Memory(Memory::OutOfMemory))?; + pt.map_to(page, frame, flags, alloc_ref) + }; + + match map_result { + Ok(flush) => flush.ignore(), + Err(x86_64::structures::paging::mapper::MapToError::PageAlreadyMapped(_)) => unsafe { + if let Ok((_, flush)) = pt.unmap(page) { + flush.ignore(); + } + let mut alloc_lock2 = frame::FRAME_ALLOCATOR.lock(); + let alloc_ref2 = alloc_lock2 + .as_mut() + .ok_or(Kernel::Memory(Memory::OutOfMemory))?; + pt.map_to(page, frame, flags, alloc_ref2) + .map_err(|_| Kernel::Memory(Memory::InvalidAddress))? + .ignore(); + }, + Err(_) => return Err(Kernel::Memory(Memory::InvalidAddress)), + } + } + + Ok(()) +} + +/// CR3を指定した物理アドレスのページテーブルに切り替える +/// +/// ## Arguments +/// - `table_phys`: 切り替えるページテーブルの物理アドレス +/// +/// ## Safety +/// - `table_phys`が有効なページテーブルの物理アドレスであることを呼び出し元が保証する必要がある +pub fn switch_page_table(table_phys: u64) { + if table_phys == 0 || (table_phys & 0xfff) != 0 { + crate::warn!( + "Refusing to switch CR3 with invalid page table address: {:#x}", + table_phys + ); + return; + } + unsafe { + let frame = PhysFrame::::containing_address(PhysAddr::new(table_phys)); + Cr3::write(frame, Cr3Flags::empty()); + } +} + +/// 指定したページテーブル上で仮想アドレスを物理アドレスへ変換する(簡易版) +/// +/// # Arguments +/// * `table_phys` - ページテーブルの物理アドレス +/// * `virt_addr` - 変換する仮想アドレス +/// +/// # Returns +/// 成功時: 物理アドレス +/// エラー時: None +pub fn virt_to_phys_in_table(table_phys: u64, virt_addr: u64) -> Option { + translate_addr_in_table(table_phys, VirtAddr::new(virt_addr)).map(|(p, _)| p.as_u64()) +} + +/// 指定したページテーブルに物理ページをマップする +/// +/// # Arguments +/// * `table_phys` - ページテーブルの物理アドレス +/// * `virt_addr` - マップ先の仮想アドレス(ページアラインド済み) +/// * `phys_addr` - 物理ページアドレス(ページアラインド済み) +/// * `writable` - 書き込み可能フラグ +/// * `user_accessible` - ユーザーアクセス可能フラグ +/// +/// # Returns +/// 成功時: Ok(()) +/// エラー時: Err(Kernel::Memory(Memory::*) +pub fn map_page_in_table( + table_phys: u64, + virt_addr: u64, + phys_addr: u64, + writable: bool, + user_accessible: bool, +) -> Result<()> { + if (table_phys & 0xfff) != 0 { + return Err(Kernel::Memory(Memory::AlignmentError)); + } + if (virt_addr & 0xfff) != 0 { + return Err(Kernel::Memory(Memory::AlignmentError)); + } + if (phys_addr & 0xfff) != 0 { + return Err(Kernel::Memory(Memory::AlignmentError)); + } + + let phys_off = physical_memory_offset().ok_or(Kernel::Memory(Memory::NotMapped))?; + // Ensure SMAP/SMEP disabled while manipulating HHDM pointers + let _smap_guard = crate::cpu::SmapSmepGuard::new(); + + // ページテーブルエントリを手動で歩いてマップ + let l4_vaddr = table_phys + .checked_add(phys_off) + .ok_or(Kernel::Memory(Memory::OutOfMemory))?; + let l4 = unsafe { &mut *(l4_vaddr as *mut PageTable) }; + + let vaddr = VirtAddr::new(virt_addr); + let l4i = vaddr.p4_index(); + let l4e = &mut l4[l4i]; + + // L3テーブルの確保またはアクセス + let l3_phys = if l4e.is_unused() || !l4e.flags().contains(PageTableFlags::PRESENT) { + let new_l3_frame = frame::allocate_frame()?; + let new_l3 = new_l3_frame.start_address().as_u64(); + let new_l3_vaddr = new_l3 + .checked_add(phys_off) + .ok_or(Kernel::Memory(Memory::OutOfMemory))?; + unsafe { + core::ptr::write_bytes(new_l3_vaddr as *mut u8, 0, 4096); + } + let mut flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; + if user_accessible { + flags |= PageTableFlags::USER_ACCESSIBLE; + } + l4e.set_addr(PhysAddr::new(new_l3), flags); + new_l3 + } else { + if user_accessible && !l4e.flags().contains(PageTableFlags::USER_ACCESSIBLE) { + l4e.set_addr(l4e.addr(), l4e.flags() | PageTableFlags::USER_ACCESSIBLE); + } + l4e.addr().as_u64() + }; + + let l3_vaddr = l3_phys + .checked_add(phys_off) + .ok_or(Kernel::Memory(Memory::OutOfMemory))?; + let l3 = unsafe { &mut *(l3_vaddr as *mut PageTable) }; + let l3i = vaddr.p3_index(); + let l3e = &mut l3[l3i]; + + if !l3e.is_unused() + && l3e.flags().contains(PageTableFlags::PRESENT) + && l3e.flags().contains(PageTableFlags::HUGE_PAGE) + { + return Err(Kernel::Memory(Memory::AlreadyMapped)); + } + + // L2テーブルの確保またはアクセス + let l2_phys = if l3e.is_unused() || !l3e.flags().contains(PageTableFlags::PRESENT) { + let new_l2_frame = frame::allocate_frame()?; + let new_l2 = new_l2_frame.start_address().as_u64(); + let new_l2_vaddr = new_l2 + .checked_add(phys_off) + .ok_or(Kernel::Memory(Memory::OutOfMemory))?; + unsafe { + core::ptr::write_bytes(new_l2_vaddr as *mut u8, 0, 4096); + } + let mut flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; + if user_accessible { + flags |= PageTableFlags::USER_ACCESSIBLE; + } + l3e.set_addr(PhysAddr::new(new_l2), flags); + new_l2 + } else { + if user_accessible && !l3e.flags().contains(PageTableFlags::USER_ACCESSIBLE) { + l3e.set_addr(l3e.addr(), l3e.flags() | PageTableFlags::USER_ACCESSIBLE); + } + l3e.addr().as_u64() + }; + + let l2_vaddr = l2_phys + .checked_add(phys_off) + .ok_or(Kernel::Memory(Memory::OutOfMemory))?; + let l2 = unsafe { &mut *(l2_vaddr as *mut PageTable) }; + let l2i = vaddr.p2_index(); + let l2e = &mut l2[l2i]; + + if !l2e.is_unused() + && l2e.flags().contains(PageTableFlags::PRESENT) + && l2e.flags().contains(PageTableFlags::HUGE_PAGE) + { + return Err(Kernel::Memory(Memory::AlreadyMapped)); + } + + // L1テーブルの確保またはアクセス + let l1_phys = if l2e.is_unused() || !l2e.flags().contains(PageTableFlags::PRESENT) { + let new_l1_frame = frame::allocate_frame()?; + let new_l1 = new_l1_frame.start_address().as_u64(); + let new_l1_vaddr = new_l1 + .checked_add(phys_off) + .ok_or(Kernel::Memory(Memory::OutOfMemory))?; + unsafe { + core::ptr::write_bytes(new_l1_vaddr as *mut u8, 0, 4096); + } + let mut flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; + if user_accessible { + flags |= PageTableFlags::USER_ACCESSIBLE; + } + l2e.set_addr(PhysAddr::new(new_l1), flags); + new_l1 + } else { + if user_accessible && !l2e.flags().contains(PageTableFlags::USER_ACCESSIBLE) { + l2e.set_addr(l2e.addr(), l2e.flags() | PageTableFlags::USER_ACCESSIBLE); + } + l2e.addr().as_u64() + }; + + let l1_vaddr = l1_phys + .checked_add(phys_off) + .ok_or(Kernel::Memory(Memory::OutOfMemory))?; + let l1 = unsafe { &mut *(l1_vaddr as *mut PageTable) }; + let l1i = vaddr.p1_index(); + let l1e = &mut l1[l1i]; + + // 最終マッピング + let mut flags = PageTableFlags::PRESENT; + if writable { + flags |= PageTableFlags::WRITABLE; + } + if user_accessible { + flags |= PageTableFlags::USER_ACCESSIBLE; + } + l1e.set_addr(PhysAddr::new(phys_addr), flags); + + Ok(()) +} + +/// 指定したページテーブルから仮想ページをアンマップする +/// +/// # Arguments +/// * `table_phys` - ページテーブルの物理アドレス +/// * `virt_addr` - アンマップする仮想アドレス(ページアラインド済み) +/// +/// # Returns +/// 成功時: Ok(()) +/// エラー時: Err(Kernel::Memory(Memory::*) +pub fn unmap_page_in_table(table_phys: u64, virt_addr: u64) -> Result<()> { + if (table_phys & 0xfff) != 0 { + return Err(Kernel::Memory(Memory::AlignmentError)); + } + if (virt_addr & 0xfff) != 0 { + return Err(Kernel::Memory(Memory::AlignmentError)); + } + + let phys_off = physical_memory_offset().ok_or(Kernel::Memory(Memory::NotMapped))?; + let l4_vaddr = table_phys + .checked_add(phys_off) + .ok_or(Kernel::Memory(Memory::OutOfMemory))?; + let l4 = unsafe { &mut *(l4_vaddr as *mut PageTable) }; + + let vaddr = VirtAddr::new(virt_addr); + let l4i = vaddr.p4_index(); + let l4e = &mut l4[l4i]; + + if l4e.is_unused() || !l4e.flags().contains(PageTableFlags::PRESENT) { + return Ok(()); // すでにアンマップ済み + } + + let l3_phys = l4e.addr().as_u64(); + let l3_vaddr = l3_phys + .checked_add(phys_off) + .ok_or(Kernel::Memory(Memory::OutOfMemory))?; + let l3 = unsafe { &mut *(l3_vaddr as *mut PageTable) }; + let l3i = vaddr.p3_index(); + let l3e = &mut l3[l3i]; + + if l3e.is_unused() || !l3e.flags().contains(PageTableFlags::PRESENT) { + return Ok(()); + } + + let l2_phys = l3e.addr().as_u64(); + let l2_vaddr = l2_phys + .checked_add(phys_off) + .ok_or(Kernel::Memory(Memory::OutOfMemory))?; + let l2 = unsafe { &mut *(l2_vaddr as *mut PageTable) }; + let l2i = vaddr.p2_index(); + let l2e = &mut l2[l2i]; + + if l2e.is_unused() || !l2e.flags().contains(PageTableFlags::PRESENT) { + return Ok(()); + } + + let l1_phys = l2e.addr().as_u64(); + let l1_vaddr = l1_phys + .checked_add(phys_off) + .ok_or(Kernel::Memory(Memory::OutOfMemory))?; + let l1 = unsafe { &mut *(l1_vaddr as *mut PageTable) }; + let l1i = vaddr.p1_index(); + let l1e = &mut l1[l1i]; + + // エントリをクリア + l1e.set_unused(); + + // TLBフラッシュ(現在のCR3がtable_physの場合のみ) + let (current_cr3, _) = Cr3::read(); + if current_cr3.start_address().as_u64() == table_phys { + unsafe { + core::arch::asm!("invlpg [{}]", in(reg) virt_addr, options(nostack, preserves_flags)); + } + } + + Ok(()) +} diff --git a/src/core/mem/tss.rs b/src/core/mem/tss.rs new file mode 100644 index 0000000..0a6d76b --- /dev/null +++ b/src/core/mem/tss.rs @@ -0,0 +1,84 @@ +//! TSS管理モジュール +//! +//! TSSを管理 + +use crate::info; +use spin::Once; +use x86_64::structures::tss::TaskStateSegment; +use x86_64::VirtAddr; + +/// ダブルフォルト用ISTインデックス +pub const DOUBLE_FAULT_IST_INDEX: u16 = 0; + +static TSS: Once = Once::new(); + +/// TSSを初期化して返す +/// +/// ## Returns +/// - 初期化されたTSSへの参照 +#[allow(unused_unsafe)] +pub fn init() -> &'static TaskStateSegment { + info!("Initializing TSS..."); + + TSS.call_once(|| { + let mut tss = TaskStateSegment::new(); + + // ダブルフォルト用の専用スタックを設定 + tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX as usize] = { + const STACK_SIZE: usize = 4096 * 16; // 64KB (増量: 20KB→64KB) + static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE]; + + let stack_start = VirtAddr::from_ptr(unsafe { &raw const STACK }); + let stack_end = stack_start + STACK_SIZE as u64; + info!( + " IST[{}] stack: {:#x}", + DOUBLE_FAULT_IST_INDEX, + stack_end.as_u64() + ); + stack_end + }; + + // ユーザーモードからカーネルモードへの遷移用のRing0スタックを設定 + tss.privilege_stack_table[0] = { + const RING0_STACK_SIZE: usize = 4096 * 32; // 128KB (増量: 16KB→128KB) + static mut RING0_STACK: [u8; RING0_STACK_SIZE] = [0; RING0_STACK_SIZE]; + + let stack_start = VirtAddr::from_ptr(unsafe { &raw const RING0_STACK }); + let stack_end = stack_start + RING0_STACK_SIZE as u64; + info!(" Ring0 stack (RSP0): {:#x}", stack_end.as_u64()); + stack_end + }; + + info!("TSS configured:"); + info!( + " IST[0] stack: {:#x}", + tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX as usize].as_u64() + ); + info!( + " Ring0 stack (RSP0): {:#x}", + tss.privilege_stack_table[0].as_u64() + ); + tss + }) +} + +/// Ring 0スタック (RSP0) を更新 +/// +/// コンテキストスイッチ時に呼び出し、次のスレッドのカーネルスタックを設定する +/// +/// ## Arguments +/// - `rsp`: 新しいRSP0の値 (次のスレッドのカーネルスタックのアドレス) +pub fn set_rsp0(rsp: u64) { + if let Some(tss) = TSS.get() { + // TSSは参照として取得されるが、RSP0は実行時に変更する必要があるため、 + // 内部可変性を持つか、ポインタ経由で変更する + let ptr = tss as *const TaskStateSegment as *mut TaskStateSegment; + unsafe { + // RSP0更新中の割り込み/コンテキストスイッチを防ぐため、 + // 割り込みを一時的に無効化してアトミックに更新 + x86_64::instructions::interrupts::without_interrupts(|| { + (*ptr).privilege_stack_table[0] = VirtAddr::new(rsp); + }); + } + } +} diff --git a/src/core/mem/user.rs b/src/core/mem/user.rs new file mode 100644 index 0000000..c0318bc --- /dev/null +++ b/src/core/mem/user.rs @@ -0,0 +1,109 @@ +//! ユーザー空間メモリ管理 + +use spin::Mutex; +use x86_64::structures::paging::{Page, PageTableFlags, Size4KiB}; +use x86_64::VirtAddr; + +use crate::mem::{frame, paging}; +use crate::result::{Kernel, Memory, Result}; + +const PAGE_SIZE: u64 = 4096; +const USER_STACK_TOP: u64 = 0x0000_8000_0000; // 2GB (2^31) +const USER_STACK_GUARD_PAGES: u64 = 1; +const USER_SPACE_END: u64 = 0x0000_7FFF_FFFF_FFFF; + +static NEXT_STACK_TOP: Mutex = Mutex::new(USER_STACK_TOP); + +pub struct UserStack { + pub bottom: u64, + pub top: u64, +} + +fn current_process_user_page_table() -> Result { + let pid = crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |thread| thread.process_id())) + .ok_or(Kernel::Memory(Memory::NotMapped))?; + crate::task::with_process(pid, |proc| proc.page_table()) + .flatten() + .ok_or(Kernel::Memory(Memory::NotMapped)) +} + +/// 指定したユーザーページテーブル上に任意のユーザ空間レンジをマップ +pub fn map_user_range_in_table( + table_phys: u64, + start: u64, + size: u64, + flags: PageTableFlags, +) -> Result<()> { + if size == 0 { + return Ok(()); + } + let size_minus_one = size + .checked_sub(1) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + let end = start + .checked_add(size_minus_one) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + if start == 0 || start > USER_SPACE_END || end > USER_SPACE_END { + return Err(Kernel::Memory(Memory::InvalidAddress)); + } + if !flags.contains(PageTableFlags::USER_ACCESSIBLE) || !flags.contains(PageTableFlags::PRESENT) + { + return Err(Kernel::Memory(Memory::PermissionDenied)); + } + + paging::map_and_copy_segment_to( + table_phys, + start, + 0, + size, + &[], + flags.contains(PageTableFlags::WRITABLE), + !flags.contains(PageTableFlags::NO_EXECUTE), + ) +} + +/// 任意のユーザ空間レンジをマップ +pub fn map_user_range(start: u64, size: u64, flags: PageTableFlags) -> Result<()> { + let table_phys = current_process_user_page_table()?; + map_user_range_in_table(table_phys, start, size, flags) +} + +/// ユーザスタックを確保 +pub fn alloc_user_stack(pages: u64) -> Result { + let table_phys = current_process_user_page_table()?; + alloc_user_stack_in_table(table_phys, pages) +} + +/// 指定したユーザーページテーブル上にユーザスタックを確保 +pub fn alloc_user_stack_in_table(table_phys: u64, pages: u64) -> Result { + if pages == 0 { + return Err(Kernel::Memory(Memory::InvalidAddress)); + } + + let mut top = NEXT_STACK_TOP.lock(); + + let stack_size = pages * PAGE_SIZE; + let total = stack_size + USER_STACK_GUARD_PAGES * PAGE_SIZE; + + let new_top = top + .checked_sub(total) + .ok_or(Kernel::Memory(Memory::OutOfMemory))?; + + let stack_bottom = new_top + USER_STACK_GUARD_PAGES * PAGE_SIZE; + let stack_top = new_top + total; + + let flags = PageTableFlags::PRESENT + | PageTableFlags::WRITABLE + | PageTableFlags::USER_ACCESSIBLE + | PageTableFlags::NO_EXECUTE; + + map_user_range_in_table(table_phys, stack_bottom, stack_size, flags)?; + + *top = new_top; + + Ok(UserStack { + bottom: stack_bottom, + top: stack_top, + }) +} diff --git a/src/panic.rs b/src/core/panic.rs similarity index 64% rename from src/panic.rs rename to src/core/panic.rs index a4a1256..da48e31 100644 --- a/src/panic.rs +++ b/src/core/panic.rs @@ -3,7 +3,7 @@ //! カーネルパニック時の処理 //! 通常のパニックは最終手段として使用し、可能な限りResult型でエラー処理を行うこと -use crate::{error, warn}; +use crate::{result, warn}; /// エラーコンテキスト(パニック時に使用) pub struct ErrorContext { @@ -20,7 +20,7 @@ pub struct ErrorContext { #[allow(deprecated)] #[panic_handler] fn panic(info: &core::panic::PanicInfo) -> ! { - error!("!!! KERNEL PANIC !!!"); + crate::info!("!!! KERNEL PANIC !!!"); if let Some(loc) = info.location() { warn!("Location: {}:{}:{}", loc.file(), loc.line(), loc.column()); @@ -32,7 +32,7 @@ fn panic(info: &core::panic::PanicInfo) -> ! { warn!("Message: {}", s); } - warn!("System halted. Please reset."); + warn!("system halted. Please reset."); // 割り込みを無効化 #[cfg(target_arch = "x86_64")] @@ -55,9 +55,29 @@ fn panic(info: &core::panic::PanicInfo) -> ! { #[macro_export] macro_rules! kernel_panic { ($msg:expr) => { - panic!($msg) + { + $crate::warn!("[KERNEL PANIC] {}", $msg); + #[cfg(target_arch = "x86_64")] + unsafe { + x86_64::instructions::interrupts::disable(); + } + loop { + #[cfg(target_arch = "x86_64")] + unsafe { core::arch::asm!("hlt"); } + } + } }; ($fmt:expr, $($arg:tt)*) => { - panic!($fmt, $($arg)*) + { + $crate::warn!($fmt, $($arg)*); + #[cfg(target_arch = "x86_64")] + unsafe { + x86_64::instructions::interrupts::disable(); + } + loop { + #[cfg(target_arch = "x86_64")] + unsafe { core::arch::asm!("hlt"); } + } + } }; } diff --git a/src/core/percpu.rs b/src/core/percpu.rs new file mode 100644 index 0000000..80c8d09 --- /dev/null +++ b/src/core/percpu.rs @@ -0,0 +1,148 @@ +//! per-CPU 状態管理(SMP拡張の基盤) +//! +//! APIC ID をキーに CPU ローカルスロットを選択する。 + +use core::arch::asm; +use core::sync::atomic::{AtomicU64, Ordering}; +use x86_64::registers::control::Cr3; + +const MAX_CPUS: usize = 64; +const IA32_KERNEL_GS_BASE: u32 = 0xC000_0102; +pub const GS_SYSCALL_KERNEL_RSP_OFFSET: usize = 8; +pub const GS_SYSCALL_USER_RSP_TMP_OFFSET: usize = 24; + +#[repr(C)] +struct PerCpuState { + kernel_cr3: AtomicU64, + syscall_kernel_rsp: AtomicU64, + current_thread_id: AtomicU64, + syscall_user_rsp_tmp: AtomicU64, +} + +impl PerCpuState { + const fn new() -> Self { + Self { + kernel_cr3: AtomicU64::new(0), + syscall_kernel_rsp: AtomicU64::new(0), + current_thread_id: AtomicU64::new(0), + syscall_user_rsp_tmp: AtomicU64::new(0), + } + } +} + +static CPU_STATES: [PerCpuState; MAX_CPUS] = [const { PerCpuState::new() }; MAX_CPUS]; + +#[inline] +fn state_for_current_cpu() -> &'static PerCpuState { + &CPU_STATES[current_cpu_id()] +} + +#[inline(never)] +fn halt_unsupported_cpu(_apic_id: u32) -> ! { + x86_64::instructions::interrupts::disable(); + loop { + x86_64::instructions::hlt(); + } +} + +#[inline] +unsafe fn write_kernel_gs_base(base: u64) { + let lo = base as u32; + let hi = (base >> 32) as u32; + asm!( + "wrmsr", + in("ecx") IA32_KERNEL_GS_BASE, + in("eax") lo, + in("edx") hi, + options(nomem, nostack) + ); +} + +#[inline] +pub fn current_cpu_id() -> usize { + let apic_id = local_apic_id() as usize; + if apic_id < MAX_CPUS { + apic_id + } else { + halt_unsupported_cpu(apic_id as u32) + } +} + +#[inline] +fn local_apic_id() -> u32 { + // CPUID leaf 1: EBX[31:24] = Initial APIC ID + let ebx: u64; + unsafe { + asm!( + "xchg {tmp}, rbx", + "cpuid", + "xchg {tmp}, rbx", + inout("eax") 1u32 => _, + in("ecx") 0u32, + tmp = inout(reg) 0u64 => ebx, + out("edx") _, + options(nomem, nostack) + ); + } + ((ebx as u32) >> 24) & 0xff +} + +pub fn init_boot_cpu(syscall_kernel_rsp: u64) { + let apic_id = local_apic_id() as usize; + let slot = if apic_id < MAX_CPUS { + apic_id + } else { + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "boot CPU APIC ID exceeded per-cpu table; falling back to CPU0 slot", + ); + 0 + }; + + let (cr3, _) = Cr3::read(); + let state = &CPU_STATES[slot]; + state + .kernel_cr3 + .store(cr3.start_address().as_u64(), Ordering::SeqCst); + state + .syscall_kernel_rsp + .store(syscall_kernel_rsp, Ordering::SeqCst); + state.current_thread_id.store(0, Ordering::SeqCst); + state.syscall_user_rsp_tmp.store(0, Ordering::SeqCst); + install_current_cpu_gs_base(); +} + +pub fn install_current_cpu_gs_base() { + let state = state_for_current_cpu() as *const PerCpuState as u64; + unsafe { + write_kernel_gs_base(state); + } +} + +pub fn kernel_cr3() -> u64 { + state_for_current_cpu().kernel_cr3.load(Ordering::SeqCst) +} + +pub fn set_syscall_kernel_rsp(rsp: u64) { + state_for_current_cpu() + .syscall_kernel_rsp + .store(rsp, Ordering::SeqCst); +} + +pub fn syscall_kernel_rsp() -> u64 { + state_for_current_cpu() + .syscall_kernel_rsp + .load(Ordering::SeqCst) +} + +pub fn current_thread_raw_id() -> u64 { + state_for_current_cpu() + .current_thread_id + .load(Ordering::SeqCst) +} + +pub fn set_current_thread_raw_id(id: u64) { + state_for_current_cpu() + .current_thread_id + .store(id, Ordering::SeqCst); +} diff --git a/src/core/result.rs b/src/core/result.rs new file mode 100644 index 0000000..6a3dae9 --- /dev/null +++ b/src/core/result.rs @@ -0,0 +1,206 @@ +//! mochiOSのResultとエラー型を定義 +//! +//! すべてのカーネルエラーをResult型で表現し、panicを禁止 + +use core::fmt; + +/// トップレベル +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Kernel { + /// メモリエラー + Memory(Memory), + /// プロセスエラー + Process(Process), + /// デバイスエラー + Device(Device), + /// ELFエラー + Elf(Elf), + /// 無効なパラメータ + InvalidParam, + /// 未実装の機能 + NotImplemented, +} + +/// メモリ関連 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Memory { + /// 利用可能なメモリがない + OutOfMemory, + /// 無効なアドレスへのアクセス + InvalidAddress, + /// メモリ保護違反 + PermissionDenied, + /// 既にマップされたアドレス + AlreadyMapped, + /// マップされていないアドレス + NotMapped, + /// アライメントエラー + AlignmentError, +} + +/// プロセス関連 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Process { + /// 無効なプロセスID + InvalidPid, + /// プロセスが見つからない + ProcessNotFound, + /// ゾンビプロセス + ZombieProcess, + /// プロセス数の上限に達した + MaxProcessesReached, + /// 権限不足 + InsufficientPrivilege, + /// プロセス間通信エラー + IpcError, + /// タイムアウト + Timeout, + /// 暴走プロセス検出 + RogueProcessDetected, + /// サービス関連 + Service(Service), + /// プロセス作成完了 + CreationOk, +} + +/// サービス関連 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Service { + /// サービスが見つからない + NotFound, + /// サービスの起動失敗 + StartFailure, + /// サービスの停止失敗 + StopFailure, + /// サービスの応答なし + NoResponse, + /// サービスの権限不足 + InsufficientPrivilege, + /// サービスの不正な状態 + InvalidState, + /// サービスの競合 + Conflict, + /// 未登録のサービス + Unregistered, +} + +/// ELF関連 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Elf { + /// 無効なELFフォーマット + InvalidFormat, + /// サポートされていないELFタイプ + UnsupportedType, + /// セグメントのロード失敗 + SegmentLoadFailure, + /// シンボル解決失敗 + SymbolResolutionFailure, + /// Elfファイルの長さ不足 + InsufficientLength, +} + +/// デバイス関連 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Device { + /// デバイスがビジー状態 + Busy, + /// ハードウェアエラー + HardwareFailure, + /// タイムアウト + Timeout, + /// 不正な操作 + InvalidOperation, + /// デバイスが見つからない + DeviceNotFound, + /// ドライバのロード失敗 + DriverLoadFailure, + /// 切断されたデバイス + Disconnected, + /// サポートされていないデバイス + Unsupported, + /// 通信中に切断 + CommunicationLost, + /// リソース不足 + ResourceUnavailable, +} + +impl Kernel { + /// このエラーが致命的かどうか + /// + /// 致命的なエラーは回復不能であり、システムの継続が不可能 + /// - `Memory::OutOfMemory` + /// - `Device::HardwareFailure` + pub fn is_fatal(&self) -> bool { + matches!( + self, + Kernel::Memory(Memory::OutOfMemory) | Kernel::Device(Device::HardwareFailure) + ) + } + + /// このエラーがリトライ可能かどうか + /// + /// リトライ可能なエラーは、一時的な問題であり、再試行によって成功する可能性がある + /// - `Device::Busy` + /// - `Device::Timeout` + pub fn is_retryable(&self) -> bool { + matches!( + self, + Kernel::Device(Device::Busy) | Kernel::Device(Device::Timeout) + ) + } +} + +impl fmt::Display for Kernel { + /// エラーをフォーマット表示 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Kernel::Memory(e) => write!(f, "Memory error: {:?}", e), + Kernel::Process(e) => write!(f, "Process error: {:?}", e), + Kernel::Device(e) => write!(f, "Device error: {:?}", e), + Kernel::Elf(e) => write!(f, "ELF error: {:?}", e), + Kernel::InvalidParam => write!(f, "Invalid parameter"), + Kernel::NotImplemented => write!(f, "Not implemented"), + } + } +} + +impl fmt::Display for Memory { + /// エラーをフォーマット表示 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Memory::OutOfMemory => write!(f, "Out of memory"), + Memory::InvalidAddress => write!(f, "Invalid address"), + Memory::PermissionDenied => write!(f, "Permission denied"), + Memory::AlreadyMapped => write!(f, "Already mapped"), + Memory::NotMapped => write!(f, "Not mapped"), + Memory::AlignmentError => write!(f, "Alignment error"), + } + } +} + +/// カーネルエラーを処理 +pub fn handle_kernel_error(error: Kernel) { + crate::warn!("KERNEL ERROR: {}", error); + crate::debug!("Is fatal: {}", error.is_fatal()); + crate::debug!("Is retryable: {}", error.is_retryable()); + + match error { + Kernel::Memory(mem_err) => { + crate::warn!("Memory error: {:?}", mem_err); + } + Kernel::Process(proc_err) => { + crate::warn!("Process error: {:?}", proc_err); + } + Kernel::Device(dev_err) => { + crate::warn!("Device error: {:?}", dev_err); + } + _ => { + crate::warn!("Unknown error: {:?}", error); + } + } + + crate::info!("system halted."); +} + +/// 結果型のエイリアス +pub type Result = core::result::Result; diff --git a/src/core/syscall/console.rs b/src/core/syscall/console.rs new file mode 100644 index 0000000..de48c81 --- /dev/null +++ b/src/core/syscall/console.rs @@ -0,0 +1,29 @@ +use crate::{ + syscall::{EFAULT, EINVAL}, + util, +}; +use alloc::vec::Vec; + +/// コンソール書き込み (buf_ptr, len) +pub fn write(buf_ptr: u64, len: u64) -> u64 { + if buf_ptr == 0 { + return EINVAL; + } + let len = len as usize; + if len == 0 { + return 0; + } + + let mut copied = Vec::with_capacity(len); + copied.resize(len, 0); + if let Err(err) = crate::syscall::copy_from_user(buf_ptr, &mut copied) { + return err; + } + let text = match core::str::from_utf8(&copied) { + Ok(s) => s, + Err(_) => return EINVAL, + }; + util::console::print(format_args!("{}", text)); + util::vga::print(format_args!("{}", text)); + len as u64 +} diff --git a/src/core/syscall/exec.rs b/src/core/syscall/exec.rs new file mode 100644 index 0000000..d6a45cb --- /dev/null +++ b/src/core/syscall/exec.rs @@ -0,0 +1,1527 @@ +use crate::elf::loader as elf_loader; +use alloc::string::String; +use alloc::string::ToString; +use alloc::vec; +use alloc::vec::Vec; +use core::convert::TryInto; +use core::sync::atomic::{AtomicU64, Ordering}; +use x86_64::structures::paging::Mapper; + +/// `.service` 実行を許可するサービスマネージャープロセスID +/// 0 は未登録。 +static SERVICE_MANAGER_PID: AtomicU64 = AtomicU64::new(0); +const EM_X86_64: u16 = 0x3E; +static EXEC_ASLR_COUNTER: AtomicU64 = AtomicU64::new(0); +const STACK_TOP_BASE: u64 = 0x0000_7FFF_FFF0_0000; +const STACK_ASLR_MAX_PAGES: u64 = 4096; // 16MiB +const USER_STACK_SIZE_PAGES: usize = 32; // 128KiB stack +const TLS_BASE_MIN: u64 = 0x3000_0000; +const TLS_ASLR_MAX_PAGES: u64 = 0x4000; // 64MiB +const INITIAL_TLS_SIZE: u64 = 4096; + +struct InitialUserStack { + stack_base_vaddr: u64, + stack_end_vaddr: u64, + initial_rsp: u64, + page_data: Vec, +} + +struct UserPageTableGuard(Option); + +impl UserPageTableGuard { + fn new(table_phys: u64) -> Self { + Self(Some(table_phys)) + } + + fn disarm(&mut self) { + self.0.take(); + } +} + +impl Drop for UserPageTableGuard { + fn drop(&mut self) { + if let Some(table_phys) = self.0.take() { + let _ = crate::mem::paging::destroy_user_page_table(table_phys); + } + } +} + +/// サービスマネージャーPIDを登録する(IDベース認可) +pub fn register_service_manager_pid(pid: u64) { + SERVICE_MANAGER_PID.store(pid, Ordering::SeqCst); +} + +#[inline] +fn aslr_mix64(mut x: u64) -> u64 { + x ^= x >> 30; + x = x.wrapping_mul(0xbf58_476d_1ce4_e5b9); + x ^= x >> 27; + x = x.wrapping_mul(0x94d0_49bb_1331_11eb); + x ^ (x >> 31) +} + +fn next_aslr_seed(tag: &str) -> u64 { + let mut hash = 0xcbf2_9ce4_8422_2325u64; + for b in tag.as_bytes() { + hash ^= *b as u64; + hash = hash.wrapping_mul(0x100_0000_01b3); + } + if EXEC_ASLR_COUNTER.load(Ordering::Relaxed) == 0 { + let mut init = crate::cpu::boot_entropy_u64() ^ 0x7c4a_7f73_d3e1_9b1d; + if init == 0 { + init = 1; + } + let _ = EXEC_ASLR_COUNTER.compare_exchange(0, init, Ordering::SeqCst, Ordering::Relaxed); + } + let ctr = EXEC_ASLR_COUNTER.fetch_add(0x9e37_79b9_7f4a_7c15, Ordering::Relaxed); + let ticks = crate::interrupt::timer::get_ticks(); + let tid = crate::task::current_thread_id() + .map(|t| t.as_u64()) + .unwrap_or(0); + let hw = crate::cpu::hw_random_u64().unwrap_or(0); + let boot = crate::cpu::boot_entropy_u64(); + aslr_mix64(hash ^ ctr ^ ticks.rotate_left(17) ^ tid.rotate_left(7) ^ hw.rotate_left(29) ^ boot) +} + +#[inline] +fn aslr_offset_pages(seed: u64, max_pages: u64) -> u64 { + if max_pages == 0 { + 0 + } else { + aslr_mix64(seed) % max_pages + } +} + +fn caller_can_launch_service() -> bool { + let caller = crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id())); + let Some(caller_pid) = caller else { + // カーネルコンテキストからの起動は許可 + return true; + }; + + if crate::task::with_process(caller_pid, |p| { + p.privilege() == crate::task::PrivilegeLevel::Core + }) + .unwrap_or(false) + { + return true; + } + + let manager_pid_raw = SERVICE_MANAGER_PID.load(Ordering::SeqCst); + if manager_pid_raw == 0 || caller_pid.as_u64() != manager_pid_raw { + return false; + } + let manager_pid = crate::task::ProcessId::from_u64(manager_pid_raw); + crate::task::with_process(manager_pid, |p| { + let state = p.state(); + let alive = state != crate::task::ProcessState::Zombie + && state != crate::task::ProcessState::Terminated; + let privileged = matches!( + p.privilege(), + crate::task::PrivilegeLevel::Service | crate::task::PrivilegeLevel::Core + ); + alive && privileged + }) + .unwrap_or(false) +} + +fn caller_is_service_or_core() -> bool { + crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id())) + .and_then(|pid| crate::task::with_process(pid, |p| p.privilege())) + .is_some_and(|lvl| { + matches!( + lvl, + crate::task::PrivilegeLevel::Core | crate::task::PrivilegeLevel::Service + ) + }) +} + +fn read_nul_args_from_user( + args_ptr: u64, + max_total_bytes: usize, + max_args: usize, +) -> Result, u64> { + use crate::syscall::types::EINVAL; + + if args_ptr == 0 { + return Ok(Vec::new()); + } + let mut storage = alloc::vec![0u8; max_total_bytes]; + crate::syscall::copy_from_user(args_ptr, &mut storage)?; + if let Some(end) = storage.windows(2).position(|w| w == [0, 0]) { + storage.truncate(end + 2); + } + + let mut out = Vec::new(); + for s in storage.split(|&b| b == 0) { + if s.is_empty() { + continue; + } + let text = core::str::from_utf8(s).map_err(|_| EINVAL)?; + out.push(String::from(text)); + if out.len() >= max_args { + break; + } + } + Ok(out) +} + +/// カーネル内から実行可能ファイルを読み込み実行するシステムコール +/// args_ptr: ヌル区切り引数文字列へのポインタ("arg1\0arg2\0\0"形式)、0 なら引数なし +pub fn exec_kernel(path_ptr: u64, args_ptr: u64) -> u64 { + let mut provided_path: Option = None; + if path_ptr != 0 { + let path = match crate::syscall::read_user_cstring(path_ptr, 256) { + Ok(s) => s, + Err(_) => return crate::syscall::types::EINVAL, + }; + provided_path = Some(path); + } + let path = provided_path.as_deref().unwrap_or("/hello.bin"); + + // ユーザー空間からはサービス(.serviceで終わる名前)を起動できない + if path.ends_with(".service") && !caller_can_launch_service() { + return crate::syscall::types::EPERM; + } + + let extra_args_owned = match read_nul_args_from_user(args_ptr, 512, 64) { + Ok(v) => v, + Err(e) => return e, + }; + let extra_args: Vec<&str> = extra_args_owned.iter().map(|s| s.as_str()).collect(); + exec_internal(path, None, &extra_args) +} + +/// 名前を指定してカーネル内から実行可能ファイルを実行する(カーネル内部用) +pub fn exec_kernel_with_name(path: &str, name: &str) -> u64 { + exec_internal(path, Some(name), &[]) +} + +fn exec_internal(path: &str, name_override: Option<&str>, args: &[&str]) -> u64 { + let mut process_name = name_override + .map(|s| s.to_string()) + .unwrap_or_else(|| path.rsplit('/').next().unwrap_or(path).to_string()); + // Special-case mapping: drivers/net.elf should be exposed as "netdrv" for compatibility + if process_name == "net" + || process_name == "net.elf" + || path.ends_with("/bin/drivers/net.elf") + { + process_name = "netdrv".to_string(); + } + if let Some(data) = crate::init::fs::read(path) { + exec_with_data(&data, &process_name, path, args, None) + } else if let Some(data) = crate::kmod::fs::read_all(path) { + exec_with_data(&data, &process_name, path, args, None) + } else { + crate::warn!("exec: file not found: {}", path); + crate::syscall::types::ENOENT + } +} + +/// Exec by streaming image with zero-copy frame transfer when possible. +pub fn exec_from_fs_stream(path_ptr: u64, args_ptr: u64) -> u64 { + let path = match crate::syscall::read_user_cstring(path_ptr, 256) { + Ok(s) => s, + Err(_) => return crate::syscall::types::EINVAL, + }; + + if path.ends_with(".service") && !caller_can_launch_service() { + return crate::syscall::types::EPERM; + } + + let extra_args_owned = match read_nul_args_from_user(args_ptr, 512, 64) { + Ok(v) => v, + Err(e) => return e, + }; + let extra_args: Vec<&str> = extra_args_owned.iter().map(|s| s.as_str()).collect(); + + // First try to obtain the image via FS service (streaming). If that fails, fall back to kmod::fs. + match crate::syscall::fs::exec_image_via_fs(&path) { + Ok(data) => return exec_with_data(&data, &path, &path, &extra_args, None), + Err(_) => { + // fallthrough to kmod fallback + } + } + + if let Some(data) = crate::kmod::fs::read_all(&path) { + return exec_with_data(&data, &path, &path, &extra_args, None); + } + + crate::syscall::types::ENOENT +} + +#[inline] +fn resolve_exec_privilege(process_name: &str, exec_path: &str) -> crate::task::PrivilegeLevel { + // .service は従来通り Service 権限で実行。 + // bin/drivers 配下は Service/Core 呼び出し元からの起動時に Service 権限を付与する。 + // Kagami / ViewKit / Binder / Dock はデスクトップ描画のため Service 権限を付与する。 + let is_driver_path = + exec_path.starts_with("bin/drivers/") || exec_path.starts_with("/bin/drivers/"); + let is_kagami_viewkit_path = matches!( + exec_path, + "/applications/Kagami.app/entry.elf" + | "/applications/ViewKit.app/entry.elf" + | "/applications/Binder.app/entry.elf" + | "/applications/Dock.app/entry.elf" + | "applications/Kagami.app/entry.elf" + | "applications/ViewKit.app/entry.elf" + | "applications/Binder.app/entry.elf" + | "applications/Dock.app/entry.elf" + ); + if process_name.ends_with(".service") + || is_kagami_viewkit_path + || (is_driver_path && caller_is_service_or_core()) + { + crate::task::PrivilegeLevel::Service + } else { + crate::task::PrivilegeLevel::User + } +} + +fn map_initial_tls(table_phys: u64, aslr_seed: u64) -> Result { + let tls_base = TLS_BASE_MIN + .saturating_add(aslr_offset_pages(aslr_seed ^ 0x19d7_3c6a, TLS_ASLR_MAX_PAGES) * 4096); + let mut tls_data = vec![0u8; INITIAL_TLS_SIZE as usize]; + tls_data[..8].copy_from_slice(&tls_base.to_ne_bytes()); + match crate::mem::paging::map_and_copy_segment_to( + table_phys, + tls_base, + INITIAL_TLS_SIZE, + INITIAL_TLS_SIZE, + &tls_data, + true, + false, + ) { + Ok(()) => Ok(tls_base), + Err(e) => { + crate::warn!( + "Failed to map initial TLS block at {:#x}: {:?}", + tls_base, + e + ); + Err(crate::syscall::types::EINVAL) + } + } +} + +#[inline(never)] +fn build_initial_user_stack( + aslr_seed: u64, + argv: &[&str], + envp: &[&str], + execfn: &str, + auxv_entries: &[(u64, u64)], +) -> Result { + let stack_end_vaddr = STACK_TOP_BASE + .saturating_sub(aslr_offset_pages(aslr_seed ^ 0x53a9_1e2d, STACK_ASLR_MAX_PAGES) * 4096); + let stack_base_vaddr = stack_end_vaddr - (USER_STACK_SIZE_PAGES as u64 * 4096); + + let mut string_block = Vec::new(); + let mut argv_offsets = Vec::new(); + for arg in argv { + argv_offsets.push(string_block.len()); + string_block.extend_from_slice(arg.as_bytes()); + string_block.push(0); + } + + let mut envp_offsets = Vec::new(); + for env in envp { + envp_offsets.push(string_block.len()); + string_block.extend_from_slice(env.as_bytes()); + string_block.push(0); + } + + let random_offset = string_block.len(); + let mut rng = aslr_seed + .wrapping_mul(6364136223846793005) + .wrapping_add(1442695040888963407); + for _ in 0..16 { + rng = rng + .wrapping_mul(6364136223846793005) + .wrapping_add(1442695040888963407); + string_block.push((rng >> 33) as u8); + } + + let execfn_offset = string_block.len(); + string_block.extend_from_slice(execfn.as_bytes()); + string_block.push(0); + + let string_area_len = string_block.len(); + let pointers_bytes = + 8 + (argv.len() * 8) + 8 + (envp.len() * 8) + 8 + (auxv_entries.len() * 16); + let total_data_needed = string_area_len + pointers_bytes; + let padding_len = (16 - (total_data_needed % 16)) % 16; + let total_size = total_data_needed + padding_len; + + if total_size > 4096 { + crate::warn!("Arguments too large for single page stack setup"); + return Err(crate::syscall::types::EINVAL); + } + + let string_area_base = stack_end_vaddr - string_area_len as u64; + let random_addr = string_area_base + random_offset as u64; + let execfn_addr = string_area_base + execfn_offset as u64; + let initial_rsp = stack_end_vaddr - total_size as u64; + + let mut page_data = Vec::new(); + let page_offset = total_size % 4096; + let unused_space = if page_offset == 0 { + 0 + } else { + 4096 - page_offset + }; + page_data.resize(unused_space, 0); + + page_data.extend_from_slice(&(argv.len() as u64).to_ne_bytes()); + for off in argv_offsets { + let ptr = string_area_base + off as u64; + page_data.extend_from_slice(&ptr.to_ne_bytes()); + } + page_data.extend_from_slice(&0u64.to_ne_bytes()); + + for off in envp_offsets { + let ptr = string_area_base + off as u64; + page_data.extend_from_slice(&ptr.to_ne_bytes()); + } + page_data.extend_from_slice(&0u64.to_ne_bytes()); + + for (key, value) in auxv_entries { + let resolved_value = match *key { + 25 => random_addr, // AT_RANDOM + 31 => execfn_addr, // AT_EXECFN + _ => *value, + }; + page_data.extend_from_slice(&key.to_ne_bytes()); + page_data.extend_from_slice(&resolved_value.to_ne_bytes()); + } + + page_data.resize(page_data.len() + padding_len, 0); + page_data.extend_from_slice(&string_block); + + if page_data.len() != 4096 { + crate::warn!("internal: page_data.len() != 4096: {}", page_data.len()); + return Err(crate::syscall::types::EINVAL); + } + + Ok(InitialUserStack { + stack_base_vaddr, + stack_end_vaddr, + initial_rsp, + page_data, + }) +} + +/// メモリ上の ELF バッファからプロセスを生成する(内部共通実装) +fn delegated_parent_pid() -> Option { + None +} + +fn exec_with_data( + data: &[u8], + process_name: &str, + exec_path: &str, + args: &[&str], + parent_override: Option, +) -> u64 { + crate::debug!("exec: name={}", process_name); + let aslr_seed = next_aslr_seed(process_name); + + { + let data: &[u8] = data; + // Disable SMAP/SMEP for the duration of the exec mapping operations. + // Many helper functions perform direct physical/HHDM accesses; re-enabling + // SMAP/SMEP during execution can cause page faults when touching user + // page tables. Hold the guard for the whole scope so it's restored on drop. + let _smap_guard = crate::cpu::SmapSmepGuard::new(); + + // MED-27修正: エントリポイントが0の場合はELFが無効として拒否する + // 以前はentry=0のままプロセスを作成し、仮想アドレス0にジャンプしていた + let mut entry = match elf_loader::entry_point(data) { + Some(e) if e != 0 => e, + _ => { + crate::warn!("exec: ELF entry point is 0 or missing, rejecting"); + return crate::syscall::types::EINVAL; + } + }; + crate::debug!("ELF entry: {:#x}", entry); + let new_pt_phys = match crate::mem::paging::create_user_page_table() { + Ok(phys) => phys, + Err(e) => { + crate::warn!( + "Failed to create user page table for {}: {:?}", + process_name, + e + ); + return crate::syscall::types::EINVAL; + } + }; + let mut new_pt_guard = UserPageTableGuard::new(new_pt_phys); + crate::debug!("Created user page table at {:#x}", new_pt_phys); + + // Note: SMAP/SMEP are already disabled globally during kernel initialization + // and are kept disabled for all exec operations. See src/core/mem/mod.rs:66 + + // ELFアーキテクチャ検証 (MED-07) + let mut phdr_vaddr: u64 = 0; + let mut phentsize: u64 = 0; + let mut phnum: u64 = 0; + if let Some(eh) = elf_loader::parse_elf_header(data) { + if eh.e_machine != EM_X86_64 { + crate::warn!("ELF e_machine {:#x} is not x86-64, rejecting", eh.e_machine); + return crate::syscall::types::EINVAL; + } + phentsize = eh.e_phentsize as u64; + phnum = eh.e_phnum as u64; + let phoff = eh.e_phoff as usize; + let phentsz = eh.e_phentsize as usize; + // phentszが0の場合は無限ループを防ぐため拒否 (MED-08) + if phentsz == 0 { + crate::warn!("ELF phentsize is 0, rejecting"); + return crate::syscall::types::EINVAL; + } + let phnum = eh.e_phnum as usize; + let mut load_base: u64 = 0; + let mut load_base_set = false; + for i in 0..phnum { + // オーバーフロー安全な乗算と加算 (MED-08) + let off_hdr = match i.checked_mul(phentsz).and_then(|x| phoff.checked_add(x)) { + Some(o) if o < data.len() => o, + _ => { + crate::warn!("ELF program header offset overflow or out of bounds"); + return crate::syscall::types::EINVAL; + } + }; + if let Some(ph) = elf_loader::parse_phdr(data, off_hdr) { + if ph.p_type == elf_loader::PT_LOAD { + let vaddr = ph.p_vaddr; + let memsz = ph.p_memsz; + let filesz = ph.p_filesz; + let src_off = ph.p_offset as usize; + let flags = ph.p_flags; + let writable = (flags & 0x2) != 0; + let executable = (flags & 0x1) != 0; + + // ELFセグメントのvaddrがユーザー空間内であることを検証 (CRIT-05) + const USER_SPACE_END: u64 = 0x0000_7FFF_FFFF_FFFF; + if vaddr >= USER_SPACE_END { + crate::warn!("ELF segment vaddr {:#x} is in kernel space", vaddr); + return crate::syscall::types::EINVAL; + } + if memsz > 0 { + match vaddr.checked_add(memsz) { + Some(e) if e <= USER_SPACE_END => {} + _ => { + crate::warn!("ELF segment vaddr+memsz overflows user space"); + return crate::syscall::types::EINVAL; + } + } + } + + // ELFセグメントの境界チェック (CRIT-04) + let src_end = match src_off.checked_add(filesz as usize) { + Some(e) if e <= data.len() => e, + _ => { + crate::warn!("ELF segment src offset+filesz out of bounds"); + return crate::syscall::types::EINVAL; + } + }; + + crate::debug!( + "Mapping seg {} -> {:#x} (filesz={}, memsz={})", + i, + vaddr, + filesz, + memsz + ); + let seg_src = &data[src_off..src_end]; + + if !load_base_set { + load_base = ph.p_vaddr.saturating_sub(ph.p_offset); + load_base_set = true; + } + + if let Err(e) = crate::mem::paging::map_and_copy_segment_to( + new_pt_phys, + vaddr, + filesz, + memsz, + seg_src, + writable, + executable, + ) { + crate::warn!("Failed to map segment at {:#x}: {:?}", vaddr, e); + crate::warn!(" new_pt_phys={:#x}, filesz={}, memsz={}, writable={}, executable={}", + new_pt_phys, filesz, memsz, writable, executable); + return crate::syscall::types::EINVAL; + } + } + } + } + + phdr_vaddr = load_base.saturating_add(eh.e_phoff); + } + + // __sinit は newlib (mochiOS サービス) 専用のシンボル。 + // 外部バイナリ (BusyBox 等) はシンボルテーブルが巨大で探索コストが高いためスキップ。 + let needs_sinit = exec_path.ends_with(".service"); + let mut sinit_addr: Option = None; + if needs_sinit { + if let Some(eh_sym) = elf_loader::parse_elf_header(data) { + let shoff = eh_sym.e_shoff as usize; + let shentsz = eh_sym.e_shentsize as usize; + let shnum = eh_sym.e_shnum as usize; + if shoff > 0 && shentsz > 0 && shnum > 0 && data.len() >= shoff + shentsz * shnum { + let mut symtab_offset: usize = 0; + let mut symtab_size: usize = 0; + let mut symtab_entsize: usize = 0; + let mut strtab_offset: usize = 0; + let mut strtab_size: usize = 0; + for si in 0..shnum { + let sh_off = shoff + si * shentsz; + if sh_off + shentsz > data.len() { + break; + } + let sh_type = match data[sh_off + 4..sh_off + 8].try_into() { + Ok(b) => u32::from_le_bytes(b), + Err(_) => { + crate::warn!("ELF section header truncated"); + return crate::syscall::types::EINVAL; + } + }; + let sh_offset = match data[sh_off + 24..sh_off + 32].try_into() { + Ok(b) => u64::from_le_bytes(b) as usize, + Err(_) => { + crate::warn!("ELF section header truncated"); + return crate::syscall::types::EINVAL; + } + }; + let sh_size = match data[sh_off + 32..sh_off + 40].try_into() { + Ok(b) => u64::from_le_bytes(b) as usize, + Err(_) => { + crate::warn!("ELF section header truncated"); + return crate::syscall::types::EINVAL; + } + }; + let sh_link = match data[sh_off + 40..sh_off + 44].try_into() { + Ok(b) => u32::from_le_bytes(b), + Err(_) => { + crate::warn!("ELF section header truncated"); + return crate::syscall::types::EINVAL; + } + }; + let sh_entsize = match data[sh_off + 56..sh_off + 64].try_into() { + Ok(b) => u64::from_le_bytes(b) as usize, + Err(_) => { + crate::warn!("ELF section header truncated"); + return crate::syscall::types::EINVAL; + } + }; + + // SHT_SYMTAB == 2 + if sh_type == 2 { + symtab_offset = sh_offset; + symtab_size = sh_size; + symtab_entsize = sh_entsize; + // linked string table + let link_idx = sh_link as usize; + if link_idx < shnum { + let link_sh_off = shoff + link_idx * shentsz; + strtab_offset = + match data[link_sh_off + 24..link_sh_off + 32].try_into() { + Ok(b) => u64::from_le_bytes(b) as usize, + Err(_) => { + crate::warn!("ELF section header truncated"); + return crate::syscall::types::EINVAL; + } + }; + strtab_size = + match data[link_sh_off + 32..link_sh_off + 40].try_into() { + Ok(b) => u64::from_le_bytes(b) as usize, + Err(_) => { + crate::warn!("ELF section header truncated"); + return crate::syscall::types::EINVAL; + } + }; + } + break; + } + } + if symtab_offset > 0 && strtab_offset > 0 && symtab_entsize > 0 { + let nsyms = symtab_size / symtab_entsize; + for i_sym in 0..nsyms { + let sym_off = symtab_offset + i_sym * symtab_entsize; + if sym_off + symtab_entsize > data.len() { + break; + } + let st_name = match data[sym_off..sym_off + 4].try_into() { + Ok(b) => u32::from_le_bytes(b) as usize, + Err(_) => { + crate::warn!("ELF symbol entry truncated"); + break; + } + }; + let st_value = match data[sym_off + 8..sym_off + 16].try_into() { + Ok(b) => u64::from_le_bytes(b), + Err(_) => { + crate::warn!("ELF symbol entry truncated"); + break; + } + }; + + if st_name < strtab_size { + let name_off = strtab_offset + st_name; + if name_off < data.len() { + let mut end = name_off; + while end < data.len() && data[end] != 0 { + end += 1; + } + if end <= data.len() { + if let Ok(name_str) = + core::str::from_utf8(&data[name_off..end]) + { + if name_str == "__sinit" { + sinit_addr = Some(st_value); + break; + } + } + } + } + } + } + } + } + } + } // needs_sinit + + let base_name = exec_path.rsplit('/').next().unwrap_or(process_name); + let argv0 = base_name.strip_suffix(".elf").unwrap_or(base_name); + let mut all_args: Vec<&str> = Vec::new(); + all_args.push(argv0); + for a in args { + all_args.push(a); + } + if process_name.ends_with("busybox.elf") { + let argv1 = all_args.get(1).copied().unwrap_or(""); + crate::info!( + "busybox argv: argc={}, argv0='{}', argv1='{}'", + all_args.len(), + argv0, + argv1 + ); + } + let envs: [&str; 0] = []; + let auxv_entries = [ + (3u64, phdr_vaddr), + (4u64, phentsize), + (5u64, phnum), + (6u64, 4096u64), + (7u64, 0u64), + (8u64, 0u64), + (9u64, entry), + (11u64, 0u64), + (12u64, 0u64), + (13u64, 0u64), + (14u64, 0u64), + (16u64, 0u64), + (17u64, 100u64), + (23u64, 0u64), + (25u64, 0u64), + (31u64, 0u64), + (0u64, 0u64), + ]; + let InitialUserStack { + stack_base_vaddr, + stack_end_vaddr, + initial_rsp, + page_data, + } = match build_initial_user_stack(aslr_seed, &all_args, &envs, exec_path, &auxv_entries) { + Ok(stack) => stack, + Err(errno) => return errno, + }; + + crate::debug!( + "Allocating user stack: base={:#x}, top={:#x}, size={} pages, rsp={:#x}", + stack_base_vaddr, + stack_end_vaddr, + USER_STACK_SIZE_PAGES, + initial_rsp + ); + + // Map the lower 7 pages as zero-filled (writable, non-executable stack) + if let Err(e) = crate::mem::paging::map_and_copy_segment_to( + new_pt_phys, + stack_base_vaddr, + 0, + (USER_STACK_SIZE_PAGES - 1) as u64 * 4096, + &[], + true, + false, + ) { + crate::warn!("Failed to allocate user stack lower: {:?}", e); + return crate::syscall::types::EINVAL; + } + // Map the top page with args (writable, non-executable stack) + let top_page_vaddr = stack_end_vaddr - 4096; + if let Err(e) = crate::mem::paging::map_and_copy_segment_to( + new_pt_phys, + top_page_vaddr, + 4096, + 4096, + &page_data, + true, + false, + ) { + crate::warn!("Failed to allocate user stack top: {:?}", e); + return crate::syscall::types::EINVAL; + } + + crate::debug!("User stack allocated successfully"); + + // Pre-map initial heap pages to avoid immediate page faults from user allocations. + // Map two pages at the default heap base so small early allocations won't fault. + const HEAP_BASE_MIN: u64 = 0x4000_0000; + const HEAP_ASLR_MAX_PAGES: u64 = 0x8000; // 128MiB + let default_heap_base = HEAP_BASE_MIN + .saturating_add(aslr_offset_pages(aslr_seed ^ 0x4a11_6b5c, HEAP_ASLR_MAX_PAGES) * 4096); + let heap_map_size: u64 = 4096 * 2; + let mut heap_pre_mapped = false; + if let Err(e) = crate::mem::paging::map_and_copy_segment_to( + new_pt_phys, + default_heap_base, + 0, + heap_map_size, + &[], + true, + false, + ) { + crate::warn!( + "Failed to pre-map initial heap pages at {:#x}: {:?}", + default_heap_base, + e + ); + } else { + crate::debug!( + "Pre-mapped {} bytes for heap at {:#x} for {}", + heap_map_size, + default_heap_base, + process_name + ); + heap_pre_mapped = true; + } + + // __sinitがあれば、スタブを作成して先に呼び出す + if let Some(sinit) = sinit_addr { + let stub_addr = match stack_end_vaddr.checked_add(4096) { + Some(v) => v, + None => return crate::syscall::types::EINVAL, + }; + crate::info!( + "Found __sinit at {:#x}, mapping init stub at {:#x}", + sinit, + stub_addr + ); + let mut stub_page = vec![0u8; 4096]; + let mut cur = 0usize; + if cur + 24 > stub_page.len() { + crate::warn!("__sinit stub size overflow: {}", cur + 24); + return crate::syscall::types::EINVAL; + } + // movabs rax, + stub_page[cur..cur + 2].copy_from_slice(&[0x48, 0xB8]); + cur += 2; + stub_page[cur..cur + 8].copy_from_slice(&sinit.to_le_bytes()); + cur += 8; + // call rax + stub_page[cur..cur + 2].copy_from_slice(&[0xFF, 0xD0]); + cur += 2; + // movabs rax, + stub_page[cur..cur + 2].copy_from_slice(&[0x48, 0xB8]); + cur += 2; + stub_page[cur..cur + 8].copy_from_slice(&entry.to_le_bytes()); + cur += 8; + // jmp rax + stub_page[cur..cur + 2].copy_from_slice(&[0xFF, 0xE0]); + cur += 2; + + if let Err(e) = crate::mem::paging::map_and_copy_segment_to( + new_pt_phys, + stub_addr, + cur as u64, + 4096, + &stub_page[0..cur], + false, + true, + ) { + crate::warn!("Failed to map __sinit stub at {:#x}: {:?}", stub_addr, e); + } else { + // jump to stub first + entry = stub_addr; + } + } + + // プロセスを作成してページテーブルをセット + let parent_pid = parent_override.or_else(|| { + crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id())) + }); + let privilege = resolve_exec_privilege(process_name, exec_path); + let mut proc = crate::task::Process::new(process_name, privilege, parent_pid, 0); + proc.set_page_table(new_pt_phys); + proc.set_stack_bottom(stack_base_vaddr); + proc.set_stack_top(stack_end_vaddr + 4096); + crate::info!( + "[STACK_INIT] {}: stack_base={:#x}, stack_end={:#x}, stack_top={:#x}", + proc.name(), + stack_base_vaddr, + stack_end_vaddr, + stack_end_vaddr + 4096 + ); + // 親プロセスの CWD を子プロセスに継承する + if let Some(ppid) = parent_pid { + let parent_cwd = crate::task::with_process(ppid, |p| { + let mut s = alloc::string::String::new(); + s.push_str(p.cwd()); + s + }); + if let Some(cwd_str) = parent_cwd { + proc.set_cwd(&cwd_str); + } + } + if heap_pre_mapped { + proc.set_heap_start(default_heap_base); + proc.set_heap_end(default_heap_base + heap_map_size); + } + let initial_fs_base = match map_initial_tls(new_pt_phys, aslr_seed) { + Ok(base) => base, + Err(errno) => return errno, + }; + let pid = proc.id(); + let is_core_service = process_name.ends_with("core.service"); + if is_core_service + && SERVICE_MANAGER_PID + .compare_exchange(0, pid.as_u64(), Ordering::SeqCst, Ordering::SeqCst) + .is_err() + { + crate::warn!("core.service is already running, rejecting duplicate launch"); + return crate::syscall::types::EINVAL; + } + if crate::task::add_process(proc).is_none() { + if is_core_service { + let _ = SERVICE_MANAGER_PID.compare_exchange( + pid.as_u64(), + 0, + Ordering::SeqCst, + Ordering::SeqCst, + ); + } + return crate::syscall::types::EINVAL; + } + // allocate kernel stack for the new thread + // unmap_guard_page() がページテーブル操作を行うため、SmapSmepGuard スコープを保持 + const KERNEL_THREAD_STACK_SIZE: usize = 4096 * 32; // 128KB + let kstack = match crate::task::thread::allocate_kernel_stack(KERNEL_THREAD_STACK_SIZE) { + Some(a) => a, + None => { + crate::warn!("Failed to allocate kernel stack for thread"); + let _ = crate::task::remove_process(pid); + if is_core_service { + let _ = SERVICE_MANAGER_PID.compare_exchange( + pid.as_u64(), + 0, + Ordering::SeqCst, + Ordering::SeqCst, + ); + } + let _ = crate::mem::paging::destroy_user_page_table(new_pt_phys); + return crate::syscall::types::ENOMEM; + } + }; + new_pt_guard.disarm(); + + // ユーザーモードスレッドを作成 + // RSP に initial_rsp を設定 + let mut thread = crate::task::Thread::new_usermode( + pid, + process_name, + entry, + initial_rsp, + kstack, + KERNEL_THREAD_STACK_SIZE, + ); + thread.set_fs_base(initial_fs_base); + + crate::info!( + "exec: loaded '{}', entry={:#x}, pid={:?}", + process_name, + entry, + pid + ); + + let add_res = crate::task::add_thread(thread); + crate::info!("exec: add_thread returned: {:?}", add_res); + if add_res.is_none() { + crate::warn!("Failed to add thread"); + let _ = crate::task::remove_process(pid); + if is_core_service { + let _ = SERVICE_MANAGER_PID.compare_exchange( + pid.as_u64(), + 0, + Ordering::SeqCst, + Ordering::SeqCst, + ); + } + let _ = crate::mem::paging::destroy_user_page_table(new_pt_phys); + return crate::syscall::types::EINVAL; + } + + // report scheduling state + crate::info!( + "exec: scheduler_enabled={} thread_count={}", + crate::task::is_scheduler_enabled(), + crate::task::thread_count() + ); + if let Some(next) = crate::task::peek_next_thread() { + crate::info!("exec: peek_next_thread -> {:?}", next); + } else { + crate::info!("exec: peek_next_thread -> None"); + } + + // log current thread and thread-state counts + crate::info!( + "exec: current_thread={:?}", + crate::task::current_thread_id() + ); + crate::info!( + "exec: ready_count={} running_count={}", + crate::task::count_threads_by_state(crate::task::ThreadState::Ready), + crate::task::count_threads_by_state(crate::task::ThreadState::Running) + ); + if let Some(tid) = add_res { + crate::info!("exec: new_thread_id={:?}", tid); + } + + // Ensure newly launched user process gets CPU promptly + if crate::task::is_scheduler_enabled() { + crate::task::yield_now(); + } + + crate::info!( + "exec: created usermode process '{}' (pid={:?}, entry={:#x})", + process_name, + pid, + entry + ); + + pid.as_u64() + } +} + +/// ユーザー空間の null 終端ポインタ配列(char**)を読み取る +/// +/// 各エントリは 64 ビットポインタ。NULL で終端。 +/// max_entries を超えた場合は切り捨てる。 +fn read_user_ptr_array(array_ptr: u64, max_entries: usize) -> Vec { + if array_ptr == 0 { + return Vec::new(); + } + let mut result = Vec::new(); + for i in 0..=max_entries { + let ptr_addr = match (i as u64) + .checked_mul(8) + .and_then(|o| array_ptr.checked_add(o)) + { + Some(a) => a, + None => break, + }; + if !crate::syscall::validate_user_ptr(ptr_addr, 8) { + break; + } + let entry_ptr = match crate::syscall::read_user_u64(ptr_addr) { + Ok(ptr) => ptr, + Err(_) => break, + }; + if entry_ptr == 0 { + break; + } + let s = match crate::syscall::read_user_cstring(entry_ptr, 4096) { + Ok(s) => s, + Err(_) => break, + }; + result.push(s); + if result.len() >= max_entries { + break; + } + } + result +} + +/// execve システムコール +/// +/// 現在のプロセスイメージを新しいプログラムで置き換える +/// +/// # 引数 +/// - `path_ptr`: 実行ファイルパスのポインタ (null 終端) +/// - `argv`: 引数ポインタ配列 (char*[]) — null 終端、0 の場合は [path] を使用 +/// - `envp`: 環境変数ポインタ配列 (char*[]) — null 終端、0 の場合は空 +pub fn execve_syscall(path_ptr: u64, argv: u64, envp: u64) -> u64 { + use crate::syscall::types::{EINVAL, ENOENT, EPERM}; + + if path_ptr == 0 { + return EINVAL; + } + + let path_owned = match crate::syscall::read_user_cstring(path_ptr, 256) { + Ok(s) => s, + Err(_) => return EINVAL, + }; + let path = path_owned.as_str(); + let aslr_seed = next_aslr_seed(path); + + // サービス起動はサービスマネージャー(Coreまたは登録PID)に限定 + if path.ends_with(".service") && !caller_can_launch_service() { + return EPERM; + } + + // initfs からファイルを読み込む + let data_vec = match crate::init::fs::read(path) { + Some(d) => d, + None => return ENOENT, + }; + let data: &[u8] = &data_vec; + + // ELF エントリポイントとセグメントを解析 + let entry = match crate::elf::loader::entry_point(data) { + Some(e) => e, + None => return EINVAL, + }; + + // 新しいページテーブルを作成 + let new_pt_phys = match crate::mem::paging::create_user_page_table() { + Ok(p) => p, + Err(_) => return EINVAL, + }; + let mut new_pt_guard = UserPageTableGuard::new(new_pt_phys); + + // PT_LOAD セグメントをマップ / ELF メタデータを収集 + const USER_SPACE_END_EXECVE: u64 = 0x0000_7FFF_FFFF_FFFF; + let mut phdr_vaddr: u64 = 0; + let mut phentsize: u64 = 0; + let mut phnum: u64 = 0; + if let Some(eh) = crate::elf::loader::parse_elf_header(data) { + // ELFアーキテクチャ検証 (MED-07) + if eh.e_machine != EM_X86_64 { + crate::warn!("execve: ELF e_machine {:#x} is not x86-64", eh.e_machine); + return EINVAL; + } + phentsize = eh.e_phentsize as u64; + phnum = eh.e_phnum as u64; + let phoff = eh.e_phoff as usize; + let phentsz = eh.e_phentsize as usize; + // phentszが0の場合は無限ループを防ぐ (MED-08) + if phentsz == 0 { + return EINVAL; + } + let n = eh.e_phnum as usize; + let mut load_base: u64 = 0; + let mut load_base_set = false; + for i in 0..n { + // オーバーフロー安全な乗算と加算 (MED-08) + let off_hdr = match i.checked_mul(phentsz).and_then(|x| phoff.checked_add(x)) { + Some(o) if o < data.len() => o, + _ => return EINVAL, + }; + if let Some(ph) = crate::elf::loader::parse_phdr(data, off_hdr) { + if ph.p_type == crate::elf::loader::PT_LOAD { + // ELFセグメントのvaddrがユーザー空間内であることを検証 (CRIT-05) + if ph.p_vaddr >= USER_SPACE_END_EXECVE { + crate::warn!( + "execve: ELF segment vaddr {:#x} is in kernel space", + ph.p_vaddr + ); + return EINVAL; + } + if ph.p_memsz > 0 { + match ph.p_vaddr.checked_add(ph.p_memsz) { + Some(e) if e <= USER_SPACE_END_EXECVE => {} + _ => { + crate::warn!( + "execve: ELF segment vaddr+memsz overflows user space" + ); + return EINVAL; + } + } + } + // 最初の PT_LOAD から load_base を計算 (AT_PHDR 算出用) + if !load_base_set { + load_base = ph.p_vaddr.saturating_sub(ph.p_offset); + load_base_set = true; + } + // ELFセグメントの境界チェック (CRIT-04) + let src_off = ph.p_offset as usize; + let src_end = match src_off.checked_add(ph.p_filesz as usize) { + Some(e) if e <= data.len() => e, + _ => { + crate::warn!("execve: ELF segment src offset+filesz out of bounds"); + return EINVAL; + } + }; + let seg_src = &data[src_off..src_end]; + if crate::mem::paging::map_and_copy_segment_to( + new_pt_phys, + ph.p_vaddr, + ph.p_filesz, + ph.p_memsz, + seg_src, + (ph.p_flags & 0x2) != 0, + (ph.p_flags & 0x1) != 0, + ) + .is_err() + { + return EINVAL; + } + } + } + } + phdr_vaddr = load_base + eh.e_phoff; + } + + // ユーザースタックをセットアップ (Linux x86_64 ABI: argc, argv[], NULL, envp[], NULL, auxv[]) + // argv / envp をユーザー空間から読み込む + let mut argv_strings = read_user_ptr_array(argv, 256); + if argv_strings.is_empty() { + argv_strings.push(path_owned.clone()); + } + let envp_strings = read_user_ptr_array(envp, 1024); + let argc = argv_strings.len(); + let argv_refs: Vec<&str> = argv_strings.iter().map(|s| s.as_str()).collect(); + let envp_refs: Vec<&str> = envp_strings.iter().map(|s| s.as_str()).collect(); + let auxv_entries = [ + (3u64, phdr_vaddr), + (4u64, phentsize), + (5u64, phnum), + (6u64, 4096u64), + (7u64, 0u64), + (8u64, 0u64), + (9u64, entry), + (11u64, 0u64), + (12u64, 0u64), + (13u64, 0u64), + (14u64, 0u64), + (16u64, 0u64), + (17u64, 100u64), + (23u64, 0u64), + (25u64, 0u64), + (31u64, 0u64), + (0u64, 0u64), + ]; + let InitialUserStack { + stack_base_vaddr, + stack_end_vaddr, + initial_rsp, + page_data, + } = match build_initial_user_stack( + aslr_seed, + &argv_refs, + &envp_refs, + &path_owned, + &auxv_entries, + ) { + Ok(stack) => stack, + Err(errno) => return errno, + }; + + if crate::mem::paging::map_and_copy_segment_to( + new_pt_phys, + stack_base_vaddr, + 0, + (USER_STACK_SIZE_PAGES - 1) as u64 * 4096, + &[], + true, + false, + ) + .is_err() + { + return EINVAL; + } + let top_page_vaddr = stack_end_vaddr - 4096; + if crate::mem::paging::map_and_copy_segment_to( + new_pt_phys, + top_page_vaddr, + 4096, + 4096, + &page_data, + true, + false, + ) + .is_err() + { + return EINVAL; + } + + // 初期ヒープをASLR付きで確保 + const HEAP_BASE_MIN: u64 = 0x4000_0000; + const HEAP_ASLR_MAX_PAGES: u64 = 0x8000; // 128MiB + let heap_base = HEAP_BASE_MIN + .saturating_add(aslr_offset_pages(aslr_seed ^ 0x4a11_6b5c, HEAP_ASLR_MAX_PAGES) * 4096); + let heap_map_size: u64 = 4096 * 2; + if crate::mem::paging::map_and_copy_segment_to( + new_pt_phys, + heap_base, + 0, + heap_map_size, + &[], + true, + false, + ) + .is_err() + { + return EINVAL; + } + let initial_fs_base = match map_initial_tls(new_pt_phys, aslr_seed) { + Ok(base) => base, + Err(errno) => return errno, + }; + + // 現在のプロセスのページテーブルとヒープを更新 + let current_tid = match crate::task::current_thread_id() { + Some(t) => t, + None => return EINVAL, + }; + let pid = match crate::task::with_thread(current_tid, |t| t.process_id()) { + Some(p) => p, + None => return EINVAL, + }; + crate::task::with_thread_mut(current_tid, |t| t.set_fs_base(initial_fs_base)); + let old_pt_phys = crate::task::with_process_mut(pid, |p| { + let prev = p.page_table(); + p.set_page_table(new_pt_phys); + p.set_heap_start(heap_base); + p.set_heap_end(heap_base + heap_map_size); + p.set_stack_bottom(stack_base_vaddr); + p.set_stack_top(stack_end_vaddr + 4096); + crate::info!( + "[STACK_INIT] {}: stack_base={:#x}, stack_end={:#x}, stack_top={:#x}", + p.name(), + stack_base_vaddr, + stack_end_vaddr, + stack_end_vaddr + 4096 + ); + prev + }) + .flatten(); + if let Some(old) = old_pt_phys { + if old != new_pt_phys { + let _ = crate::mem::paging::destroy_user_page_table(old); + } + } + new_pt_guard.disarm(); + + // FD_CLOEXEC が設定された FD を exec 時に閉じる + crate::task::with_process_mut(pid, |p| p.fd_table_mut().close_cloexec_fds()); + + // 新しいページテーブルに切り替えてジャンプ + unsafe { + crate::mem::paging::switch_page_table(new_pt_phys); + crate::task::jump_to_usermode(entry, initial_rsp); + } +} + +/// メモリ上の ELF バッファから新プロセスを起動するシステムコール +/// +/// # 引数 +/// - `buf_ptr`: ユーザー空間の ELF データへのポインタ +/// - `buf_len`: バッファのバイト数 +pub fn exec_from_buffer_syscall(buf_ptr: u64, buf_len: u64) -> u64 { + use crate::syscall::types::{EFAULT, EINVAL, EPERM}; + + // core/service のみ許可 + if !caller_can_launch_service() { + return EPERM; + } + + if buf_ptr == 0 || buf_len == 0 || buf_len > 32 * 1024 * 1024 { + return EINVAL; + } + + // ポインタの範囲がユーザー空間内かつ現在のプロセスのページテーブルにマップ済みか検証 + if !crate::syscall::validate_user_ptr(buf_ptr, buf_len) { + return EFAULT; + } + + let mut owned = alloc::vec![0u8; buf_len as usize]; + if let Err(e) = crate::syscall::copy_from_user(buf_ptr, &mut owned) { + return e; + } + + exec_with_data( + &owned, + "user_exec", + "user_exec", + &[], + delegated_parent_pid(), + ) +} + +/// メモリ上の ELF バッファと実行パス名から新プロセスを起動するシステムコール +/// +/// # 引数 +/// - `buf_ptr`: ユーザー空間の ELF データへのポインタ +/// - `buf_len`: バッファのバイト数 +/// - `path_ptr`: ユーザー空間の null 終端パス文字列 +pub fn exec_from_buffer_named_syscall(buf_ptr: u64, buf_len: u64, path_ptr: u64) -> u64 { + use crate::syscall::types::{EFAULT, EINVAL, EPERM}; + + if !caller_can_launch_service() { + return EPERM; + } + if buf_ptr == 0 || buf_len == 0 || buf_len > 32 * 1024 * 1024 || path_ptr == 0 { + return EINVAL; + } + if !crate::syscall::validate_user_ptr(buf_ptr, buf_len) { + return EFAULT; + } + + let path = match crate::syscall::read_user_cstring(path_ptr, 256) { + Ok(s) => s, + Err(_) => return EINVAL, + }; + let process_name = path.rsplit('/').next().unwrap_or(path.as_str()); + + let mut owned = alloc::vec![0u8; buf_len as usize]; + if let Err(e) = crate::syscall::copy_from_user(buf_ptr, &mut owned) { + return e; + } + + exec_with_data( + &owned, + process_name, + path.as_str(), + &[], + delegated_parent_pid(), + ) +} + +/// メモリ上の ELF バッファと実行パス名・引数から新プロセスを起動するシステムコール +/// +/// # 引数 +/// - `buf_ptr`: ユーザー空間の ELF データへのポインタ +/// - `buf_len`: バッファのバイト数 +/// - `path_ptr`: ユーザー空間の null 終端パス文字列 +/// - `args_ptr`: ユーザー空間の null 区切り引数列("arg1\0arg2\0\0") +pub fn exec_from_buffer_named_args_syscall( + buf_ptr: u64, + buf_len: u64, + path_ptr: u64, + args_ptr: u64, +) -> u64 { + use crate::syscall::types::{EFAULT, EINVAL, EPERM}; + + if !caller_can_launch_service() { + return EPERM; + } + if buf_ptr == 0 || buf_len == 0 || buf_len > 32 * 1024 * 1024 || path_ptr == 0 { + return EINVAL; + } + if !crate::syscall::validate_user_ptr(buf_ptr, buf_len) { + return EFAULT; + } + + let path = match crate::syscall::read_user_cstring(path_ptr, 256) { + Ok(s) => s, + Err(_) => return EINVAL, + }; + let process_name = path.rsplit('/').next().unwrap_or(path.as_str()); + + let args_owned = match read_nul_args_from_user(args_ptr, 512, 64) { + Ok(v) => v, + Err(e) => return e, + }; + let args_refs: Vec<&str> = args_owned.iter().map(|s| s.as_str()).collect(); + + let mut owned = alloc::vec![0u8; buf_len as usize]; + if let Err(e) = crate::syscall::copy_from_user(buf_ptr, &mut owned) { + return e; + } + + exec_with_data( + &owned, + process_name, + path.as_str(), + &args_refs, + delegated_parent_pid(), + ) +} + +/// メモリ上の ELF バッファと実行パス名・引数・要求元スレッドIDから新プロセスを起動するシステムコール +pub fn exec_from_buffer_named_args_with_requester_syscall( + buf_ptr: u64, + buf_len: u64, + path_ptr: u64, + args_ptr: u64, + requester_tid: u64, +) -> u64 { + use crate::syscall::types::{EFAULT, EINVAL, EPERM}; + + if !caller_can_launch_service() { + return EPERM; + } + if buf_ptr == 0 || buf_len == 0 || buf_len > 32 * 1024 * 1024 || path_ptr == 0 { + return EINVAL; + } + if !crate::syscall::validate_user_ptr(buf_ptr, buf_len) { + return EFAULT; + } + + let path = match crate::syscall::read_user_cstring(path_ptr, 256) { + Ok(s) => s, + Err(_) => return EINVAL, + }; + let process_name = path.rsplit('/').next().unwrap_or(path.as_str()); + + let args_owned = match read_nul_args_from_user(args_ptr, 512, 64) { + Ok(v) => v, + Err(e) => return e, + }; + let args_refs: Vec<&str> = args_owned.iter().map(|s| s.as_str()).collect(); + + let mut owned = alloc::vec![0u8; buf_len as usize]; + if let Err(e) = crate::syscall::copy_from_user(buf_ptr, &mut owned) { + return e; + } + + let parent_override = if requester_tid != 0 { + let requester = crate::task::ThreadId::from_u64(requester_tid); + let caller_pid = match crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id())) + { + Some(pid) => pid, + None => return EPERM, + }; + match crate::task::with_thread(requester, |t| t.process_id()) { + Some(pid) => { + let caller_is_core = crate::task::with_process(caller_pid, |p| { + p.privilege() == crate::task::PrivilegeLevel::Core + }) + .unwrap_or(false); + + if pid != caller_pid && !caller_is_core { + return EPERM; + } + Some(pid) + } + None => return EINVAL, + } + } else { + None + }; + + exec_with_data( + &owned, + process_name, + path.as_str(), + &args_refs, + parent_override.or_else(delegated_parent_pid), + ) +} diff --git a/src/core/syscall/fs.rs b/src/core/syscall/fs.rs new file mode 100644 index 0000000..8c82434 --- /dev/null +++ b/src/core/syscall/fs.rs @@ -0,0 +1,1735 @@ +//! ファイルシステム関連のシステムコール + +use super::types::{EBADF, EEXIST, EFAULT, EINVAL, EIO, ENOENT, ENOSYS, ENOTDIR, ESRCH, SUCCESS}; +use crate::task::fd_table::{FdTable, FileHandle, FD_BASE, O_CLOEXEC, PROCESS_MAX_FDS}; +use alloc::string::String; +use alloc::string::ToString; +use alloc::sync::Arc; +use alloc::vec; +use alloc::vec::Vec; +use core::sync::atomic::AtomicUsize; + +// グローバル FD テーブルは廃止。各プロセスの Process::fd_table を使用する。 + +#[inline] +fn current_process_id_raw() -> Option { + crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id().as_u64())) +} + +/// 現在プロセスの FD テーブルを読み取り専用で操作する。 +fn with_fd_table(pid_raw: u64, f: F) -> Option +where + F: FnOnce(&FdTable) -> R, +{ + let pid = crate::task::ids::ProcessId::from_u64(pid_raw); + crate::task::with_process(pid, |p| f(p.fd_table())) +} + +/// 現在プロセスの FD テーブルを可変で操作する。 +fn with_fd_table_mut(pid_raw: u64, f: F) -> Option +where + F: FnOnce(&mut FdTable) -> R, +{ + let pid = crate::task::ids::ProcessId::from_u64(pid_raw); + crate::task::with_process_mut(pid, |p| f(p.fd_table_mut())) +} + +// ファイルシステムIPC定数(swiftlib::fs_constsと同一の値を維持) +const FS_PATH_MAX: usize = 512; +const FS_DATA_MAX: usize = 4096; +const IPC_MAX_MSG_SIZE: usize = 65536; +const FS_RECV_TIMEOUT_TICKS: u64 = 500; + +#[repr(C)] +#[derive(Clone, Copy)] +pub(crate) struct FsRequest { + pub(crate) op: u64, + pub(crate) arg1: u64, + pub(crate) arg2: u64, + pub(crate) path: [u8; FS_PATH_MAX], +} + +impl FsRequest { + pub(crate) const OP_OPEN: u64 = 1; + pub(crate) const OP_READ: u64 = 2; + pub(crate) const OP_CLOSE: u64 = 4; + pub(crate) const OP_STAT: u64 = 6; + pub(crate) const OP_FSTAT: u64 = 7; + pub(crate) const OP_READDIR: u64 = 8; + pub(crate) const OP_EXEC_STREAM: u64 = 9; + pub(crate) const OP_READDIR_ALL: u64 = 10; +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub(crate) struct FsResponse { + pub(crate) status: i64, + pub(crate) len: u64, + pub(crate) data: [u8; FS_DATA_MAX], +} + +static CACHED_FS_SERVICE_TID: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0); + +#[inline] +pub(crate) fn fs_service_tid() -> Option { + let cached = CACHED_FS_SERVICE_TID.load(core::sync::atomic::Ordering::Acquire); + if cached != 0 && crate::task::thread_id_exists(cached) { + return Some(cached); + } + + let pid = crate::task::find_process_id_by_name("fs.service") + .or_else(|| crate::task::find_process_id_by_name("fs"))?; + + let mut found_tid = None; + crate::task::for_each_thread(|t| { + if found_tid.is_none() && t.process_id() == pid { + found_tid = Some(t.id().as_u64()); + } + }); + + if let Some(tid) = found_tid { + CACHED_FS_SERVICE_TID.store(tid, core::sync::atomic::Ordering::Release); + Some(tid) + } else { + None + } +} + +fn recv_from_fs_with_timeout(fs_tid: u64, buf: &mut [u8]) -> Result { + let start_tick = crate::syscall::time::get_ticks(); + loop { + if !crate::task::thread_id_exists(fs_tid) { + return Err(EIO); + } + + if let Some(n) = crate::syscall::ipc::recv_from_sender_for_kernel_nonblocking(fs_tid, buf)? + { + return Ok(n); + } + + if crate::syscall::time::get_ticks().saturating_sub(start_tick) > FS_RECV_TIMEOUT_TICKS { + return Err(EIO); + } + crate::task::yield_now(); + } +} + +pub(crate) fn fs_service_request(fs_tid: u64, req: &FsRequest) -> Result { + let req_slice = unsafe { + core::slice::from_raw_parts( + req as *const _ as *const u8, + core::mem::size_of::(), + ) + }; + if crate::syscall::ipc::send_from_kernel(fs_tid, req_slice) { + let mut resp_buf = [0u8; core::mem::size_of::()]; + let n = recv_from_fs_with_timeout(fs_tid, &mut resp_buf)?; + if n < core::mem::size_of::() { + return Err(EIO); + } + let resp: FsResponse = + unsafe { core::ptr::read_unaligned(resp_buf.as_ptr() as *const FsResponse) }; + Ok(resp) + } else { + Err(EIO) + } +} + +// Internal: send request then receive a streamed image from stream backend. Protocol: +// 1) send FsRequest with OP_EXEC_STREAM +// 2) receive initial FsResponse (status,len) where len == total image size +// 3) receive raw data chunks (no per-chunk header) until total bytes received == initial len +fn fs_service_request_stream(fs_tid: u64, req: &FsRequest) -> Result, u64> { + let req_slice = unsafe { + core::slice::from_raw_parts( + req as *const _ as *const u8, + core::mem::size_of::(), + ) + }; + if !crate::syscall::ipc::send_from_kernel(fs_tid, req_slice) { + return Err(EIO); + } + // receive initial FsResponse header + let mut header_buf = [0u8; core::mem::size_of::()]; + let n = recv_from_fs_with_timeout(fs_tid, &mut header_buf)?; + if n < core::mem::size_of::() { + return Err(EIO); + } + let header: FsResponse = + unsafe { core::ptr::read_unaligned(header_buf.as_ptr() as *const FsResponse) }; + if header.status < 0 { + return Err((-header.status) as u64); + } + let total = header.len as usize; + if total == 0 { + return Ok(Vec::new()); + } + if total > 8 * 1024 * 1024 { + return Err(EINVAL); + } + // allocate destination buffer once; receive directly into it to avoid intermediate copies + let mut out = vec![0u8; total]; + let mut received = 0usize; + while received < total { + let remaining = total - received; + let recv_len = core::cmp::min(remaining, IPC_MAX_MSG_SIZE); + let dst = &mut out[received..received + recv_len]; + let n = recv_from_fs_with_timeout(fs_tid, dst)?; + if n == 0 { + return Err(EIO); + } + received += n; + } + if received != total { + return Err(EIO); + } + Ok(out) +} + +pub(crate) fn exec_image_via_fs(path: &str) -> Result, u64> { + let fs_tid = fs_service_tid().ok_or(ESRCH)?; + let req = FsRequest { + op: FsRequest::OP_EXEC_STREAM, + arg1: 0, + arg2: 0, + path: encode_fs_path(path)?, + }; + fs_service_request_stream(fs_tid, &req) +} + +pub(crate) fn encode_fs_path(path: &str) -> Result<[u8; FS_PATH_MAX], u64> { + let mut out = [0u8; FS_PATH_MAX]; + let bytes = path.as_bytes(); + if bytes.is_empty() || bytes.len() >= FS_PATH_MAX { + crate::warn!( + "fs: rejected path length {} (max {})", + bytes.len(), + FS_PATH_MAX - 1 + ); + return Err(EINVAL); + } + if bytes.iter().any(|&b| b == 0) { + return Err(EINVAL); + } + out[..bytes.len()].copy_from_slice(bytes); + Ok(out) +} + +fn open_via_fs_service(path: &str, flags: u64) -> Result { + let fs_tid = fs_service_tid().ok_or(ESRCH)?; + let req = FsRequest { + op: FsRequest::OP_OPEN, + arg1: 0, + arg2: flags, + path: encode_fs_path(path)?, + }; + let resp = fs_service_request(fs_tid, &req)?; + if resp.status < 0 { + return Err((-resp.status) as u64); + } + Ok(resp.status as u64) +} + +fn read_via_fs_service(fd_remote: u64, out: &mut [u8]) -> Result { + let fs_tid = fs_service_tid().ok_or(ESRCH)?; + let req = FsRequest { + op: FsRequest::OP_READ, + arg1: fd_remote, + arg2: out.len() as u64, + path: [0; FS_PATH_MAX], + }; + let resp = fs_service_request(fs_tid, &req)?; + if resp.status < 0 { + return Err((-resp.status) as u64); + } + let n = core::cmp::min( + resp.len as usize, + core::cmp::min(out.len(), resp.data.len()), + ); + out[..n].copy_from_slice(&resp.data[..n]); + Ok(n) +} + +fn close_via_fs_service(fd_remote: u64) -> u64 { + let fs_tid = match fs_service_tid() { + Some(t) => t, + None => return ESRCH, + }; + let req = FsRequest { + op: FsRequest::OP_CLOSE, + arg1: fd_remote, + arg2: 0, + path: [0; FS_PATH_MAX], + }; + match fs_service_request(fs_tid, &req) { + Ok(resp) => { + if resp.status < 0 { + (-resp.status) as u64 + } else { + SUCCESS + } + } + Err(e) => e, + } +} + +pub(crate) fn close_remote_fd_from_kernel(fd_remote: u64) { + let _ = close_via_fs_service(fd_remote); +} + +fn stat_path_via_fs_service(path: &str) -> Result<(u16, u64), u64> { + let fs_tid = fs_service_tid().ok_or(ESRCH)?; + let req = FsRequest { + op: FsRequest::OP_STAT, + arg1: 0, + arg2: 0, + path: encode_fs_path(path)?, + }; + let resp = fs_service_request(fs_tid, &req)?; + if resp.status < 0 { + return Err((-resp.status) as u64); + } + Ok((resp.status as u16, resp.len)) +} + +fn fstat_via_fs_service(fd_remote: u64) -> Result<(u16, u64), u64> { + let fs_tid = fs_service_tid().ok_or(ESRCH)?; + let req = FsRequest { + op: FsRequest::OP_FSTAT, + arg1: fd_remote, + arg2: 0, + path: [0; FS_PATH_MAX], + }; + let resp = fs_service_request(fs_tid, &req)?; + if resp.status < 0 { + return Err((-resp.status) as u64); + } + Ok((resp.status as u16, resp.len)) +} + +fn readdir_chunk_via_fs_service( + fd_remote: u64, + start_index: usize, + out: &mut [u8], +) -> Result<(usize, usize), u64> { + let fs_tid = fs_service_tid().ok_or(ESRCH)?; + let max_bytes = out.len().min(FS_DATA_MAX).min(u32::MAX as usize); + let start = start_index.min(u32::MAX as usize); + let req = FsRequest { + op: FsRequest::OP_READDIR, + arg1: fd_remote, + arg2: ((start as u64) << 32) | (max_bytes as u64), + path: [0; FS_PATH_MAX], + }; + let resp = fs_service_request(fs_tid, &req)?; + if resp.status < 0 { + return Err((-resp.status) as u64); + } + let next_index = usize::try_from(resp.status).map_err(|_| EIO)?; + let n = core::cmp::min( + resp.len as usize, + core::cmp::min(out.len(), resp.data.len()), + ); + out[..n].copy_from_slice(&resp.data[..n]); + Ok((n, next_index)) +} + +fn read_cstring(ptr: u64) -> Result { + crate::syscall::read_user_cstring(ptr, 1024) +} + +#[inline] +fn mode_is_directory(mode: u16) -> bool { + (mode & 0xF000) == 0x4000 +} + +#[inline] +fn mode_for_stat(mode: u16) -> u32 { + let mut out = mode as u32; + if (out & 0xF000) == 0 { + out |= 0x8000; + } + if (out & 0o777) == 0 { + out |= 0o755; + } + out +} + +#[inline] +fn should_fallback_to_initfs(errno: u64) -> bool { + // Prefer ATA rootfs and fallback to initfs only when rootfs path is unavailable. + errno == ESRCH || errno == ENOENT || errno == ENOTDIR || errno == EBADF +} + +#[inline] +fn fallback_file_metadata(path: &str) -> Option<(u16, u64)> { + if crate::kmod::fs::is_mounted() { + crate::kmod::fs::file_metadata(path) + } else { + crate::kmod::fs::file_metadata(path).or_else(|| crate::init::fs::file_metadata(path)) + } +} + +#[inline] +fn fallback_is_directory(path: &str) -> bool { + if crate::kmod::fs::is_mounted() { + crate::kmod::fs::is_directory(path) + } else { + crate::kmod::fs::is_directory(path) || crate::init::fs::is_directory(path) + } +} + +#[inline] +fn fallback_readdir(path: &str) -> Option> { + if crate::kmod::fs::is_mounted() { + crate::kmod::fs::readdir_path(path) + } else { + crate::kmod::fs::readdir_path(path).or_else(|| crate::init::fs::readdir_path(path)) + } +} + +fn parse_readdir_names(bytes: &[u8]) -> Vec { + let mut out = Vec::new(); + for raw in bytes.split(|&b| b == b'\n') { + if raw.is_empty() { + continue; + } + if let Ok(name) = core::str::from_utf8(raw) { + if !name.is_empty() { + out.push(name.to_string()); + } + } + } + out +} + +fn parse_readdir_typed(bytes: &[u8]) -> Vec<(String, u8)> { + let mut out = Vec::new(); + for record in bytes.split(|&b| b == b'\n') { + if record.len() < 2 { + continue; + } + let dtype = record[record.len() - 1]; + if !(1..=8).contains(&dtype) { + continue; + } + if record.len() >= 2 && record[record.len() - 2] == 0 { + let name_bytes = &record[..record.len() - 2]; + if let Ok(name) = core::str::from_utf8(name_bytes) { + if !name.is_empty() { + out.push((name.to_string(), dtype)); + } + } + } + } + out +} + +/// パスを正規化する(`.` / `..` を解決し重複スラッシュを除去) +fn normalize_path(path: &str) -> String { + let mut parts: Vec<&str> = Vec::new(); + for seg in path.split('/') { + match seg { + "" | "." => {} + ".." => { + parts.pop(); + } + other => parts.push(other), + } + } + if parts.is_empty() { + "/".to_string() + } else { + alloc::format!("/{}", parts.join("/")) + } +} + +/// プロセスの CWD を基に相対パスを絶対パスへ解決する +fn resolve_path(pid_raw: u64, path: &str) -> String { + if path.starts_with('/') { + normalize_path(path) + } else { + let pid = crate::task::ids::ProcessId::from_u64(pid_raw); + let cwd = crate::task::with_process(pid, |p| { + let mut s = alloc::string::String::new(); + s.push_str(p.cwd()); + s + }) + .unwrap_or_else(|| "/".to_string()); + normalize_path(&alloc::format!("{}/{}", cwd.trim_end_matches('/'), path)) + } +} + +const FS_SERVICE_RETRY_COUNT: usize = 3; +const FS_SERVICE_RETRY_MS: u64 = 10; +const O_ACCMODE: u64 = 0o3; +const O_WRONLY: u64 = 0o1; +const O_RDWR: u64 = 0o2; +const O_CREAT: u64 = 0o100; +const O_EXCL: u64 = 0o200; +const O_TRUNC: u64 = 0o1000; +const O_APPEND: u64 = 0o2000; + +pub(crate) fn is_tty_like_path(path: &str) -> bool { + path == "/dev/tty" + || path == "/dev/console" + || path == "/dev/stdin" + || path == "/dev/stdout" + || path == "/dev/stderr" + || path.starts_with("/dev/pts/") +} + +fn make_tty_handle(path: &str) -> alloc::boxed::Box { + let tty_path = if is_tty_like_path(path) { + path + } else { + "/dev/tty" + }; + alloc::boxed::Box::new(FileHandle { + data: alloc::boxed::Box::new([]), + pos: 0, + dir_path: Some(tty_path.to_string()), + is_remote: false, + fd_remote: 0, + remote_refs: None, + pipe_id: None, + pipe_write: false, + open_flags: O_RDWR, + }) +} + +fn has_write_intent(flags: u64) -> bool { + let acc = flags & O_ACCMODE; + acc == O_WRONLY || acc == O_RDWR || (flags & (O_CREAT | O_TRUNC)) != 0 +} + +fn open_resolved_for_pid(owner_pid: u64, path: &str, flags: u64) -> u64 { + if is_tty_like_path(path) { + let cloexec = (flags & O_CLOEXEC) != 0; + return match with_fd_table_mut(owner_pid, |t| t.alloc(make_tty_handle(path), cloexec)) { + Some(Some(fd)) => fd as u64, + _ => ENOSYS, + }; + } + + if has_write_intent(flags) { + let exists_in_service = stat_path_via_fs_service(path).is_ok(); + let exists_in_fallback = fallback_file_metadata(path).is_some(); + if (flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL) + && (exists_in_service || exists_in_fallback) + { + return EEXIST; + } + + let mut data_vec = Vec::new(); + if (flags & O_TRUNC) == 0 && exists_in_fallback { + if let Some(d) = crate::kmod::fs::read_all(path) { + data_vec = d; + } + } + let start_pos = if (flags & O_APPEND) != 0 { + data_vec.len() + } else { + 0 + }; + let cloexec = (flags & O_CLOEXEC) != 0; + let handle = alloc::boxed::Box::new(FileHandle { + data: data_vec.into_boxed_slice(), + pos: start_pos, + dir_path: None, + is_remote: false, + fd_remote: 0, + remote_refs: None, + pipe_id: None, + pipe_write: false, + open_flags: flags, + }); + return match with_fd_table_mut(owner_pid, |t| t.alloc(handle, cloexec)) { + Some(Some(fd)) => fd as u64, + _ => ENOSYS, + }; + } + + // カーネル内で管理する FD_CLOEXEC は fs.service へ渡さない。 + let backend_flags = flags & !O_CLOEXEC; + let mut last_err = 0u64; + let mut opened = None; + for _ in 0..FS_SERVICE_RETRY_COUNT { + match open_via_fs_service(path, backend_flags) { + Ok(remote_fd) => { + opened = Some(remote_fd); + break; + } + Err(e) => { + last_err = e; + if e == EIO { + crate::task::yield_now(); + continue; + } else { + break; + } + } + } + } + + let (data_vec, dir_path, is_remote, fd_remote) = match opened { + Some(remote_fd) => { + // ディレクトリFD経由の openat 相対解決で必要になるため、 + // リモートでもディレクトリは dir_path を保持する。 + let dir_path = match stat_path_via_fs_service(path) { + Ok((mode, _)) if (mode as u64 & 0xF000) == 0x4000 => Some(path.to_string()), + _ => None, + }; + (Vec::new(), dir_path, true, remote_fd) + } + None => { + let errno = if last_err != 0 { last_err } else { EIO }; + if should_fallback_to_initfs(errno) { + if fallback_is_directory(path) { + (Vec::new(), Some(path.to_string()), false, 0) + } else { + match crate::kmod::fs::read_all(path) { + Some(d) => (d, None, false, 0), + None => return ENOENT, + } + } + } else { + return errno; + } + } + }; + + let cloexec = (flags & O_CLOEXEC) != 0; + let handle = alloc::boxed::Box::new(FileHandle { + data: data_vec.into_boxed_slice(), + pos: 0, + dir_path, + is_remote, + fd_remote, + remote_refs: if is_remote { + Some(Arc::new(AtomicUsize::new(1))) + } else { + None + }, + pipe_id: None, + pipe_write: false, + open_flags: flags, + }); + + match with_fd_table_mut(owner_pid, |t| t.alloc(handle, cloexec)) { + Some(Some(fd)) => fd as u64, + _ => { + if is_remote { + let _ = close_via_fs_service(fd_remote); + } + ENOSYS + } + } +} + +/// Openシステムコール (initfs の読み取り専用をサポートする簡易実装) +pub fn open(path_ptr: u64, flags: u64) -> u64 { + let owner_pid = match current_process_id_raw() { + Some(pid) => pid, + None => return EBADF, + }; + + let path = match read_cstring(path_ptr) { + Ok(s) => s, + Err(e) => return e, + }; + + let path = resolve_path(owner_pid, &path); + open_resolved_for_pid(owner_pid, &path, flags) +} + +/// Closeシステムコール +pub fn close(fd: u64) -> u64 { + if fd < FD_BASE as u64 { + return EBADF; + } + let idx = fd as usize; + if idx >= PROCESS_MAX_FDS { + return EBADF; + } + let pid = match current_process_id_raw() { + Some(p) => p, + None => return EBADF, + }; + let handle = with_fd_table_mut(pid, |t| t.take(idx)); + match handle { + Some(Some(_h)) => SUCCESS, + _ => EBADF, + } +} + +/// Seekシステムコール +pub fn seek(fd: u64, offset: i64, whence: u64) -> u64 { + if fd < FD_BASE as u64 { + return ENOSYS; + } + let idx = fd as usize; + if idx >= PROCESS_MAX_FDS { + return EBADF; + } + let pid = match current_process_id_raw() { + Some(p) => p, + None => return EBADF, + }; + + match with_fd_table_mut(pid, |t| { + let fh = t.get_mut(idx).ok_or(EBADF)?; + let remote_len = if fh.is_remote { + let (_, size) = fstat_via_fs_service(fh.fd_remote)?; + Some(i64::try_from(size).map_err(|_| EINVAL)?) + } else { + None + }; + let new_pos = match whence { + 0 => offset, + 1 => fh.pos as i64 + offset, + 2 => { + let len = remote_len.unwrap_or(fh.data.len() as i64); + len + offset + } + _ => return Err(EINVAL), + }; + if new_pos < 0 { + return Err(EINVAL); + } + let new_pos = if fh.is_remote { + new_pos as usize + } else { + core::cmp::min(new_pos as usize, fh.data.len()) + }; + fh.pos = new_pos; + Ok(fh.pos as u64) + }) { + Some(Ok(pos)) => pos, + Some(Err(e)) => e, + None => EBADF, + } +} + +/// Linux x86_64 struct stat をユーザーバッファに書き込む +/// +/// struct stat のレイアウト (144 バイト): +/// 0: st_dev (u64) +/// 8: st_ino (u64) +/// 16: st_nlink (u64) +/// 24: st_mode (u32) +/// 28: st_uid (u32) +/// 32: st_gid (u32) +/// 36: __pad0 (u32) +/// 40: st_rdev (u64) +/// 48: st_size (i64) +/// 56: st_blksize (i64) +/// 64: st_blocks (i64) — 512 バイト単位 +/// 72-143: timespec × 3 + unused (ゼロ) +fn write_stat_buf(stat_ptr: u64, mode: u32, size: u64) { + const STAT_SIZE: usize = 144; + let blocks = size.div_ceil(512); + let mut buf = [0u8; STAT_SIZE]; + buf[0..8].copy_from_slice(&1u64.to_ne_bytes()); + buf[8..16].copy_from_slice(&1u64.to_ne_bytes()); + buf[16..24].copy_from_slice(&1u64.to_ne_bytes()); + buf[24..28].copy_from_slice(&mode.to_ne_bytes()); + buf[48..56].copy_from_slice(&size.to_ne_bytes()); + buf[56..64].copy_from_slice(&4096u64.to_ne_bytes()); + buf[64..72].copy_from_slice(&blocks.to_ne_bytes()); + let _ = crate::syscall::copy_to_user(stat_ptr, &buf); +} + +/// Fstatシステムコール +pub fn fstat(fd: u64, stat_ptr: u64) -> u64 { + if stat_ptr == 0 { + return EFAULT; + } + const STAT_SIZE: u64 = 144; + if !crate::syscall::validate_user_ptr(stat_ptr, STAT_SIZE) { + return EFAULT; + } + + if fd < FD_BASE as u64 { + // stdin/stdout/stderr → キャラクタデバイス (S_IFCHR | 0666 = 0x2000 | 0o666) + write_stat_buf(stat_ptr, 0x2000 | 0o666, 0); + return SUCCESS; + } + + let pid = match current_process_id_raw() { + Some(p) => p, + None => return EBADF, + }; + let idx = fd as usize; + if idx >= PROCESS_MAX_FDS { + return EBADF; + } + + // FileHandle からメタデータを取得する + let file_info = with_fd_table(pid, |t| { + t.get(idx).map(|fh| { + let is_tty = fh + .dir_path + .as_deref() + .map(is_tty_like_path) + .unwrap_or(false); + ( + fh.data.len() as u64, + fh.dir_path.is_some(), + is_tty, + fh.is_remote, + fh.fd_remote, + ) + }) + }); + let (size, is_dir, is_tty, is_remote, fd_remote) = match file_info { + Some(Some(v)) => v, + _ => return EBADF, + }; + if is_remote { + let (mode, size) = match fstat_via_fs_service(fd_remote) { + Ok(v) => v, + Err(e) => return e, + }; + write_stat_buf(stat_ptr, mode_for_stat(mode), size); + return SUCCESS; + } + // S_IFCHR = 0x2000, S_IFREG = 0x8000, S_IFDIR = 0x4000 + let mode = if is_tty { + 0x2000u32 | 0o666 + } else if is_dir { + 0x4000u32 | 0o755 + } else { + 0x8000u32 | 0o755 + }; + write_stat_buf(stat_ptr, mode, size); + SUCCESS +} + +/// Statシステムコール +pub fn stat(path_ptr: u64, stat_ptr: u64) -> u64 { + if path_ptr == 0 || stat_ptr == 0 { + return EINVAL; + } + const STAT_SIZE: u64 = 144; + if !crate::syscall::validate_user_ptr(stat_ptr, STAT_SIZE) { + return EFAULT; + } + let owner_pid = match current_process_id_raw() { + Some(pid) => pid, + None => return EBADF, + }; + let path = match read_cstring(path_ptr) { + Ok(s) => s, + Err(e) => return e, + }; + let resolved = resolve_path(owner_pid, &path); + + match stat_path_via_fs_service(&resolved) { + Ok((mode, size)) => { + write_stat_buf(stat_ptr, mode_for_stat(mode), size); + SUCCESS + } + Err(errno) if should_fallback_to_initfs(errno) => match fallback_file_metadata(&resolved) { + Some((inode_mode, size)) => { + write_stat_buf(stat_ptr, mode_for_stat(inode_mode), size); + SUCCESS + } + None => ENOENT, + }, + Err(errno) => errno, + } +} + +/// Mkdirシステムコール(読み取り専用ファイルシステムのため未実装) +pub fn mkdir(_path_ptr: u64, _mode: u64) -> u64 { + ENOSYS +} + +/// Rmdirシステムコール(読み取り専用ファイルシステムのため未実装) +pub fn rmdir(_path_ptr: u64) -> u64 { + ENOSYS +} + +/// Readdirシステムコール +pub fn readdir(fd: u64, buf_ptr: u64, buf_len: u64) -> u64 { + if buf_ptr == 0 || buf_len == 0 { + return EINVAL; + } + if !crate::syscall::validate_user_ptr(buf_ptr, buf_len) { + return EFAULT; + } + if fd < FD_BASE as u64 { + return EBADF; + } + let idx = fd as usize; + if idx >= PROCESS_MAX_FDS { + return EBADF; + } + let pid = match current_process_id_raw() { + Some(p) => p, + None => return EBADF, + }; + + let (dir_path, start_pos, is_remote, fd_remote) = match with_fd_table(pid, |t| { + t.get(idx) + .map(|fh| (fh.dir_path.clone(), fh.pos, fh.is_remote, fh.fd_remote)) + }) { + Some(Some((Some(p), pos, is_remote, fd_remote))) => (p, pos, is_remote, fd_remote), + _ => return EBADF, + }; + + if is_remote { + let mut offset = start_pos; + let mut copied = 0usize; + let mut chunk = [0u8; FS_DATA_MAX]; + while copied < buf_len as usize { + let want = core::cmp::min(chunk.len(), buf_len as usize - copied); + let (n, next_index) = + match readdir_chunk_via_fs_service(fd_remote, offset, &mut chunk[..want]) { + Ok(v) => v, + Err(e) => return e, + }; + if n == 0 { + break; + } + if crate::syscall::copy_to_user(buf_ptr + copied as u64, &chunk[..n]).is_err() { + return EFAULT; + } + copied += n; + offset = next_index; + if n < want { + break; + } + } + let _ = with_fd_table_mut(pid, |t| { + if let Some(fh) = t.get_mut(idx) { + fh.pos = offset; + } + }); + return copied as u64; + } + + let names = match fallback_readdir(&dir_path) { + Some(n) => n, + None => return EINVAL, + }; + let joined = names.join("\n"); + let bytes = joined.as_bytes(); + let to_copy = core::cmp::min(bytes.len(), buf_len as usize); + if crate::syscall::copy_to_user(buf_ptr, &bytes[..to_copy]).is_err() { + return EFAULT; + } + to_copy as u64 +} + +/// Chdirシステムコール +pub fn chdir(path_ptr: u64) -> u64 { + if path_ptr == 0 { + return EINVAL; + } + let pid_raw = match current_process_id_raw() { + Some(pid) => pid, + None => return EBADF, + }; + let path = match read_cstring(path_ptr) { + Ok(s) => s, + Err(e) => return e, + }; + let resolved = resolve_path(pid_raw, &path); + match stat_path_via_fs_service(&resolved) { + Ok((mode, _)) => { + if !mode_is_directory(mode) { + return ENOTDIR; + } + } + Err(errno) if should_fallback_to_initfs(errno) => { + if !fallback_is_directory(&resolved) { + return ENOTDIR; + } + } + Err(errno) => return errno, + } + let pid = crate::task::ids::ProcessId::from_u64(pid_raw); + crate::task::with_process_mut(pid, |p| p.set_cwd(&resolved)); + SUCCESS +} + +/// Getcwdシステムコール +pub fn getcwd(buf_ptr: u64, size: u64) -> u64 { + if buf_ptr == 0 || size == 0 { + return EINVAL; + } + if !crate::syscall::validate_user_ptr(buf_ptr, size) { + return EFAULT; + } + let pid_raw = match current_process_id_raw() { + Some(pid) => pid, + None => return EFAULT, + }; + let pid = crate::task::ids::ProcessId::from_u64(pid_raw); + let mut tmp = [0u8; 256]; + let cwd_len = crate::task::with_process(pid, |p| { + let s = p.cwd().as_bytes(); + let n = s.len().min(255); + tmp[..n].copy_from_slice(&s[..n]); + n + }) + .unwrap_or(1); + let needed = cwd_len + 1; + if (size as usize) < needed { + return EINVAL; + } + if crate::syscall::copy_to_user(buf_ptr, &tmp[..cwd_len]).is_err() { + return EFAULT; + } + if crate::syscall::copy_to_user(buf_ptr + cwd_len as u64, &[0]).is_err() { + return EFAULT; + } + buf_ptr +} + +/// Read: 開かれたファイルからデータを読み込む +pub fn read(fd: u64, buf_ptr: u64, len: u64) -> u64 { + if buf_ptr == 0 { + return EFAULT; + } + if len == 0 { + return 0; + } + if !crate::syscall::validate_user_ptr(buf_ptr, len) { + return EFAULT; + } + if fd < FD_BASE as u64 { + return EBADF; + } + let idx = fd as usize; + if idx >= PROCESS_MAX_FDS { + return EBADF; + } + let pid = match current_process_id_raw() { + Some(p) => p, + None => return EBADF, + }; + + let (is_remote, fd_remote) = + match with_fd_table(pid, |t| t.get(idx).map(|fh| (fh.is_remote, fh.fd_remote))) { + Some(Some(v)) => v, + _ => return EBADF, + }; + + if is_remote { + let mut tmp = alloc::vec![0u8; len as usize]; + let n = match read_via_fs_service(fd_remote, &mut tmp) { + Ok(v) => v, + Err(e) => return e, + }; + if n > 0 { + if crate::syscall::copy_to_user(buf_ptr, &tmp[..n]).is_err() { + return EFAULT; + } + } + return n as u64; + } + + let local = match with_fd_table_mut(pid, |t| { + let fh = t.get_mut(idx)?; + let avail = fh.data.len().saturating_sub(fh.pos); + if avail == 0 { + return Some(Vec::new()); + } + let to_read = core::cmp::min(avail, len as usize); + let mut data = Vec::with_capacity(to_read); + data.extend_from_slice(&fh.data[fh.pos..fh.pos + to_read]); + fh.pos += to_read; + Some(data) + }) { + Some(Some(v)) => v, + _ => return EBADF, + }; + + if local.is_empty() { + return 0; + } + + if crate::syscall::copy_to_user(buf_ptr, &local).is_err() { + return EFAULT; + } + local.len() as u64 +} + +/// Write: 開かれたファイルへデータを書き込む(ローカル一時FDのみ) +pub fn write(fd: u64, buf_ptr: u64, len: u64) -> u64 { + if buf_ptr == 0 { + return EFAULT; + } + if len == 0 { + return 0; + } + if !crate::syscall::validate_user_ptr(buf_ptr, len) { + return EFAULT; + } + if fd < FD_BASE as u64 { + return EBADF; + } + let idx = fd as usize; + if idx >= PROCESS_MAX_FDS { + return EBADF; + } + let pid = match current_process_id_raw() { + Some(p) => p, + None => return EBADF, + }; + + let mut buf = alloc::vec![0u8; len as usize]; + if let Err(errno) = crate::syscall::copy_from_user(buf_ptr, &mut buf) { + return errno; + } + + let wrote = with_fd_table_mut(pid, |t| { + let fh = t.get_mut(idx).ok_or(EBADF)?; + if fh.is_remote { + return Err(ENOSYS); + } + let end = fh.pos.checked_add(buf.len()).ok_or(EINVAL)?; + let mut data = fh.data.to_vec(); + if end > data.len() { + data.resize(end, 0); + } + data[fh.pos..end].copy_from_slice(&buf); + fh.data = data.into_boxed_slice(); + fh.pos = end; + Ok(buf.len() as u64) + }); + match wrote { + Some(Ok(n)) => n, + Some(Err(errno)) => errno, + None => EBADF, + } +} + +/// Fcntl システムコール(FD フラグ操作) +/// +/// - F_GETFD (1): FD フラグを取得 +/// - F_SETFD (2): FD フラグを設定 +/// - F_GETFL (3): ファイル状態フラグを取得(スタブ: 0 を返す) +/// - F_SETFL (4): ファイル状態フラグを設定(スタブ: 成功を返す) +pub fn fcntl(fd: u64, cmd: u64, arg: u64) -> u64 { + use crate::task::fd_table::FD_CLOEXEC; + const F_GETFD: u64 = 1; + const F_SETFD: u64 = 2; + const F_GETFL: u64 = 3; + const F_SETFL: u64 = 4; + const F_GETLK: u64 = 5; + const F_SETLK: u64 = 6; + const F_SETLKW: u64 = 7; + + if fd < FD_BASE as u64 { + // stdin/stdout/stderr: FD フラグは 0 + return match cmd { + F_GETFD | F_GETFL | F_GETLK => 0, + F_SETFD | F_SETFL | F_SETLK | F_SETLKW => SUCCESS, + _ => EINVAL, + }; + } + let idx = fd as usize; + if idx >= PROCESS_MAX_FDS { + return EBADF; + } + let pid = match current_process_id_raw() { + Some(p) => p, + None => return EBADF, + }; + + match cmd { + F_GETFD => match with_fd_table(pid, |t| t.get_flags(idx)) { + Some(Some(flags)) => flags as u64, + _ => EBADF, + }, + F_SETFD => { + let cloexec = (arg & 1) != 0; + let new_flags = if cloexec { FD_CLOEXEC } else { 0 }; + match with_fd_table_mut(pid, |t| t.set_flags(idx, new_flags)) { + Some(true) => SUCCESS, + _ => EBADF, + } + } + F_GETFL => match with_fd_table(pid, |t| t.get(idx).map(|fh| fh.open_flags)) { + Some(Some(v)) => v, + _ => EBADF, + }, + F_SETFL => { + match with_fd_table_mut(pid, |t| { + let fh = t.get_mut(idx).ok_or(EBADF)?; + fh.open_flags = (fh.open_flags & O_ACCMODE) | (arg & !O_ACCMODE); + Ok::<(), u64>(()) + }) { + Some(Ok(())) => SUCCESS, + Some(Err(errno)) => errno, + None => EBADF, + } + } + F_GETLK => SUCCESS, + F_SETLK | F_SETLKW => SUCCESS, + _ => EINVAL, + } +} + +/// fsync/fdatasync システムコール(最小実装) +pub fn fsync(fd: u64) -> u64 { + if fd < FD_BASE as u64 { + return SUCCESS; + } + let idx = fd as usize; + if idx >= PROCESS_MAX_FDS { + return EBADF; + } + let pid = match current_process_id_raw() { + Some(p) => p, + None => return EBADF, + }; + match with_fd_table(pid, |t| t.get(idx).is_some()) { + Some(true) => SUCCESS, + _ => EBADF, + } +} + +/// truncate システムコール(最小実装) +pub fn truncate(_path_ptr: u64, _len: u64) -> u64 { + SUCCESS +} + +/// ftruncate システムコール(ローカル一時FDのみ) +pub fn ftruncate(fd: u64, len: u64) -> u64 { + if fd < FD_BASE as u64 { + return EBADF; + } + let idx = fd as usize; + if idx >= PROCESS_MAX_FDS { + return EBADF; + } + let pid = match current_process_id_raw() { + Some(p) => p, + None => return EBADF, + }; + let new_len = match usize::try_from(len) { + Ok(v) => v, + Err(_) => return EINVAL, + }; + let res = with_fd_table_mut(pid, |t| { + let fh = t.get_mut(idx).ok_or(EBADF)?; + if fh.is_remote { + return Err(ENOSYS); + } + let mut data = fh.data.to_vec(); + data.resize(new_len, 0); + fh.data = data.into_boxed_slice(); + if fh.pos > new_len { + fh.pos = new_len; + } + Ok(()) + }); + match res { + Some(Ok(())) => SUCCESS, + Some(Err(errno)) => errno, + None => EBADF, + } +} + +/// Dup システムコール: FD を複製して最小の空き番号に割り当てる +pub fn dup(fd: u64) -> u64 { + if fd < FD_BASE as u64 { + let pid = match current_process_id_raw() { + Some(p) => p, + None => return EBADF, + }; + return match with_fd_table_mut(pid, |t| t.alloc(make_tty_handle("/dev/tty"), false)) { + Some(Some(new_fd)) => new_fd as u64, + _ => ENOSYS, + }; + } + let idx = fd as usize; + if idx >= PROCESS_MAX_FDS { + return EBADF; + } + let pid = match current_process_id_raw() { + Some(p) => p, + None => return EBADF, + }; + + // 既存エントリをクローンして新しい FD を割り当てる + let cloned = with_fd_table(pid, |t| { + t.get(idx).map(|fh| { + alloc::boxed::Box::new(FileHandle { + data: fh.data.clone(), + pos: fh.pos, + dir_path: fh.dir_path.clone(), + is_remote: fh.is_remote, + fd_remote: fh.fd_remote, + remote_refs: fh.clone_remote_refs(), + pipe_id: fh.pipe_id, + pipe_write: fh.pipe_write, + open_flags: fh.open_flags, + }) + }) + }); + let new_handle = match cloned { + Some(Some(h)) => h, + _ => return EBADF, + }; + + match with_fd_table_mut(pid, |t| t.alloc(new_handle, false)) { + Some(Some(new_fd)) => new_fd as u64, + _ => ENOSYS, + } +} + +/// Dup2 システムコール: FD を指定した番号に複製する +pub fn dup2(old_fd: u64, new_fd: u64) -> u64 { + if new_fd < FD_BASE as u64 || new_fd as usize >= PROCESS_MAX_FDS { + return EBADF; + } + if old_fd == new_fd { + // old_fd が有効かどうかだけ確認 + if old_fd < FD_BASE as u64 { + return old_fd; + } + let pid = match current_process_id_raw() { + Some(p) => p, + None => return EBADF, + }; + return match with_fd_table(pid, |t| t.get(old_fd as usize).is_some()) { + Some(true) => old_fd, + _ => EBADF, + }; + } + + let new_idx = new_fd as usize; + let pid = match current_process_id_raw() { + Some(p) => p, + None => return EBADF, + }; + + let new_handle = if old_fd < FD_BASE as u64 { + make_tty_handle("/dev/tty") + } else { + let old_idx = old_fd as usize; + if old_idx >= PROCESS_MAX_FDS { + return EBADF; + } + let cloned = with_fd_table(pid, |t| { + t.get(old_idx).map(|fh| { + alloc::boxed::Box::new(FileHandle { + data: fh.data.clone(), + pos: fh.pos, + dir_path: fh.dir_path.clone(), + is_remote: fh.is_remote, + fd_remote: fh.fd_remote, + remote_refs: fh.clone_remote_refs(), + pipe_id: fh.pipe_id, + pipe_write: fh.pipe_write, + open_flags: fh.open_flags, + }) + }) + }); + match cloned { + Some(Some(h)) => h, + _ => return EBADF, + } + }; + + // new_fd が使用中なら閉じる + with_fd_table_mut(pid, |t| { + t.close_fd(new_idx); + let ptr = alloc::boxed::Box::into_raw(new_handle) as u64; + t.entries[new_idx] = ptr; + t.flags[new_idx] = 0; + }); + + new_fd +} + +/// unlink システムコール(最小実装) +pub fn unlink(path_ptr: u64) -> u64 { + if path_ptr == 0 { + return EINVAL; + } + match read_cstring(path_ptr) { + Ok(_) => SUCCESS, + Err(errno) => errno, + } +} + +/// unlinkat システムコール(最小実装) +pub fn unlinkat(_dirfd: i64, path_ptr: u64, _flags: u64) -> u64 { + unlink(path_ptr) +} + +/// Openat システムコール +/// +/// AT_FDCWD(-100) の場合は CWD 相対の open() と同等。 +/// それ以外の dirfd は fd_table からディレクトリパスを取得してプレフィックスとして使用する。 +pub fn openat(dirfd: i64, path_ptr: u64, flags: u64, _mode: u64) -> u64 { + const AT_FDCWD: i64 = -100; + + if dirfd == AT_FDCWD { + // CWD 相対 → 通常の open() と同じ + return open(path_ptr, flags); + } + + // dirfd が示すディレクトリを取得 + let pid = match current_process_id_raw() { + Some(p) => p, + None => return EBADF, + }; + let idx = dirfd as usize; + if idx >= PROCESS_MAX_FDS { + return EBADF; + } + let dir_path = match with_fd_table(pid, |t| t.get(idx).and_then(|fh| fh.dir_path.clone())) { + Some(Some(p)) => p, + _ => return EBADF, + }; + + // path を dir_path に対して解決する + let path = match read_cstring(path_ptr) { + Ok(s) => s, + Err(e) => return e, + }; + let full_path = if path.starts_with('/') { + path + } else { + alloc::format!("{}/{}", dir_path.trim_end_matches('/'), path) + }; + + open_resolved_for_pid(pid, &normalize_path(&full_path), flags) +} + +/// Newfstatat (fstatat) システムコール +/// +/// AT_FDCWD(-100) の場合は stat() と同等。 +pub fn newfstatat(dirfd: i64, path_ptr: u64, stat_ptr: u64, flags: u64) -> u64 { + const AT_FDCWD: i64 = -100; + const AT_EMPTY_PATH: u64 = 0x1000; + + // AT_EMPTY_PATH: path が空の場合は dirfd 自体を fstat する + if (flags & AT_EMPTY_PATH) != 0 { + if dirfd == AT_FDCWD { + return stat(path_ptr, stat_ptr); + } + return fstat(dirfd as u64, stat_ptr); + } + + if dirfd == AT_FDCWD { + return stat(path_ptr, stat_ptr); + } + + // dirfd 相対パスを解決して stat + let pid = match current_process_id_raw() { + Some(p) => p, + None => return EBADF, + }; + let idx = dirfd as usize; + if idx >= PROCESS_MAX_FDS { + return EBADF; + } + let dir_path = match with_fd_table(pid, |t| t.get(idx).and_then(|fh| fh.dir_path.clone())) { + Some(Some(p)) => p, + _ => return EBADF, + }; + let path = match read_cstring(path_ptr) { + Ok(s) => s, + Err(e) => return e, + }; + let full = if path.starts_with('/') { + normalize_path(&path) + } else { + normalize_path(&alloc::format!( + "{}/{}", + dir_path.trim_end_matches('/'), + path + )) + }; + match stat_path_via_fs_service(&full) { + Ok((mode, size)) => { + const STAT_SIZE: u64 = 144; + if !crate::syscall::validate_user_ptr(stat_ptr, STAT_SIZE) { + return EFAULT; + } + write_stat_buf(stat_ptr, mode_for_stat(mode), size); + SUCCESS + } + Err(errno) if should_fallback_to_initfs(errno) => match fallback_file_metadata(&full) { + Some((inode_mode, size)) => { + const STAT_SIZE: u64 = 144; + if !crate::syscall::validate_user_ptr(stat_ptr, STAT_SIZE) { + return EFAULT; + } + write_stat_buf(stat_ptr, mode_for_stat(inode_mode), size); + SUCCESS + } + None => ENOENT, + }, + Err(errno) => errno, + } +} + +/// Faccessat システムコール +pub fn faccessat(dirfd: i64, path_ptr: u64, _mode: u64, _flags: u64) -> u64 { + use super::types::ENOENT; + const AT_FDCWD: i64 = -100; + if path_ptr == 0 { + return EINVAL; + } + let path = match read_cstring(path_ptr) { + Ok(s) => s, + Err(e) => return e, + }; + let resolved = if dirfd == AT_FDCWD || path.starts_with('/') { + normalize_path(&path) + } else { + let pid = match current_process_id_raw() { + Some(p) => p, + None => return EBADF, + }; + let idx = dirfd as usize; + if idx >= PROCESS_MAX_FDS { + return EBADF; + } + match with_fd_table(current_process_id_raw().unwrap_or(0), |t| { + t.get(idx).and_then(|fh| fh.dir_path.clone()) + }) { + Some(Some(d)) => { + normalize_path(&alloc::format!("{}/{}", d.trim_end_matches('/'), path)) + } + _ => return EBADF, + } + }; + match stat_path_via_fs_service(&resolved) { + Ok(_) => SUCCESS, + Err(errno) if should_fallback_to_initfs(errno) => { + if fallback_file_metadata(&resolved).is_some() { + SUCCESS + } else { + ENOENT + } + } + Err(errno) => errno, + } +} + +/// statfs システムコール(最小実装) +/// +/// Linux x86_64 の `struct statfs` (120 bytes) を埋めて返す。 +pub fn statfs(path_ptr: u64, buf_ptr: u64) -> u64 { + const STATFS_SIZE: u64 = 120; + if path_ptr == 0 || buf_ptr == 0 { + return EINVAL; + } + if !crate::syscall::validate_user_ptr(buf_ptr, STATFS_SIZE) { + return EFAULT; + } + + let pid = match current_process_id_raw() { + Some(p) => p, + None => return EBADF, + }; + let path = match read_cstring(path_ptr) { + Ok(s) => s, + Err(e) => return e, + }; + let resolved = resolve_path(pid, &path); + if stat_path_via_fs_service(&resolved).is_err() && fallback_file_metadata(&resolved).is_none() { + return ENOENT; + } + + // struct statfs { + // long f_type, f_bsize, f_blocks, f_bfree, f_bavail, f_files, f_ffree; + // fsid_t f_fsid; long f_namelen, f_frsize, f_flags, f_spare[4]; + // } + let mut buf = [0u8; STATFS_SIZE as usize]; + buf[0..8].copy_from_slice(&0xEF53u64.to_ne_bytes()); // ext2 magic + buf[8..16].copy_from_slice(&4096u64.to_ne_bytes()); // f_bsize + buf[64..72].copy_from_slice(&255u64.to_ne_bytes()); // f_namelen + buf[72..80].copy_from_slice(&4096u64.to_ne_bytes()); // f_frsize + crate::syscall::copy_to_user(buf_ptr, &buf) + .map(|_| SUCCESS) + .unwrap_or_else(|e| e) +} + +/// readlinkat システムコール(最小実装) +/// +/// `/proc/self/exe` と `/proc/self/cwd` のみをサポートする。 +pub fn readlinkat(dirfd: i64, path_ptr: u64, buf_ptr: u64, buf_len: u64) -> u64 { + const AT_FDCWD: i64 = -100; + if path_ptr == 0 || buf_ptr == 0 || buf_len == 0 { + return EINVAL; + } + if !crate::syscall::validate_user_ptr(buf_ptr, buf_len) { + return EFAULT; + } + let raw = match read_cstring(path_ptr) { + Ok(s) => s, + Err(e) => return e, + }; + let path = if raw.starts_with('/') || dirfd == AT_FDCWD { + normalize_path(&raw) + } else { + // 最小実装: dirfd 相対は未対応 + return EBADF; + }; + + let pid = match current_process_id_raw() { + Some(p) => crate::task::ids::ProcessId::from_u64(p), + None => return EBADF, + }; + let target = if path == "/proc/self/exe" { + match crate::task::with_process(pid, |p| String::from(p.name())) { + Some(name) if name.starts_with('/') => name, + Some(name) => alloc::format!("/{}", name), + None => return ESRCH, + } + } else if path == "/proc/self/cwd" { + match crate::task::with_process(pid, |p| String::from(p.cwd())) { + Some(cwd) => cwd, + None => return ESRCH, + } + } else { + return ENOENT; + }; + + let bytes = target.as_bytes(); + let copy_len = core::cmp::min(bytes.len(), buf_len as usize); + if let Err(errno) = crate::syscall::copy_to_user(buf_ptr, &bytes[..copy_len]) { + return errno; + } + copy_len as u64 +} + +/// Getdents64 システムコール +/// +/// struct linux_dirent64 形式でエントリをバッファに書き込む。 +/// - d_ino (8), d_off (8), d_reclen (2), d_type (1), d_name (可変長, null終端) +/// - レコードは 8 バイトアラインメント +/// FD の `pos` をエントリインデックスとして使用する。 +pub fn getdents64(fd: u64, buf_ptr: u64, buf_len: u64) -> u64 { + if buf_ptr == 0 || buf_len == 0 { + return EINVAL; + } + if !crate::syscall::validate_user_ptr(buf_ptr, buf_len) { + return EFAULT; + } + if fd < FD_BASE as u64 { + return EBADF; + } + let idx = fd as usize; + if idx >= PROCESS_MAX_FDS { + return EBADF; + } + let pid = match current_process_id_raw() { + Some(p) => p, + None => return EBADF, + }; + + // ディレクトリパスと現在の読み取り位置を取得 + let (dir_path, start_pos, is_remote, fd_remote) = match with_fd_table(pid, |t| { + t.get(idx) + .map(|fh| (fh.dir_path.clone(), fh.pos, fh.is_remote, fh.fd_remote)) + }) { + Some(Some((Some(p), pos, is_remote, fd_remote))) => (p, pos, is_remote, fd_remote), + // リモート fd は dir_path が None でも fd_remote で readdir できる + Some(Some((None, pos, true, fd_remote))) => (String::new(), pos, true, fd_remote), + _ => return EBADF, + }; + + let entries = if is_remote { + // 大きなディレクトリでの切り詰めを避けるため、オフセット付きで分割取得する。 + let mut all: Vec<(String, u8)> = Vec::new(); + let mut chunk = [0u8; FS_DATA_MAX]; + let mut cursor = 0usize; + let mut safety = 0usize; + loop { + safety = safety.saturating_add(1); + if safety > 4096 { + return EIO; + } + let (n, next) = match readdir_chunk_via_fs_service(fd_remote, cursor, &mut chunk) { + Ok(v) => v, + Err(e) => return e, + }; + if n == 0 { + break; + } + let parsed = parse_readdir_typed(&chunk[..n]); + for entry in parsed { + all.push(entry); + } + if next <= cursor { + break; + } + cursor = next; + } + all + } else { + match fallback_readdir(&dir_path) { + Some(e) => e + .into_iter() + // d_type の判定で追加 stat を打つとカーネルモジュール呼び出し回数が増え不安定化するため、 + // ここでは DT_UNKNOWN(0) を返して利用側のフォールバックに任せる。 + .map(|name| (name, 0u8)) + .collect(), + None => return EINVAL, + } + }; + + let mut written: usize = 0; + let mut new_pos = start_pos; + + // "." と ".." を先頭に追加 + let dot_entries: [(&str, u8); 2] = [(".", 4u8), ("..", 4u8)]; + let all_entries: Vec<(alloc::string::String, u8)> = { + let mut v: Vec<(alloc::string::String, u8)> = dot_entries + .iter() + .map(|(n, t)| (alloc::string::String::from(*n), *t)) + .collect(); + for (name, dtype) in &entries { + v.push((name.clone(), *dtype)); + } + v + }; + + let mut out = alloc::vec![0u8; buf_len as usize]; + for (i, (name, dtype)) in all_entries.iter().enumerate().skip(start_pos) { + let name_bytes = name.as_bytes(); + let name_len = name_bytes.len() + 1; + let raw_size = 8 + 8 + 2 + 1 + name_len; + let reclen = (raw_size + 7) & !7usize; + if written + reclen > buf_len as usize { + break; + } + let buf = &mut out[written..written + reclen]; + buf.fill(0); + buf[0..8].copy_from_slice(&((i as u64 + 1).to_ne_bytes())); + let next_off = (i + 1) as u64; + buf[8..16].copy_from_slice(&next_off.to_ne_bytes()); + buf[16..18].copy_from_slice(&(reclen as u16).to_ne_bytes()); + buf[18] = *dtype; + buf[19..19 + name_bytes.len()].copy_from_slice(name_bytes); + buf[19 + name_bytes.len()] = 0; + written += reclen; + new_pos = i + 1; + } + if written > 0 && crate::syscall::copy_to_user(buf_ptr, &out[..written]).is_err() { + return EFAULT; + } + + // FD の pos を更新する + with_fd_table_mut(pid, |t| { + if let Some(fh) = t.get_mut(idx) { + fh.pos = new_pos; + } + }); + + written as u64 +} diff --git a/src/core/syscall/io.rs b/src/core/syscall/io.rs new file mode 100644 index 0000000..5263604 --- /dev/null +++ b/src/core/syscall/io.rs @@ -0,0 +1,406 @@ +//! I/O関連のシステムコール + +use super::types::{EBADF, EFAULT, EINVAL, SUCCESS}; +use crate::syscall::types; +use crate::util::console; +use crate::{debug, error, info, warn}; + +/// 標準出力のファイルディスクリプタ +const STDOUT_FD: u64 = 1; +/// 標準エラー出力のファイルディスクリプタ +const STDERR_FD: u64 = 2; +const IOVEC_SIZE: u64 = 16; +const IOV_MAX: u64 = 1024; + +#[inline] +fn is_tty_path(path: Option<&str>) -> bool { + match path { + Some(p) => crate::syscall::fs::is_tty_like_path(p), + None => false, + } +} + +/// 現在のプロセスの親プロセスのメインスレッドIDを返す +fn get_parent_thread_id() -> Option { + let tid = crate::task::current_thread_id()?; + let pid = crate::task::with_thread(tid, |t| t.process_id())?; + let parent_pid = crate::task::with_process(pid, |p| p.parent_id())??; + let mut parent_tid: Option = None; + crate::task::for_each_thread(|t| { + if parent_tid.is_none() && t.process_id() == parent_pid { + parent_tid = Some(t.id().as_u64()); + } + }); + parent_tid +} + +/// Writeシステムコール +/// +/// # 引数 +/// - `fd`: ファイルディスクリプタ (1=stdout, 2=stderr, >=3=ファイル/パイプ) +/// - `buf_ptr`: 書き込むデータのポインタ +/// - `len`: 書き込むデータの長さ +/// +/// # 戻り値 +/// 書き込んだバイト数、またはエラーコード +pub fn write(fd: u64, buf_ptr: u64, len: u64) -> u64 { + debug!("write: fd={}, buf_ptr={:#x}, len={}", fd, buf_ptr, len); + + if len == 0 { + return SUCCESS; + } + if buf_ptr == 0 { + return EFAULT; + } + + // fd >= 3: パイプ書き込みを試みる + if fd >= 3 { + return write_fd(fd, buf_ptr, len); + } + + if fd != STDOUT_FD && fd != STDERR_FD { + return EBADF; + } + + let mut buf = alloc::vec![0u8; len as usize]; + if let Err(err) = crate::syscall::copy_from_user(buf_ptr, &mut buf) { + return err; + } + + // シリアルには常に出力する(デバッグ用) + x86_64::instructions::interrupts::without_interrupts(|| { + use core::fmt::Write; + let mut serial = console::SERIAL.lock(); + for &byte in &buf { + serial.send_byte(byte); + } + }); + + // TTY 出力を追跡し、必要な端末応答(DSR/DAなど)を stdin 側へ注入する + crate::syscall::tty::process_output(&buf); + + // 親プロセス(シェル)が存在すればIPCで転送して描画させる + // 対話アプリ(vim等)では描画欠落が致命的なので、キューが空くまで待って必ず送る。 + let parent_tid = get_parent_thread_id(); + if let Some(parent_tid) = parent_tid { + const CHUNK: usize = 512; + let mut offset = 0; + while offset < buf.len() { + let end = core::cmp::min(offset + CHUNK, buf.len()); + while !crate::syscall::ipc::send_from_kernel(parent_tid, &buf[offset..end]) { + crate::task::yield_now(); + } + offset = end; + } + } + len +} + +/// Writevシステムコール +/// +/// iov 配列を順に処理し、内部的に `write` を呼び出す。 +pub fn writev(fd: u64, iov_ptr: u64, iovcnt: u64) -> u64 { + if iovcnt == 0 { + return SUCCESS; + } + if iov_ptr == 0 { + return EFAULT; + } + if iovcnt > IOV_MAX { + return EINVAL; + } + + let table_bytes = match iovcnt.checked_mul(IOVEC_SIZE) { + Some(n) => n, + None => return EINVAL, + }; + if !super::validate_user_ptr(iov_ptr, table_bytes) { + return EFAULT; + } + + let mut total_written: u64 = 0; + for i in 0..iovcnt { + let off = match i.checked_mul(IOVEC_SIZE) { + Some(v) => v, + None => return EINVAL, + }; + let entry_ptr = match iov_ptr.checked_add(off) { + Some(v) => v, + None => return EFAULT, + }; + + let mut entry = [0u8; IOVEC_SIZE as usize]; + if let Err(err) = crate::syscall::copy_from_user(entry_ptr, &mut entry) { + return if total_written > 0 { + total_written + } else { + err + }; + } + + let mut base_bytes = [0u8; 8]; + let mut len_bytes = [0u8; 8]; + base_bytes.copy_from_slice(&entry[0..8]); + len_bytes.copy_from_slice(&entry[8..16]); + let base = u64::from_ne_bytes(base_bytes); + let len = u64::from_ne_bytes(len_bytes); + + if len == 0 { + continue; + } + if base == 0 { + return if total_written > 0 { + total_written + } else { + EFAULT + }; + } + + let wrote = write(fd, base, len); + if (wrote as i64) < 0 { + return if total_written > 0 { + total_written + } else { + wrote + }; + } + + total_written = match total_written.checked_add(wrote) { + Some(v) => v, + None => return EINVAL, + }; + + if wrote < len { + break; + } + } + + total_written +} + +/// Readvシステムコール +/// +/// iov 配列を順に処理し、内部的に `read` を呼び出す。 +pub fn readv(fd: u64, iov_ptr: u64, iovcnt: u64) -> u64 { + if iovcnt == 0 { + return SUCCESS; + } + if iov_ptr == 0 { + return EFAULT; + } + if iovcnt > IOV_MAX { + return EINVAL; + } + + let table_bytes = match iovcnt.checked_mul(IOVEC_SIZE) { + Some(n) => n, + None => return EINVAL, + }; + if !super::validate_user_ptr(iov_ptr, table_bytes) { + return EFAULT; + } + + let mut total_read: u64 = 0; + for i in 0..iovcnt { + let off = match i.checked_mul(IOVEC_SIZE) { + Some(v) => v, + None => return EINVAL, + }; + let entry_ptr = match iov_ptr.checked_add(off) { + Some(v) => v, + None => return EFAULT, + }; + + let mut entry = [0u8; IOVEC_SIZE as usize]; + if let Err(err) = crate::syscall::copy_from_user(entry_ptr, &mut entry) { + return if total_read > 0 { total_read } else { err }; + } + + let mut base_bytes = [0u8; 8]; + let mut len_bytes = [0u8; 8]; + base_bytes.copy_from_slice(&entry[0..8]); + len_bytes.copy_from_slice(&entry[8..16]); + let base = u64::from_ne_bytes(base_bytes); + let len = u64::from_ne_bytes(len_bytes); + + if len == 0 { + continue; + } + if base == 0 { + return if total_read > 0 { total_read } else { EFAULT }; + } + + let n = read(fd, base, len); + if (n as i64) < 0 { + return if total_read > 0 { total_read } else { n }; + } + total_read = match total_read.checked_add(n) { + Some(v) => v, + None => return EINVAL, + }; + if n < len { + break; + } + } + total_read +} + +/// fd >= 3 への書き込み(パイプ書き込み端か通常ファイルへの書き込み) +fn write_fd(fd: u64, buf_ptr: u64, len: u64) -> u64 { + let pid = match crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id())) + { + Some(p) => p, + None => return EBADF, + }; + + let idx = fd as usize; + // パイプ/TTY かどうか確認 + let fd_info = crate::task::with_process(pid, |p| { + p.fd_table().get(idx).map(|fh| { + ( + fh.pipe_id, + fh.pipe_write, + is_tty_path(fh.dir_path.as_deref()), + ) + }) + }) + .flatten(); + + match fd_info { + Some((_, _, true)) => write(STDOUT_FD, buf_ptr, len), + Some((Some(pipe_id), true, false)) => { + // パイプ書き込み端 + if !super::validate_user_ptr(buf_ptr, len) { + return EFAULT; + } + let mut buf = alloc::vec![0u8; len as usize]; + if let Err(e) = crate::syscall::copy_from_user(buf_ptr, &mut buf) { + return e; + } + match crate::syscall::pipe::pipe_write(pipe_id, &buf) { + Ok(n) => n as u64, + Err(e) => e, + } + } + Some((None, _, false)) => crate::syscall::fs::write(fd, buf_ptr, len), + Some((Some(_), false, false)) => EBADF, + None => EBADF, + } +} + +/// Readシステムコール +/// - fd == 0 の場合はキーボードからブロッキングで読み取る +/// - fd >= 3 の場合はパイプ or initfs から開かれたファイルを読み取る(fs::read / pipe に委譲) +pub fn read(fd: u64, buf_ptr: u64, len: u64) -> u64 { + use super::types::EFAULT; + + if buf_ptr == 0 { + return EFAULT; + } + if len == 0 { + return 0; + } + + if fd == 0 { + return crate::syscall::tty::read_stdin(buf_ptr, len); + } + + if fd >= 3 { + return read_fd(fd, buf_ptr, len); + } + + // fd=1,2 への read は無効 + super::types::EBADF +} + +/// fd >= 3 からの読み取り(パイプ読み込み端 or 通常ファイル) +fn read_fd(fd: u64, buf_ptr: u64, len: u64) -> u64 { + let pid = match crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id())) + { + Some(p) => p, + None => return EBADF, + }; + + let idx = fd as usize; + let fd_info = crate::task::with_process(pid, |p| { + p.fd_table().get(idx).map(|fh| { + ( + fh.pipe_id, + fh.pipe_write, + is_tty_path(fh.dir_path.as_deref()), + ) + }) + }) + .flatten(); + + match fd_info { + Some((_, _, true)) => crate::syscall::tty::read_stdin(buf_ptr, len), + Some((Some(pipe_id), false, false)) => { + // パイプ読み込み端: ブロッキング読み取り + if !super::validate_user_ptr(buf_ptr, len) { + return EFAULT; + } + let mut buf = alloc::vec![0u8; len as usize]; + let n = crate::syscall::pipe::pipe_read_blocking(pipe_id, &mut buf); + if n > 0 { + if let Err(e) = crate::syscall::copy_to_user(buf_ptr, &buf[..n]) { + return e; + } + } + n as u64 + } + Some(_) => { + // 通常ファイル + crate::syscall::fs::read(fd, buf_ptr, len) + } + None => EBADF, + } +} + +/// Logシステムコール +/// +/// カーネルログにメッセージを書き込む +/// # 引数 +/// msg: メッセージ +/// len: メッセージの長さ +/// level: ログレベル(0=ERROR、1=WARNING、2=INFO、3=DEBUG) +/// +/// # 戻り値 +/// 成功時はSUCCESS、エラー時はエラーコード +pub fn log(msg: u64, len: u64, level: u64) -> u64 { + if msg == 0 || len == 0 { + return super::types::EINVAL; + } + + let mut copied = alloc::vec![0u8; len as usize]; + if let Err(err) = crate::syscall::copy_from_user(msg, &mut copied) { + return err; + } + + let msg = match core::str::from_utf8(&copied) { + Ok(s) => s, + Err(_) => return super::types::EINVAL, + }; + + match level { + 0 => error!("{}", msg), + 1 => warn!("{}", msg), + 2 => info!("{}", msg), + 3 => debug!("{}", msg), + _ => return EINVAL, + } + SUCCESS +} + +/// 重力があるかを確認します。 +/// +/// ### Return +/// SUCCESS: 重力は存在しています。 +/// それ以外が返された場合、このPCは重力下にありません。 +/// +/// 宇宙空間で使用することは想定していません。 +pub fn check_gravity_exist() -> u64 { + SUCCESS +} diff --git a/src/core/syscall/io_port.rs b/src/core/syscall/io_port.rs new file mode 100644 index 0000000..04362df --- /dev/null +++ b/src/core/syscall/io_port.rs @@ -0,0 +1,232 @@ +//! I/Oポートアクセス用のシステムコール + +use crate::syscall::{EFAULT, EINVAL, EPERM, SUCCESS}; +use core::arch::asm; + +/// 呼び出し元プロセスがI/Oポートアクセス権限を持つか確認する +/// +/// ServiceまたはCore権限レベルのプロセスのみ許可する +fn caller_has_port_privilege() -> bool { + crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id())) + .and_then(|pid| { + crate::task::with_process(pid, |p| { + matches!( + p.privilege(), + crate::task::PrivilegeLevel::Core | crate::task::PrivilegeLevel::Service + ) + }) + }) + .unwrap_or(false) +} + +/// I/Oポートから読み取り +/// +/// # Arguments +/// * `port` - ポート番号 (0-65535) +/// * `size` - データサイズ (1=byte, 2=word, 4=dword) +/// +/// # Returns +/// 読み取った値、またはエラー時は EINVAL +pub fn port_in(port: u64, size: u64) -> u64 { + // 権限チェック: ServiceまたはCore権限のプロセスのみI/Oポートアクセスを許可 + if !caller_has_port_privilege() { + return EPERM; + } + + if port > 0xFFFF { + return EINVAL; + } + + let port = port as u16; + + unsafe { + match size { + 1 => { + // inb + let value: u8; + asm!( + "in al, dx", + in("dx") port, + out("al") value, + options(nomem, nostack, preserves_flags) + ); + value as u64 + } + 2 => { + // inw + let value: u16; + asm!( + "in ax, dx", + in("dx") port, + out("ax") value, + options(nomem, nostack, preserves_flags) + ); + value as u64 + } + 4 => { + // inl + let value: u32; + asm!( + "in eax, dx", + in("dx") port, + out("eax") value, + options(nomem, nostack, preserves_flags) + ); + value as u64 + } + _ => EINVAL, + } + } +} + +/// I/Oポートへ書き込み +/// +/// # Arguments +/// * `port` - ポート番号 (0-65535) +/// * `value` - 書き込む値 +/// * `size` - データサイズ (1=byte, 2=word, 4=dword) +/// +/// # Returns +/// SUCCESS、またはエラー時は EINVAL +pub fn port_out(port: u64, value: u64, size: u64) -> u64 { + // 権限チェック: ServiceまたはCore権限のプロセスのみI/Oポートアクセスを許可 + if !caller_has_port_privilege() { + return EPERM; + } + + if port > 0xFFFF { + return EINVAL; + } + + let port = port as u16; + + unsafe { + match size { + 1 => { + // outb + asm!( + "out dx, al", + in("dx") port, + in("al") value as u8, + options(nomem, nostack, preserves_flags) + ); + } + 2 => { + // outw + asm!( + "out dx, ax", + in("dx") port, + in("ax") value as u16, + options(nomem, nostack, preserves_flags) + ); + } + 4 => { + // outl + asm!( + "out dx, eax", + in("dx") port, + in("eax") value as u32, + options(nomem, nostack, preserves_flags) + ); + } + _ => return EINVAL, + } + } + + SUCCESS +} + +/// I/Oポートから16-bitワード列を一括読み取り +/// +/// # Arguments +/// * `port` - ポート番号 (0-65535) +/// * `dst_ptr` - ユーザー空間の出力バッファ(u16配列) +/// * `count` - ワード数 +/// +/// # Returns +/// SUCCESS、またはエラー時は EINVAL / EFAULT / EPERM +pub fn port_in_words(port: u64, dst_ptr: u64, count: u64) -> u64 { + if !caller_has_port_privilege() { + return EPERM; + } + if port > 0xFFFF || count == 0 { + return EINVAL; + } + + let byte_len = match count.checked_mul(2) { + Some(v) => v, + None => return EINVAL, + }; + if !crate::syscall::validate_user_ptr(dst_ptr, byte_len) { + return EFAULT; + } + + let port = port as u16; + let mut tmp = alloc::vec![0u8; byte_len as usize]; + for i in 0..count as usize { + let mut value: u16 = 0; + unsafe { + asm!( + "in ax, dx", + in("dx") port, + out("ax") value, + options(nomem, nostack, preserves_flags) + ); + } + let off = i * 2; + tmp[off..off + 2].copy_from_slice(&value.to_ne_bytes()); + } + + match crate::syscall::copy_to_user(dst_ptr, &tmp) { + Ok(()) => SUCCESS, + Err(e) => e, + } +} + +/// I/Oポートへ16-bitワード列を一括書き込み +/// +/// # Arguments +/// * `port` - ポート番号 (0-65535) +/// * `src_ptr` - ユーザー空間の入力バッファ(u16配列) +/// * `count` - ワード数 +/// +/// # Returns +/// SUCCESS、またはエラー時は EINVAL / EFAULT / EPERM +pub fn port_out_words(port: u64, src_ptr: u64, count: u64) -> u64 { + if !caller_has_port_privilege() { + return EPERM; + } + if port > 0xFFFF || count == 0 { + return EINVAL; + } + + let byte_len = match count.checked_mul(2) { + Some(v) => v, + None => return EINVAL, + }; + if !crate::syscall::validate_user_ptr(src_ptr, byte_len) { + return EFAULT; + } + + let port = port as u16; + let mut tmp = alloc::vec![0u8; byte_len as usize]; + if let Err(e) = crate::syscall::copy_from_user(src_ptr, &mut tmp) { + return e; + } + + for i in 0..count as usize { + let off = i * 2; + let value = u16::from_ne_bytes([tmp[off], tmp[off + 1]]); + unsafe { + asm!( + "out dx, ax", + in("dx") port, + in("ax") value, + options(nomem, nostack, preserves_flags) + ); + } + } + + SUCCESS +} diff --git a/src/core/syscall/ipc.rs b/src/core/syscall/ipc.rs new file mode 100644 index 0000000..23a3c8e --- /dev/null +++ b/src/core/syscall/ipc.rs @@ -0,0 +1,826 @@ +use crate::interrupt::spinlock::SpinLock; +use alloc::vec; +use alloc::vec::Vec; + +use super::{EAGAIN, EFAULT, EINVAL}; + +const MAX_THREADS: usize = crate::task::ThreadQueue::MAX_THREADS; +const MAILBOX_CAP: usize = 64; +const MAX_MSG_SIZE: usize = 4128; // FsResponse(4112) / DiskBulkResponse(2064) を収容 + +#[derive(Debug, Clone, Copy)] +pub struct Message { + from: u64, + to: u64, + to_slot: u16, + to_generation: u64, + len: usize, + data: [u8; MAX_MSG_SIZE], + // Support up to 128 external pages (adjustable). Each entry is a physical page frame address. + ext_pages_count: u16, + ext_pages: [u64; 128], +} + +impl Message { + const fn empty() -> Self { + Self { + from: 0, + to: 0, + to_slot: 0, + to_generation: 0, + len: 0, + data: [0; MAX_MSG_SIZE], + ext_pages_count: 0, + ext_pages: [0; 128], + } + } +} + +#[derive(Debug, Clone, Copy)] +struct Mailbox { + head: usize, + tail: usize, + count: usize, + queue: [u8; MAILBOX_CAP], + slots: [Message; MAILBOX_CAP], + free: [u8; MAILBOX_CAP], + free_count: usize, + /// メッセージ待ちでスリープ中のスレッドID (0=なし) + waiter: u64, +} + +impl Mailbox { + const fn new() -> Self { + let mut free = [0u8; MAILBOX_CAP]; + let mut i = 0; + while i < MAILBOX_CAP { + free[i] = i as u8; + i += 1; + } + Self { + head: 0, + tail: 0, + count: 0, + queue: [0; MAILBOX_CAP], + slots: [Message::empty(); MAILBOX_CAP], + free, + free_count: MAILBOX_CAP, + waiter: 0, + } + } + + fn alloc_slot(&mut self) -> Option { + if self.free_count == 0 { + return None; + } + self.free_count -= 1; + Some(self.free[self.free_count] as usize) + } + + fn quarantine(&mut self, reason: &'static str) { + crate::audit::log(crate::audit::AuditEventKind::Quarantine, reason); + *self = Self::new(); + } + + fn free_slot(&mut self, idx: usize) -> bool { + if idx >= MAILBOX_CAP { + self.quarantine("ipc mailbox free list corrupted: slot index out of range"); + return false; + } + if self.free_count >= MAILBOX_CAP { + self.quarantine("ipc mailbox free list corrupted: free_count overflow"); + return false; + } + for i in 0..self.free_count { + if self.free[i] as usize == idx { + self.quarantine("ipc mailbox free list corrupted: double free"); + return false; + } + } + + self.slots[idx] = Message::empty(); + self.free[self.free_count] = idx as u8; + self.free_count += 1; + true + } + + fn enqueue_slot(&mut self, slot_idx: usize) -> Result<(), ()> { + if self.count >= MAILBOX_CAP { + return Err(()); + } + self.queue[self.tail] = slot_idx as u8; + self.tail = (self.tail + 1) % MAILBOX_CAP; + self.count += 1; + Ok(()) + } + + fn dequeue_slot(&mut self) -> Option { + if self.count == 0 { + return None; + } + let idx = self.queue[self.head] as usize; + self.head = (self.head + 1) % MAILBOX_CAP; + self.count -= 1; + Some(idx) + } + + fn push_message( + &mut self, + from: u64, + to: u64, + to_slot: u16, + to_generation: u64, + data: &[u8], + ) -> Result<(), ()> { + if data.len() > MAX_MSG_SIZE { + return Err(()); + } + let slot_idx = match self.alloc_slot() { + Some(i) => i, + None => return Err(()), + }; + let msg = &mut self.slots[slot_idx]; + msg.from = from; + msg.to = to; + msg.to_slot = to_slot; + msg.to_generation = to_generation; + msg.len = data.len(); + msg.ext_pages_count = 0; + if !data.is_empty() { + msg.data[..data.len()].copy_from_slice(data); + } + if self.enqueue_slot(slot_idx).is_err() { + let _ = self.free_slot(slot_idx); + return Err(()); + } + Ok(()) + } + + fn pop_valid_for_receiver_copy( + &mut self, + receiver: u64, + receiver_slot: u16, + receiver_generation: u64, + out: &mut [u8], + ) -> Option<(u64, usize, u16, [u64; 128])> { + while let Some(slot_idx) = self.dequeue_slot() { + let msg = &self.slots[slot_idx]; + if msg.to == receiver + && msg.to_slot == receiver_slot + && msg.to_generation == receiver_generation + { + let copy_len = core::cmp::min(msg.len, out.len()); + if msg.ext_pages_count > 0 && msg.len == 0 { + let from = msg.from; + let ext_pages_count = msg.ext_pages_count; + let ext_pages = msg.ext_pages; + if !self.free_slot(slot_idx) { + return None; + } + return Some((from, 0usize, ext_pages_count, ext_pages)); + } + if copy_len > 0 { + out[..copy_len].copy_from_slice(&msg.data[..copy_len]); + } + let from = msg.from; + let ext_pages_count = msg.ext_pages_count; + let ext_pages = msg.ext_pages; + if !self.free_slot(slot_idx) { + return None; + } + return Some((from, copy_len, ext_pages_count, ext_pages)); + } + // 古い宛先のメッセージは破棄 + if !self.free_slot(slot_idx) { + return None; + } + } + None + } + + /// 指定送信元からの有効メッセージを1件だけ取り出し、内容を out へコピーする + fn pop_from_sender_copy( + &mut self, + sender: u64, + receiver: u64, + receiver_slot: u16, + receiver_generation: u64, + out: &mut [u8], + ) -> Option<(u64, usize)> { + if self.count == 0 { + return None; + } + + let original = self.count; + for _ in 0..original { + let slot_idx = self.dequeue_slot()?; + let msg = &self.slots[slot_idx]; + if msg.from != sender + || msg.to != receiver + || msg.to_slot != receiver_slot + || msg.to_generation != receiver_generation + { + if self.enqueue_slot(slot_idx).is_err() { + let _ = self.free_slot(slot_idx); + return None; + } + continue; + } + + let copy_len = core::cmp::min(msg.len, out.len()); + if copy_len > 0 { + out[..copy_len].copy_from_slice(&msg.data[..copy_len]); + } + let from = msg.from; + if !self.free_slot(slot_idx) { + return None; + } + return Some((from, copy_len)); + } + + None + } + + /// メッセージを積んだ後、待機中スレッドがいれば返して登録を消す + fn take_waiter(&mut self) -> u64 { + let w = self.waiter; + self.waiter = 0; + w + } +} + +static MAILBOXES: SpinLock<[Mailbox; MAX_THREADS]> = SpinLock::new([Mailbox::new(); MAX_THREADS]); + +/// カーネル内部からIPC送信(ユーザー空間コピー不要) +pub fn send_from_kernel(dest_thread_id: u64, data: &[u8]) -> bool { + let len = data.len(); + if len > MAX_MSG_SIZE { + return false; + } + let (idx, dest_generation) = + match crate::task::thread_slot_index_and_generation_by_u64(dest_thread_id) { + Some(v) => v, + None => return false, + }; + if idx >= MAX_THREADS { + return false; + } + let sender = crate::task::current_thread_id() + .map(|t| t.as_u64()) + .unwrap_or(0); + MAILBOXES.lock().get_mut(idx).map_or(false, |mb| { + if mb + .push_message(sender, dest_thread_id, idx as u16, dest_generation, data) + .is_ok() + { + let waiter = mb.take_waiter(); + if waiter != 0 { + crate::task::wake_thread(crate::task::ThreadId::from_u64(waiter)); + } + true + } else { + false + } + }) +} + +/// Kernel -> recipient: send a message that carries physical page frame addresses +/// Pages are explicit physical frame addresses (one per 4KiB page). Up to 128 entries supported. +pub fn send_pages_from_kernel( + dest_thread_id: u64, + map_start: u64, + total: u64, + pages: &[u64], +) -> bool { + // Keep original behaviour as fallback: send explicit page list when provided. + // This function will continue to work for up to 128 pages. + + if pages.len() > 128 { + return false; + } + let (idx, dest_generation) = + match crate::task::thread_slot_index_and_generation_by_u64(dest_thread_id) { + Some(v) => v, + None => return false, + }; + if idx >= MAX_THREADS { + return false; + } + let sender = crate::task::current_thread_id() + .map(|t| t.as_u64()) + .unwrap_or(0); + let mut boxes = MAILBOXES.lock(); + boxes.get_mut(idx).map_or(false, |mb| { + if let Some(slot_idx) = mb.alloc_slot() { + let msg = &mut mb.slots[slot_idx]; + msg.from = sender; + msg.to = dest_thread_id; + msg.to_slot = idx as u16; + msg.to_generation = dest_generation; + // serialize map_start, total only. + // 物理ページ配列は data に露出させず ext_pages 側だけに保持する。 + let mut off = 0usize; + if 16 > MAX_MSG_SIZE { + let _ = mb.free_slot(slot_idx); + return false; + } + msg.data[off..off + 8].copy_from_slice(&map_start.to_le_bytes()); + off += 8; + msg.data[off..off + 8].copy_from_slice(&(total).to_le_bytes()); + off += 8; + msg.len = off; + msg.ext_pages_count = pages.len() as u16; + for i in 0..pages.len() { + msg.ext_pages[i] = pages[i]; + } + // enqueue + if mb.enqueue_slot(slot_idx).is_err() { + let _ = mb.free_slot(slot_idx); + return false; + } + let waiter = mb.take_waiter(); + if waiter != 0 { + crate::task::wake_thread(crate::task::ThreadId::from_u64(waiter)); + } + true + } else { + false + } + }) +} + +// New: Kernel -> recipient: send a map header only (magic + map_start + total) without page list +pub fn send_map_header_from_kernel(dest_thread_id: u64, map_start: u64, total: u64) -> bool { + const MAP_HEADER_MAGIC: u32 = 0xABCD_DCBAu32; + let (idx, dest_generation) = + match crate::task::thread_slot_index_and_generation_by_u64(dest_thread_id) { + Some(v) => v, + None => return false, + }; + if idx >= MAX_THREADS { + return false; + } + let sender = crate::task::current_thread_id() + .map(|t| t.as_u64()) + .unwrap_or(0); + let mut boxes = MAILBOXES.lock(); + boxes.get_mut(idx).map_or(false, |mb| { + if let Some(slot_idx) = mb.alloc_slot() { + let msg = &mut mb.slots[slot_idx]; + msg.from = sender; + msg.to = dest_thread_id; + msg.to_slot = idx as u16; + msg.to_generation = dest_generation; + // New format: [magic:u32][map_start:u64][total:u64] (20 bytes) + let mut off = 0usize; + if 20 > MAX_MSG_SIZE { + let _ = mb.free_slot(slot_idx); + return false; + } + msg.data[off..off + 4].copy_from_slice(&MAP_HEADER_MAGIC.to_le_bytes()); + off += 4; + msg.data[off..off + 8].copy_from_slice(&map_start.to_le_bytes()); + off += 8; + msg.data[off..off + 8].copy_from_slice(&(total).to_le_bytes()); + off += 8; + crate::debug!( + "[IPC KERN] map_header: magic={:#x} map_start={:#x} total={} -> msg.data[0..20]={:02x?}", + MAP_HEADER_MAGIC, + map_start, + total, + &msg.data[0..20] + ); + crate::info!( + "[IPC KERN] send_map_header dest={} map_start=0x{:x} total={} data={:02x?}", + dest_thread_id, + map_start, + total, + &msg.data[0..20] + ); + msg.len = off; + msg.ext_pages_count = 0; + // enqueue + if mb.enqueue_slot(slot_idx).is_err() { + let _ = mb.free_slot(slot_idx); + return false; + } + let waiter = mb.take_waiter(); + if waiter != 0 { + crate::task::wake_thread(crate::task::ThreadId::from_u64(waiter)); + } + true + } else { + false + } + }) +} + +/// IPC送信 +/// arg0: dest_thread_id +/// arg1: buf_ptr +/// arg2: len +pub fn send(dest_thread_id: u64, buf_ptr: u64, len: u64) -> u64 { + if dest_thread_id == 0 { + return EINVAL; + } + + let len = len as usize; + if len > MAX_MSG_SIZE { + return EINVAL; + } + if len > 0 && buf_ptr == 0 { + return EFAULT; + } + + let sender = match crate::task::current_thread_id() { + Some(id) => id.as_u64(), + None => return EINVAL, + }; + + let (idx, dest_generation) = + match crate::task::thread_slot_index_and_generation_by_u64(dest_thread_id) { + Some(v) => v, + None => return EINVAL, + }; + + if idx >= MAX_THREADS || idx > (u16::MAX as usize) { + return EINVAL; + } + + // NOTE: + // - 宛先スロットに加えて世代番号をメッセージへ埋め込む。 + // - これにより、送信先終了後に同一スロットへ別スレッドが再利用されても誤配送されない。 + // - 送信時点と受信時点で世代不一致なら古いメッセージとして破棄される。 + + // データをユーザー空間からコピー + let mut data = [0u8; MAX_MSG_SIZE]; + if len > 0 && buf_ptr != 0 { + if let Err(err) = crate::syscall::copy_from_user(buf_ptr, &mut data[..len]) { + return err; + } + } + + let mut boxes = MAILBOXES.lock(); + if boxes[idx] + .push_message( + sender, + dest_thread_id, + idx as u16, + dest_generation, + &data[..len], + ) + .is_err() + { + return EAGAIN; + } + let waiter = boxes[idx].take_waiter(); + drop(boxes); + if waiter != 0 { + crate::task::wake_thread(crate::task::ThreadId::from_u64(waiter)); + } + + 0 +} + +fn map_external_pages_for_receiver( + receiver_tid: u64, + map_start_hint: u64, + total: u64, + ext_pages_count: u16, + ext_pages: &[u64; 128], +) -> Result { + if ext_pages_count == 0 || ext_pages_count as usize > ext_pages.len() { + return Err(EINVAL); + } + if total == 0 { + return Err(EINVAL); + } + let max_bytes = (ext_pages_count as u64).saturating_mul(0x1000); + if total > max_bytes { + return Err(EINVAL); + } + + let target_pid = crate::task::thread_to_process_id(receiver_tid).ok_or(EINVAL)?; + let page_span = (ext_pages_count as u64).saturating_mul(0x1000); + + let _ = map_start_hint; // 受信側の安全のためヒントは無視して自動配置する + let (virt_addr, page_table, reserved_heap_old, reserved_heap_new) = + match crate::task::with_process_mut(target_pid, |p| { + let base = if p.heap_end() < 0x7100_0000_0000u64 { + 0x7100_0000_0000u64 + } else { + p.heap_end() + }; + let virt_addr = base + .checked_add(0xfff) + .map(|v| v & !0xfffu64) + .ok_or(EINVAL)?; + let new_end = virt_addr.checked_add(page_span).ok_or(EINVAL)?; + let pt = p.page_table().ok_or(EINVAL)?; + let old_end = p.heap_end(); + p.set_heap_end(new_end); + Ok((virt_addr, pt, old_end, new_end)) + }) { + Some(Ok(v)) => (v.0, v.1, Some(v.2), Some(v.3)), + Some(Err(e)) => return Err(e), + None => return Err(EINVAL), + }; + + for i in 0..(ext_pages_count as usize) { + let target_virt = virt_addr + (i as u64 * 0x1000); + let phys_addr = ext_pages[i]; + if crate::mem::paging::map_page_in_table(page_table, target_virt, phys_addr, true, false) + .is_err() + { + for j in 0..i { + let rollback_virt = virt_addr + (j as u64 * 0x1000); + let _ = crate::mem::paging::unmap_page_in_table(page_table, rollback_virt); + } + if let (Some(old_end), Some(new_end)) = (reserved_heap_old, reserved_heap_new) { + let _ = crate::task::with_process_mut(target_pid, |p| { + if p.heap_end() == new_end { + p.set_heap_end(old_end); + } + }); + } + return Err(EFAULT); + } + } + + Ok(virt_addr) +} + +fn prepare_external_pages_for_user( + receiver_tid: u64, + recv_buf: &mut [u8], + copy_len: usize, + ext_pages_count: u16, + ext_pages: &[u64; 128], +) -> Result { + if ext_pages_count == 0 { + return Ok(copy_len); + } + crate::info!("[IPC RCV] prepare_external_pages_for_user receiver={} copy_len={} ext_pages_count={} data={:02x?}", receiver_tid, copy_len, ext_pages_count, &recv_buf[0..16]); + if copy_len < 16 || recv_buf.len() < 16 { + return Err(EFAULT); + } + let map_start_hint = u64::from_le_bytes([ + recv_buf[0], + recv_buf[1], + recv_buf[2], + recv_buf[3], + recv_buf[4], + recv_buf[5], + recv_buf[6], + recv_buf[7], + ]); + let total = u64::from_le_bytes([ + recv_buf[8], + recv_buf[9], + recv_buf[10], + recv_buf[11], + recv_buf[12], + recv_buf[13], + recv_buf[14], + recv_buf[15], + ]); + let mapped_addr = map_external_pages_for_receiver( + receiver_tid, + map_start_hint, + total, + ext_pages_count, + ext_pages, + )?; + recv_buf[0..8].copy_from_slice(&mapped_addr.to_le_bytes()); + recv_buf[8..16].copy_from_slice(&total.to_le_bytes()); + Ok(16) +} + +/// IPC受信 +/// arg0: buf_ptr +/// arg1: len +/// 戻り値: (sender_id << 32) | received_len +pub fn recv(buf_ptr: u64, max_len: u64) -> u64 { + let receiver = match crate::task::current_thread_id() { + Some(id) => id.as_u64(), + None => return EINVAL, + }; + + let (idx, receiver_generation) = + match crate::task::thread_slot_index_and_generation_by_u64(receiver) { + Some(v) => v, + None => return EINVAL, + }; + + if idx >= MAX_THREADS || idx > (u16::MAX as usize) { + return EINVAL; + } + + let max_copy = core::cmp::min(max_len as usize, MAX_MSG_SIZE); + let mut recv_buf = vec![0u8; MAX_MSG_SIZE]; + let (from, copy_len, ext_pages_count, ext_pages) = { + let mut boxes = MAILBOXES.lock(); + match boxes[idx].pop_valid_for_receiver_copy( + receiver, + idx as u16, + receiver_generation, + &mut recv_buf[..max_copy], + ) { + Some(v) => v, + None => return EAGAIN, + } + }; + crate::info!("[IPC RCV] pop_valid_for_receiver_copy returned from={} copy_len={} ext_pages_count={} data={:02x?}", from, copy_len, ext_pages_count, &recv_buf[..core::cmp::min(copy_len, recv_buf.len())]); + let copy_len = match prepare_external_pages_for_user( + receiver, + &mut recv_buf, + copy_len, + ext_pages_count, + &ext_pages, + ) { + Ok(n) => n, + Err(e) => return e, + }; + + if copy_len > 0 && buf_ptr != 0 { + if let Err(err) = crate::syscall::copy_to_user(buf_ptr, &recv_buf[..copy_len]) { + return err; + } + } + + // 上位32bitに送信元ID、下位32bitに長さ + (from << 32) | (copy_len as u64) +} + +/// IPC受信(ブロッキング版) +/// メッセージが届くまでスレッドをスリープして待機する。 +/// arg0: buf_ptr +/// arg1: len +pub fn recv_blocking(buf_ptr: u64, max_len: u64) -> u64 { + let receiver = match crate::task::current_thread_id() { + Some(id) => id, + None => return EINVAL, + }; + let receiver_u64 = receiver.as_u64(); + + let (idx, receiver_generation) = + match crate::task::thread_slot_index_and_generation_by_u64(receiver_u64) { + Some(v) => v, + None => return EINVAL, + }; + + if idx >= MAX_THREADS || idx > (u16::MAX as usize) { + return EINVAL; + } + + let mut recv_buf = [0u8; MAX_MSG_SIZE]; + loop { + let max_copy = core::cmp::min(max_len as usize, MAX_MSG_SIZE); + // ロックを取得してメッセージを取り出すか、自分を waiter として登録する + let recv = { + let mut boxes = MAILBOXES.lock(); + match boxes[idx].pop_valid_for_receiver_copy( + receiver_u64, + idx as u16, + receiver_generation, + &mut recv_buf[..max_copy], + ) { + Some(v) => Some(v), + None => { + // メッセージなし:waiter として自分を登録してからロック解放 + boxes[idx].waiter = receiver_u64; + None + } + } + }; + + match recv { + Some((from, copy_len, ext_pages_count, ext_pages)) => { + let copy_len = match prepare_external_pages_for_user( + receiver_u64, + &mut recv_buf, + copy_len, + ext_pages_count, + &ext_pages, + ) { + Ok(n) => n, + Err(e) => return e, + }; + if copy_len > 0 && buf_ptr != 0 { + if let Err(err) = crate::syscall::copy_to_user(buf_ptr, &recv_buf[..copy_len]) { + return err; + } + } + return (from << 32) | (copy_len as u64); + } + None => { + // メッセージなし:pending_wakeup がなければスリープして yield + if crate::task::sleep_thread_unless_woken(receiver) { + crate::task::yield_now(); + // 実際にスリープして起床 → ループしてメッセージを再確認 + } else { + // pending_wakeup で即起床(子プロセス終了通知など)だがメッセージなし + // → waiter をクリアして 0 を返し、呼び出し元が終了検知できるようにする + { + let mut boxes = MAILBOXES.lock(); + if boxes[idx].waiter == receiver_u64 { + boxes[idx].waiter = 0; + } + } + return 0; + } + } + } + } +} + +/// カーネル内部から、特定送信元のIPCをノンブロッキング受信する +/// +/// - メッセージが無い場合は `Ok(None)` +/// - 受信データは `buf` にコピーされる +pub fn recv_from_sender_for_kernel_nonblocking( + sender_thread_id: u64, + buf: &mut [u8], +) -> Result, u64> { + let receiver = crate::task::current_thread_id().ok_or(EINVAL)?; + let receiver_u64 = receiver.as_u64(); + let (idx, receiver_generation) = + crate::task::thread_slot_index_and_generation_by_u64(receiver_u64).ok_or(EINVAL)?; + + if idx >= MAX_THREADS || idx > (u16::MAX as usize) { + return Err(EINVAL); + } + + let n = { + let mut boxes = MAILBOXES.lock(); + boxes[idx] + .pop_from_sender_copy( + sender_thread_id, + receiver_u64, + idx as u16, + receiver_generation, + buf, + ) + .map(|(_, n)| n) + }; + + Ok(n) +} + +/// カーネル内部から、特定送信元のIPCをブロッキング受信する +/// +/// - 受信データは `buf` へコピーされる(ユーザー空間検証は行わない) +/// - 指定送信元以外のメッセージはキューに保持されたまま +pub fn recv_blocking_from_sender_for_kernel( + sender_thread_id: u64, + buf: &mut [u8], +) -> Result { + let receiver = match crate::task::current_thread_id() { + Some(id) => id, + None => return Err(EINVAL), + }; + let receiver_u64 = receiver.as_u64(); + + let (idx, receiver_generation) = + match crate::task::thread_slot_index_and_generation_by_u64(receiver_u64) { + Some(v) => v, + None => return Err(EINVAL), + }; + if idx >= MAX_THREADS || idx > (u16::MAX as usize) { + return Err(EINVAL); + } + + loop { + let n = { + let mut boxes = MAILBOXES.lock(); + match boxes[idx].pop_from_sender_copy( + sender_thread_id, + receiver_u64, + idx as u16, + receiver_generation, + buf, + ) { + Some((_, n)) => Some(n), + None => { + boxes[idx].waiter = receiver_u64; + None + } + } + }; + + match n { + Some(n) => return Ok(n), + None => { + if crate::task::sleep_thread_unless_woken(receiver) { + crate::task::yield_now(); + } else { + let mut boxes = MAILBOXES.lock(); + if boxes[idx].waiter == receiver_u64 { + boxes[idx].waiter = 0; + } + return Err(EAGAIN); + } + } + } + } +} diff --git a/src/core/syscall/keyboard.rs b/src/core/syscall/keyboard.rs new file mode 100644 index 0000000..3f100de --- /dev/null +++ b/src/core/syscall/keyboard.rs @@ -0,0 +1,87 @@ +use crate::syscall::{EINVAL, ENODATA, EPERM, SUCCESS}; + +/// 入力監視 API(tap)を呼び出せるか確認する +/// +/// Service または Core 権限のみ許可する。 +fn caller_has_input_privilege() -> bool { + crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id())) + .and_then(|pid| { + crate::task::with_process(pid, |p| { + matches!( + p.privilege(), + crate::task::PrivilegeLevel::Core + | crate::task::PrivilegeLevel::Service + | crate::task::PrivilegeLevel::User + ) + }) + }) + .unwrap_or(false) +} + +/// PS/2 キーボードから rawスキャンコードを1バイト読み取り +/// バッファが空なら ENODATA を返す(変換はユーザー空間で行う) +pub fn read_char() -> u64 { + match crate::util::ps2kbd::pop_scancode() { + Some(sc) => sc as u64, + None => ENODATA, + } +} + +/// ドライバ監視用キューから rawスキャンコードを1バイト読み取る(非破壊 tap) +pub fn read_char_tap() -> u64 { + if !caller_has_input_privilege() { + return EPERM; + } + match crate::util::ps2kbd::pop_tap_scancode() { + Some(sc) => sc as u64, + None => ENODATA, + } +} + +/// raw スキャンコードを通常入力キューへ注入する(Service/Core専用) +pub fn inject_scancode(scancode: u64) -> u64 { + if !caller_has_input_privilege() { + return EPERM; + } + if scancode > 0xFF { + return EINVAL; + } + crate::util::ps2kbd::push_scancode(scancode as u8); + SUCCESS +} + +/// PS/2 キーボードから rawスキャンコードを1バイト読み取る(ブロッキング版) +/// +/// バッファが空であれば、スキャンコードが届くまでスレッドをスリープして待機する。 +/// IPC recv_blocking と同じ「登録→再確認→眠る」パターンで競合を回避する。 +pub fn read_char_blocking() -> u8 { + let tid = match crate::task::current_thread_id() { + Some(id) => id, + // カーネルスレッドからの呼び出し(通常は起きない): スピンで待つ + None => loop { + if let Some(sc) = crate::util::ps2kbd::pop_scancode() { + return sc; + } + crate::task::yield_now(); + }, + }; + + loop { + // waiter を登録してから pop を再試行することで、登録後に届いたスキャンコードを見逃さない + crate::util::ps2kbd::register_waiter(tid.as_u64()); + + if let Some(sc) = crate::util::ps2kbd::pop_scancode() { + // データがあった → 起床不要なので waiter をクリアして返す + crate::util::ps2kbd::unregister_waiter(tid.as_u64()); + return sc; + } + + // データなし → pending_wakeup がなければスリープして yield + if crate::task::sleep_thread_unless_woken(tid) { + crate::task::yield_now(); + // 起床後にループしてデータを再確認 + } + // pending_wakeup で即起床した場合もループして再確認 + } +} diff --git a/src/core/syscall/linux.rs b/src/core/syscall/linux.rs new file mode 100644 index 0000000..b65d4a8 --- /dev/null +++ b/src/core/syscall/linux.rs @@ -0,0 +1,26 @@ +/// READ(読み取ったバイト数) +pub const SYS_READ: u64 = 0; +/// WRITE(書き込んだバイト数) +pub const SYS_WRITE: u64 = 1; +/// OPEN(ファイルディスクリプタ) +pub const SYS_OPEN: u64 = 2; +/// CLOSE(クローズする) +pub const SYS_CLOSE: u64 = 3; +/// STAT(ファイル情報を取得する) +pub const SYS_STAT: u64 = 4; +/// FSTAT(ファイル情報を取得する) +pub const SYS_FSTAT: u64 = 5; +/// LSTAT(シンボリックリンクの情報を取得する) +pub const SYS_LSTAT: u64 = 6; +/// MMAP(メモリマップドファイルをマップする) +pub const SYS_MMAP: u64 = 9; +/// BRK(ヒープ領域の終端を設定する) +pub const SYS_BRK: u64 = 12; +/// ACCESS(ファイルアクセス権を確認する) +pub const SYS_ACCESS: u64 = 21; +/// EXIT(プロセスを終了する) +pub const SYS_EXIT: u64 = 60; +/// GETPID(プロセスIDを取得する) +pub const SYS_GETPID: u64 = 39; +/// GETTID(スレッドIDを取得する) +pub const SYS_GETTID: u64 = 186; diff --git a/src/core/syscall/mmio.rs b/src/core/syscall/mmio.rs new file mode 100644 index 0000000..67796f4 --- /dev/null +++ b/src/core/syscall/mmio.rs @@ -0,0 +1,146 @@ +use super::types::{EFAULT, EINVAL, ENOMEM, EPERM}; +use x86_64::VirtAddr; + +const MAX_MMIO_MAP_SIZE: u64 = 64 * 1024 * 1024; + +fn caller_has_mmio_privilege() -> bool { + crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id())) + .and_then(|pid| { + crate::task::with_process(pid, |p| { + matches!( + p.privilege(), + crate::task::PrivilegeLevel::Core | crate::task::PrivilegeLevel::Service + ) + }) + }) + .unwrap_or(false) +} + +fn current_process_page_table() -> Option { + crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id())) + .and_then(|pid| crate::task::with_process(pid, |p| p.page_table())) + .flatten() +} + +fn translate_user_vaddr_to_phys(table_phys: u64, user_vaddr: u64) -> Result { + let virt = VirtAddr::try_new(user_vaddr).map_err(|_| EFAULT)?; + match crate::mem::paging::translate_addr_in_table(table_phys, virt) { + Some((phys, _)) => Ok(phys.as_u64()), + None => Err(EFAULT), + } +} + +/// 物理アドレス範囲を呼び出し元プロセスへマップする +/// +/// # Returns +/// 成功時: マップ済みユーザー仮想アドレス +/// 失敗時: errno +pub fn map_physical_range(phys_addr: u64, size: u64) -> u64 { + if !caller_has_mmio_privilege() { + return EPERM; + } + if size == 0 { + return EINVAL; + } + + let aligned_phys = phys_addr & !0xfffu64; + let page_offset = phys_addr & 0xfffu64; + let mapped_size = match size + .checked_add(page_offset) + .and_then(|v| v.checked_add(0xfff)) + .map(|v| v & !0xfffu64) + { + Some(v) if v != 0 => v, + _ => return EINVAL, + }; + + if !crate::mem::frame::is_allowed_mmio_range(aligned_phys, mapped_size) { + return EINVAL; + } + + if mapped_size > MAX_MMIO_MAP_SIZE { + return EINVAL; + } + + let tid = match crate::task::current_thread_id() { + Some(t) => t, + None => return ENOMEM, + }; + let pid = match crate::task::with_thread(tid, |t| t.process_id()) { + Some(p) => p, + None => return ENOMEM, + }; + + let result = crate::task::with_process_mut(pid, |process| { + if process.heap_start() == 0 { + let default_base = 0x5000_0000u64; + process.set_heap_start(default_base); + process.set_heap_end(default_base); + } + + let base = process.heap_end(); + let map_start = base.checked_add(0xfff).map(|v| v & !0xfffu64).unwrap_or(0); + if map_start == 0 || map_start > 0x0000_7FFF_FFFF_FFFF { + return Err(ENOMEM); + } + + let pt_phys = match process.page_table() { + Some(p) => p, + None => return Err(ENOMEM), + }; + + let new_end = map_start.checked_add(mapped_size).ok_or(ENOMEM)?; + let final_addr = map_start.checked_add(page_offset).ok_or(ENOMEM)?; + if final_addr > 0x0000_7FFF_FFFF_FFFF { + return Err(ENOMEM); + } + + crate::mem::paging::map_physical_range_to_user( + pt_phys, + map_start, + aligned_phys, + mapped_size, + ) + .map_err(|_| ENOMEM)?; + + process.set_heap_end(new_end); + Ok(final_addr) + }); + + match result { + Some(Ok(va)) => va, + Some(Err(e)) => e, + None => ENOMEM, + } +} + +/// ユーザー仮想アドレスを物理アドレスへ変換する +/// +/// xHCI など DMA デバイスに渡すアドレス算出で使用する。 +/// +/// # 注意 +/// 現在はページの pin/refcount を行わないため、呼び出し側は DMA 完了まで +/// 対象ページがアンマップされないことを保証する必要がある。 +pub fn virt_to_phys(user_vaddr: u64) -> u64 { + if !caller_has_mmio_privilege() { + return EPERM; + } + if user_vaddr == 0 { + return EFAULT; + } + if !crate::syscall::validate_user_ptr(user_vaddr, 1) { + return EFAULT; + } + + let table_phys = match current_process_page_table() { + Some(pt) => pt, + None => return ENOMEM, + }; + + match translate_user_vaddr_to_phys(table_phys, user_vaddr) { + Ok(phys) => phys, + Err(e) => e, + } +} diff --git a/src/core/syscall/mod.rs b/src/core/syscall/mod.rs new file mode 100644 index 0000000..1a979b2 --- /dev/null +++ b/src/core/syscall/mod.rs @@ -0,0 +1,560 @@ +//! システムコール + +pub mod exec; +pub mod fs; +pub mod io; +pub mod io_port; +pub mod ipc; +pub mod keyboard; +pub mod mmio; +pub mod mouse; +pub mod pgroup; +pub mod pipe; +pub mod privileged; +pub mod process; +pub mod signal; +pub mod syscall_entry; +pub mod task; +pub mod time; +pub mod tty; +pub mod vga; + +mod console; +mod linux; +mod types; + +use alloc::string::String; +use alloc::vec::Vec; + +/// ユーザー空間ポインタの有効性を検証する +/// +/// ポインタが null でなく、ユーザー空間のアドレス範囲内にあること、 +/// かつ `ptr + len` がオーバーフローしないことを確認する。 +/// +/// x86-64 canonical ユーザー空間上限: 0x0000_7FFF_FFFF_FFFF +pub fn validate_user_ptr(ptr: u64, len: u64) -> bool { + if ptr == 0 { + return false; + } + // x86-64 ユーザー空間の上限アドレス (canonical hole 下側) + const USER_SPACE_END: u64 = 0x0000_7FFF_FFFF_FFFF; + if ptr > USER_SPACE_END { + return false; + } + let end_inclusive = if len == 0 { + ptr + } else { + match ptr.checked_add(len - 1) { + Some(e) => e, + None => return false, // 整数オーバーフロー + } + }; + if end_inclusive > USER_SPACE_END { + return false; + } + + let user_pt = match crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id())) + .and_then(|pid| crate::task::with_process(pid, |p| p.page_table())) + .flatten() + { + Some(pt) => pt, + None => return false, + }; + + crate::mem::paging::is_user_range_mapped_in_table(user_pt, ptr, len) +} + +#[inline] +pub fn with_user_memory_access(f: impl FnOnce() -> R) -> R { + // Legacy no-op shim kept for old internal call sites outside the hardened + // syscall copy path. New user-memory access must use copy_from_user/ + // copy_to_user so permission checks happen through the page-table walker. + f() +} + +fn current_user_page_table() -> Option { + crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id())) + .and_then(|pid| crate::task::with_process(pid, |p| p.page_table())) + .flatten() +} + +/// ユーザー空間の null 終端文字列を最大長付きで読み取り、カーネル所有の `String` を返す。 +pub fn read_user_cstring(ptr: u64, max_len: usize) -> Result { + if ptr == 0 || max_len == 0 { + return Err(EINVAL); + } + + let mut bytes = Vec::with_capacity(max_len); + for i in 0..max_len { + let addr = ptr.checked_add(i as u64).ok_or(EFAULT)?; + let mut one = [0u8; 1]; + copy_from_user(addr, &mut one)?; + let b = one[0]; + if b == 0 { + return String::from_utf8(bytes).map_err(|_| EINVAL); + } + bytes.push(b); + } + Err(EINVAL) +} + +/// ユーザー空間からバイト列をコピーする(コピー先はカーネル空間)。 +pub fn copy_from_user(src_ptr: u64, dst: &mut [u8]) -> Result<(), u64> { + if dst.is_empty() { + return Ok(()); + } + let user_pt = match current_user_page_table() { + Some(pt) => pt, + None => return Err(EFAULT), + }; + if src_ptr == 0 { + return Err(EFAULT); + } + crate::mem::paging::copy_from_user_in_table(user_pt, src_ptr, dst).map_err(|err| { + crate::audit::log( + crate::audit::AuditEventKind::Usercopy, + "copy_from_user rejected unmapped or unreadable range", + ); + match err { + crate::Kernel::Memory(crate::result::Memory::OutOfMemory) => EFAULT, + crate::Kernel::Memory(crate::result::Memory::PermissionDenied) => EFAULT, + crate::Kernel::Memory(crate::result::Memory::InvalidAddress) => EFAULT, + _ => EFAULT, + } + }) +} + +/// バイト列をユーザー空間へコピーする(コピー元はカーネル空間)。 +pub fn copy_to_user(dst_ptr: u64, src: &[u8]) -> Result<(), u64> { + if src.is_empty() { + return Ok(()); + } + let user_pt = match current_user_page_table() { + Some(pt) => pt, + None => return Err(EFAULT), + }; + if dst_ptr == 0 { + return Err(EFAULT); + } + crate::mem::paging::copy_to_user_in_table(user_pt, dst_ptr, src).map_err(|err| { + crate::audit::log( + crate::audit::AuditEventKind::Usercopy, + "copy_to_user rejected unmapped or unwritable range", + ); + match err { + crate::Kernel::Memory(crate::result::Memory::OutOfMemory) => EFAULT, + crate::Kernel::Memory(crate::result::Memory::PermissionDenied) => EFAULT, + crate::Kernel::Memory(crate::result::Memory::InvalidAddress) => EFAULT, + _ => EFAULT, + } + }) +} + +pub fn read_user_u64(ptr: u64) -> Result { + let mut buf = [0u8; 8]; + copy_from_user(ptr, &mut buf)?; + Ok(u64::from_ne_bytes(buf)) +} + +pub fn read_user_u32(ptr: u64) -> Result { + let mut buf = [0u8; 4]; + copy_from_user(ptr, &mut buf)?; + Ok(u32::from_ne_bytes(buf)) +} + +pub fn read_user_i64(ptr: u64) -> Result { + let mut buf = [0u8; 8]; + copy_from_user(ptr, &mut buf)?; + Ok(i64::from_ne_bytes(buf)) +} + +pub fn read_user_i32(ptr: u64) -> Result { + let mut buf = [0u8; 4]; + copy_from_user(ptr, &mut buf)?; + Ok(i32::from_ne_bytes(buf)) +} + +pub fn read_user_u16(ptr: u64) -> Result { + let mut buf = [0u8; 2]; + copy_from_user(ptr, &mut buf)?; + Ok(u16::from_ne_bytes(buf)) +} + +pub fn write_user_u64(ptr: u64, value: u64) -> Result<(), u64> { + copy_to_user(ptr, &value.to_ne_bytes()) +} + +pub fn write_user_u32(ptr: u64, value: u32) -> Result<(), u64> { + copy_to_user(ptr, &value.to_ne_bytes()) +} + +pub fn write_user_i32(ptr: u64, value: i32) -> Result<(), u64> { + copy_to_user(ptr, &value.to_ne_bytes()) +} + +pub fn write_user_u16(ptr: u64, value: u16) -> Result<(), u64> { + copy_to_user(ptr, &value.to_ne_bytes()) +} + +pub use types::{ + SyscallNumber, EAGAIN, EBADF, EFAULT, EINVAL, ENODATA, ENOENT, ENOSYS, EPERM, ESRCH, SUCCESS, +}; + +use x86_64::structures::idt::InterruptStackFrame; + +/// システムコールのディスパッチ +pub fn dispatch(num: u64, arg0: u64, arg1: u64, arg2: u64, arg3: u64, arg4: u64) -> u64 { + match num { + x if x == SyscallNumber::Read as u64 => io::read(arg0, arg1, arg2), + x if x == SyscallNumber::Readv as u64 => io::readv(arg0, arg1, arg2), + x if x == SyscallNumber::Write as u64 => io::write(arg0, arg1, arg2), + x if x == SyscallNumber::Writev as u64 => io::writev(arg0, arg1, arg2), + x if x == SyscallNumber::Poll as u64 => pgroup::poll(arg0, arg1, arg2), + x if x == SyscallNumber::Open as u64 => fs::open(arg0, arg1), + x if x == SyscallNumber::Close as u64 => fs::close(arg0), + x if x == SyscallNumber::Stat as u64 => fs::stat(arg0, arg1), + x if x == SyscallNumber::Fstat as u64 => fs::fstat(arg0, arg1), + x if x == SyscallNumber::Lseek as u64 => fs::seek(arg0, arg1 as i64, arg2), + x if x == SyscallNumber::Mmap as u64 => process::mmap(arg0, arg1, arg2, arg3, arg4), + x if x == SyscallNumber::Munmap as u64 => process::munmap(arg0, arg1), + x if x == SyscallNumber::Brk as u64 => process::brk(arg0), + x if x == SyscallNumber::RtSigaction as u64 => signal::rt_sigaction(arg0, arg1, arg2), + x if x == SyscallNumber::RtSigprocmask as u64 => signal::rt_sigprocmask(arg0, arg1, arg2), + x if x == SyscallNumber::Kill as u64 => signal::kill(arg0, arg1), + x if x == SyscallNumber::Tkill as u64 => signal::tkill(arg0, arg1), + x if x == SyscallNumber::Tgkill as u64 => signal::tgkill(arg0, arg1, arg2), + x if x == SyscallNumber::Sigaltstack as u64 => process::sigaltstack(arg0, arg1), + x if x == SyscallNumber::Statfs as u64 => fs::statfs(arg0, arg1), + x if x == SyscallNumber::GetPid as u64 => process::getpid(), + x if x == SyscallNumber::Clone as u64 => process::fork(), + x if x == SyscallNumber::Fork as u64 => process::fork(), + x if x == SyscallNumber::Execve as u64 => exec::execve_syscall(arg0, arg1, arg2), + x if x == SyscallNumber::Wait as u64 => process::wait(arg0, arg1, arg2), + x if x == SyscallNumber::GetTid as u64 => process::gettid(), + x if x == SyscallNumber::Futex as u64 => process::futex(arg0, arg1 as u32, arg2, arg3), + x if x == SyscallNumber::ArchPrctl as u64 => process::arch_prctl(arg0, arg1), + x if x == SyscallNumber::ClockGettime as u64 => time::clock_gettime(arg0, arg1), + x if x == SyscallNumber::Getcwd as u64 => fs::getcwd(arg0, arg1), + x if x == SyscallNumber::Truncate as u64 => fs::truncate(arg0, arg1), + x if x == SyscallNumber::Ftruncate as u64 => fs::ftruncate(arg0, arg1), + x if x == SyscallNumber::Exit as u64 => process::exit(arg0), + x if x == SyscallNumber::ExitGroup as u64 => process::exit(arg0), + x if x == SyscallNumber::Yield as u64 => { + task::yield_now(); + SUCCESS + } + x if x == SyscallNumber::GetTicks as u64 => time::get_ticks(), + x if x == SyscallNumber::IpcSend as u64 => ipc::send(arg0, arg1, arg2), + x if x == SyscallNumber::IpcRecv as u64 => ipc::recv(arg0, arg1), + x if x == SyscallNumber::IpcRecvWait as u64 => ipc::recv_blocking(arg0, arg1), + x if x == SyscallNumber::Exec as u64 => exec::exec_kernel(arg0, arg1), + x if x == SyscallNumber::ExecFromFsStream as u64 => exec::exec_from_fs_stream(arg0, arg1), + x if x == SyscallNumber::Sleep as u64 => process::sleep(arg0), + x if x == SyscallNumber::Log as u64 => io::log(arg0, arg1, arg2), + x if x == SyscallNumber::PortIn as u64 => io_port::port_in(arg0, arg1), + x if x == SyscallNumber::PortOut as u64 => io_port::port_out(arg0, arg1, arg2), + x if x == SyscallNumber::PortInWords as u64 => io_port::port_in_words(arg0, arg1, arg2), + x if x == SyscallNumber::PortOutWords as u64 => io_port::port_out_words(arg0, arg1, arg2), + x if x == SyscallNumber::Mkdir as u64 => fs::mkdir(arg0, arg1), + x if x == SyscallNumber::Rmdir as u64 => fs::rmdir(arg0), + x if x == SyscallNumber::Readdir as u64 => fs::readdir(arg0, arg1, arg2), + x if x == SyscallNumber::Chdir as u64 => fs::chdir(arg0), + x if x == SyscallNumber::KeyboardRead as u64 => keyboard::read_char(), + x if x == SyscallNumber::KeyboardReadTap as u64 => keyboard::read_char_tap(), + x if x == SyscallNumber::MouseRead as u64 => { + if arg0 == 0 { + match mouse::read_packet() { + Ok(packet) => packet, + Err(errno) => errno, + } + } else { + match mouse::read_packet() { + Ok(packet) => { + let packet_bytes = (packet as u32).to_ne_bytes(); + match copy_to_user(arg0, &packet_bytes) { + Ok(()) => SUCCESS, + Err(errno) => errno, + } + } + Err(errno) => errno, + } + } + } + x if x == SyscallNumber::MouseReadWait as u64 => { + if arg0 == 0 { + match mouse::read_packet_blocking() { + Ok(packet) => packet, + Err(errno) => errno, + } + } else { + match mouse::read_packet_blocking() { + Ok(packet) => { + let packet_bytes = (packet as u32).to_ne_bytes(); + match copy_to_user(arg0, &packet_bytes) { + Ok(()) => SUCCESS, + Err(errno) => errno, + } + } + Err(errno) => errno, + } + } + } + x if x == SyscallNumber::KeyboardInject as u64 => keyboard::inject_scancode(arg0), + x if x == SyscallNumber::MouseInject as u64 => mouse::inject_packet(arg0), + x if x == SyscallNumber::MapPhysicalRange as u64 => mmio::map_physical_range(arg0, arg1), + x if x == SyscallNumber::VirtToPhys as u64 => mmio::virt_to_phys(arg0), + x if x == SyscallNumber::FindProcessByName as u64 => { + process::find_process_by_name(arg0, arg1) + } + x if x == SyscallNumber::ListProcesses as u64 => process::list_processes(arg0, arg1), + x if x == SyscallNumber::GetThreadPrivilege as u64 => task::get_thread_privilege(arg0), + x if x == SyscallNumber::GetFramebufferInfo as u64 => vga::get_framebuffer_info(arg0), + x if x == SyscallNumber::MapFramebuffer as u64 => vga::map_framebuffer(), + x if x == SyscallNumber::ExecFromBuffer as u64 => { + exec::exec_from_buffer_syscall(arg0, arg1) + } + x if x == SyscallNumber::ExecFromBufferNamed as u64 => { + exec::exec_from_buffer_named_syscall(arg0, arg1, arg2) + } + x if x == SyscallNumber::ExecFromBufferNamedArgs as u64 => { + exec::exec_from_buffer_named_args_syscall(arg0, arg1, arg2, arg3) + } + x if x == SyscallNumber::ExecFromBufferNamedArgsWithRequester as u64 => { + exec::exec_from_buffer_named_args_with_requester_syscall(arg0, arg1, arg2, arg3, arg4) + } + x if x == SyscallNumber::SetConsoleCursor as u64 => { + crate::util::vga::set_cursor_pixel_y(arg0 as usize); + 0 + } + x if x == SyscallNumber::GetConsoleCursor as u64 => { + crate::util::vga::get_cursor_pixel_y() as u64 + } + x if x == SyscallNumber::GetPpid as u64 => pgroup::getppid(), + x if x == SyscallNumber::Setpgid as u64 => pgroup::setpgid(arg0, arg1), + x if x == SyscallNumber::Getpgid as u64 => pgroup::getpgid(arg0), + x if x == SyscallNumber::Setsid as u64 => pgroup::setsid(), + x if x == SyscallNumber::Getsid as u64 => pgroup::getsid(arg0), + x if x == SyscallNumber::Ioctl as u64 => pgroup::ioctl(arg0, arg1, arg2), + x if x == SyscallNumber::Access as u64 => pgroup::access(arg0, arg1), + x if x == SyscallNumber::Select as u64 => pgroup::pselect6(arg0, arg1, arg2, arg3, arg4, 0), + x if x == SyscallNumber::Getuid as u64 => pgroup::getuid(), + x if x == SyscallNumber::Getgid as u64 => pgroup::getgid(), + x if x == SyscallNumber::Geteuid as u64 => pgroup::geteuid(), + x if x == SyscallNumber::Getegid as u64 => pgroup::getegid(), + x if x == SyscallNumber::Lstat as u64 => fs::stat(arg0, arg1), + x if x == SyscallNumber::Readlink as u64 => types::EINVAL, + x if x == SyscallNumber::Unlink as u64 => fs::unlink(arg0), + x if x == SyscallNumber::Fcntl as u64 => fs::fcntl(arg0, arg1, arg2), + x if x == SyscallNumber::Fsync as u64 => fs::fsync(arg0), + x if x == SyscallNumber::Fdatasync as u64 => fs::fsync(arg0), + x if x == SyscallNumber::Pipe as u64 => pipe::pipe_syscall(arg0), + x if x == SyscallNumber::Dup as u64 => fs::dup(arg0), + x if x == SyscallNumber::Dup2 as u64 => fs::dup2(arg0, arg1), + x if x == SyscallNumber::Mprotect as u64 => pgroup::mprotect(arg0, arg1, arg2), + x if x == SyscallNumber::Nanosleep as u64 => pgroup::nanosleep(arg0, arg1), + x if x == SyscallNumber::Uname as u64 => pgroup::uname(arg0), + x if x == SyscallNumber::Getrlimit as u64 => pgroup::getrlimit(arg0, arg1), + x if x == SyscallNumber::SetTidAddress as u64 => pgroup::set_tid_address(arg0), + x if x == SyscallNumber::Prlimit64 as u64 => pgroup::prlimit64(arg0, arg1, arg2, arg3), + x if x == SyscallNumber::SetRobustList as u64 => process::set_robust_list(arg0, arg1), + x if x == SyscallNumber::Pipe2 as u64 => pipe::pipe2_syscall(arg0, arg1), + x if x == SyscallNumber::Openat as u64 => fs::openat(arg0 as i64, arg1, arg2, arg3), + x if x == SyscallNumber::Getdents64 as u64 => fs::getdents64(arg0, arg1, arg2), + x if x == SyscallNumber::Newfstatat as u64 => fs::newfstatat(arg0 as i64, arg1, arg2, arg3), + x if x == SyscallNumber::Unlinkat as u64 => fs::unlinkat(arg0 as i64, arg1, arg2), + x if x == SyscallNumber::Faccessat as u64 => fs::faccessat(arg0 as i64, arg1, arg2, arg3), + x if x == SyscallNumber::Pselect6 as u64 => { + pgroup::pselect6(arg0, arg1, arg2, arg3, arg4, 0) + } + x if x == SyscallNumber::Ppoll as u64 => pgroup::ppoll(arg0, arg1, arg2, arg3, arg4), + x if x == SyscallNumber::Readlinkat as u64 => fs::readlinkat(arg0 as i64, arg1, arg2, arg3), + x if x == SyscallNumber::Getrandom as u64 => process::getrandom(arg0, arg1, arg2), + x if x == SyscallNumber::MapPhysicalPages as u64 => { + privileged::map_physical_pages(arg0, arg1, arg2, arg3) + } + x if x == SyscallNumber::GetPhysicalAddr as u64 => { + privileged::get_physical_addr(arg0, arg1) + } + x if x == SyscallNumber::AllocSharedPages as u64 => { + privileged::alloc_shared_pages(arg0, arg1, arg2, arg3) + } + x if x == SyscallNumber::UnmapPages as u64 => privileged::unmap_pages(arg0, arg1, arg2), + x if x == SyscallNumber::IpcSendPages as u64 => { + privileged::ipc_send_pages(arg0, arg1, arg2, arg3) + } + _ => ENOSYS, + } +} + +/// fork/clone のみ、現在スレッドへユーザーコンテキストを保存する +#[no_mangle] +pub extern "sysv64" fn save_user_context_for_fork( + num: u64, + user_rip: u64, + user_rsp: u64, + user_rflags: u64, +) { + if num != SyscallNumber::Clone as u64 && num != SyscallNumber::Fork as u64 { + return; + } + if let Some(tid) = crate::task::current_thread_id() { + crate::task::with_thread_mut(tid, |t| { + t.set_syscall_user_context(user_rip, user_rsp, user_rflags); + }); + } +} + +/// システムコール割り込みハンドラ (int 0x80) - アセンブリラッパー +/// +/// # Safety +/// CPU が int 0x80 入口規約どおりのスタック/レジスタ状態でこの関数へ入ることを前提とする。 +#[unsafe(naked)] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn syscall_interrupt_handler() { + core::arch::naked_asm!( + // すべてのレジスタを保存(システムコール引数を含む) + "push rax", // syscall number + "push rcx", + "push rdx", // arg2 + "push rbx", + "push rbp", + "push rsi", // arg1 + "push rdi", // arg0 + "push r8", // arg4 + "push r9", + "push r10", // arg3 + "push r11", + "push r12", + "push r13", + "push r14", + "push r15", + + // カーネルデータセグメントをロード + // (ds/esはスタックに保存しない。復元時にユーザーセグメントを再設定) + "mov ax, 0x10", // カーネルデータセグメント (index=2) + "mov ds, ax", + "mov es, ax", + + // int 0x80 経路は、kernel CR3 に切り替えたまま dispatch と signal return を完結させる。 + // KPTI で user CR3 から kernel heap を完全に外しているため、signal 配送や + // プロセス/スレッド metadata 参照を user CR3 上で行うと kernel-mode page fault になる。 + "mov rdi, rsp", // arg0 = kstack(saved registers 先頭) + "call {int80_handler}", // 最終的な戻り値を rax で返す + + // 戻り値 (rax) をスタック上の保存された rax の位置に書き込む + "mov [rsp + 112], rax", + + // ユーザーデータセグメントを設定 + "mov ax, 0x1b", // ユーザーデータセグメント (index=3, RPL=3) + "mov ds, ax", + "mov es, ax", + + // すべてのレジスタを復元 + "pop r15", + "pop r14", + "pop r13", + "pop r12", + "pop r11", + "pop r10", + "pop r9", + "pop r8", + "pop rdi", + "pop rsi", + "pop rbp", + "pop rbx", + "pop rdx", + "pop rcx", + "pop rax", + + // 割り込みから戻る + "iretq", + + int80_handler = sym syscall_interrupt_handler_rust, + ); +} + +/// int 0x80 経路専用の Rust wrapper。 +/// +/// dispatch 本体だけでなく signal 配送/rt_sigreturn まで kernel CR3 上で完結させる。 +/// これにより、KPTI で user CR3 から外した kernel heap / task metadata へ +/// user CR3 のまま触れてしまう事故を防ぐ。 +extern "sysv64" fn syscall_interrupt_handler_rust(kstack: *mut u64) -> u64 { + crate::percpu::install_current_cpu_gs_base(); + + let prev_cr3 = syscall_entry::switch_to_kernel_page_table(); + crate::cpu::reassert_runtime_hardening(); + + let syscall_num = unsafe { kstack.add(14).read() }; + if syscall_num == SyscallNumber::Clone as u64 || syscall_num == SyscallNumber::Fork as u64 { + let user_rip = unsafe { kstack.add(15).read() }; + let user_rflags = unsafe { kstack.add(17).read() }; + let user_rsp = unsafe { kstack.add(18).read() }; + save_user_context_for_fork(syscall_num, user_rip, user_rsp, user_rflags); + } + + let current_tid = crate::task::current_thread_id(); + if let Some(tid) = current_tid { + crate::task::with_thread_mut(tid, |t| t.set_in_syscall(true)); + } + + let ret = dispatch( + syscall_num, + unsafe { kstack.add(8).read() }, // saved rdi = arg0 + unsafe { kstack.add(9).read() }, // saved rsi = arg1 + unsafe { kstack.add(12).read() }, // saved rdx = arg2 + unsafe { kstack.add(5).read() }, // saved r10 = arg3 + unsafe { kstack.add(7).read() }, // saved r8 = arg4 + ); + + if let Some(tid) = current_tid { + crate::task::with_thread_mut(tid, |t| t.set_in_syscall(false)); + } + + let ret = signal::signal_and_return(kstack, ret); + syscall_entry::restore_page_table(prev_cr3); + ret +} + +/// システムコールハンドラの Rust 実装 +extern "C" fn syscall_handler_rust( + num: u64, + arg0: u64, + arg1: u64, + arg2: u64, + arg3: u64, + arg4: u64, +) -> u64 { + crate::percpu::install_current_cpu_gs_base(); + let current_tid = crate::task::current_thread_id(); + let prev_cr3 = syscall_entry::switch_to_kernel_page_table(); + crate::cpu::reassert_runtime_hardening(); + if let Some(tid) = current_tid { + crate::task::with_thread_mut(tid, |t| t.set_in_syscall(true)); + } + + let ret = dispatch(num, arg0, arg1, arg2, arg3, arg4); + + if let Some(tid) = current_tid { + crate::task::with_thread_mut(tid, |t| t.set_in_syscall(false)); + } + syscall_entry::restore_page_table(prev_cr3); + ret +} + +/// SYSCALL 命令エントリから呼ばれる system V ABI ディスパッチ関数 +/// +/// syscall_entry.rs の naked asm から `call {dispatch}` で呼ばれる。 +/// system V ABI: 引数は rdi, rsi, rdx, rcx, r8, r9 の順。 +#[no_mangle] +pub extern "sysv64" fn syscall_dispatch_sysv( + num: u64, + arg0: u64, + arg1: u64, + arg2: u64, + arg3: u64, + arg4: u64, +) -> u64 { + syscall_handler_rust(num, arg0, arg1, arg2, arg3, arg4) +} diff --git a/src/core/syscall/mouse.rs b/src/core/syscall/mouse.rs new file mode 100644 index 0000000..60be235 --- /dev/null +++ b/src/core/syscall/mouse.rs @@ -0,0 +1,100 @@ +use crate::syscall::{EINVAL, ENODATA, EPERM, SUCCESS}; + +/// マウス入力注入 API を呼び出せるか確認する +/// +/// Service または Core 権限のみ許可する。 +fn caller_has_mouse_inject_privilege() -> bool { + crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id())) + .and_then(|pid| { + crate::task::with_process(pid, |p| { + matches!( + p.privilege(), + crate::task::PrivilegeLevel::Core + | crate::task::PrivilegeLevel::Service + | crate::task::PrivilegeLevel::User + ) + }) + }) + .unwrap_or(false) +} + +/// PS/2 マウスパケットを 1 つ読み取る(非ブロッキング) +/// +/// 返り値は `b0 | (b1 << 8) | (b2 << 16)` 形式。 +/// キューが空なら ENODATA。 +pub fn read_packet() -> Result { + match crate::util::ps2mouse::pop_packet() { + Some(packet) => Ok(packet as u64), + None => Err(ENODATA), + } +} + +/// PS/2 マウスパケットを 1 つ読み取る(ブロッキング) +/// +/// データが到着するまで waiter 登録してスレッドをスリープし、 +/// IRQ12 での wake により再開する。 +pub fn read_packet_blocking() -> Result { + if let Some(packet) = crate::util::ps2mouse::pop_packet() { + return Ok(packet as u64); + } + + let tid = match crate::task::current_thread_id() { + Some(id) => id, + None => loop { + if let Some(packet) = crate::util::ps2mouse::pop_packet() { + return Ok(packet as u64); + } + crate::task::yield_now(); + }, + }; + + loop { + if crate::util::ps2mouse::register_waiter(tid.as_u64()) { + if let Some(packet) = crate::util::ps2mouse::pop_packet() { + crate::util::ps2mouse::unregister_waiter(tid.as_u64()); + return Ok(packet as u64); + } + + if crate::task::sleep_thread_unless_woken(tid) { + crate::task::yield_now(); + } + } else { + if let Some(packet) = crate::util::ps2mouse::pop_packet() { + return Ok(packet as u64); + } + crate::task::yield_now(); + } + } +} + +/// マウスパケットを通常入力キューへ注入する(Service/Core専用) +/// +/// `packet` は `b0 | (b1 << 8) | (b2 << 16)` 形式。 +/// 互換のため `wheel` を含む 4 バイト (`b3<<24`) も受理する。 +pub fn inject_packet(packet: u64) -> u64 { + if !caller_has_mouse_inject_privilege() { + return EPERM; + } + if packet > 0xFFFF_FFFF { + return EINVAL; + } + let b0 = (packet & 0xFF) as u8; + let b1 = ((packet >> 8) & 0xFF) as u8; + let b2 = ((packet >> 16) & 0xFF) as u8; + // caller が buttons のみ渡した場合でもパケット同期できるよう補完 + let mut status = b0; + if (b0 & 0x08) == 0 { + status |= 0x08; + status &= !((1 << 4) | (1 << 5)); + if (b1 & 0x80) != 0 { + status |= 1 << 4; + } + if (b2 & 0x80) != 0 { + status |= 1 << 5; + } + } + let packet32 = u32::from(status) | (u32::from(b1) << 8) | (u32::from(b2) << 16); + crate::util::ps2mouse::push_packet(packet32); + SUCCESS +} diff --git a/src/core/syscall/pgroup.rs b/src/core/syscall/pgroup.rs new file mode 100644 index 0000000..8d16e03 --- /dev/null +++ b/src/core/syscall/pgroup.rs @@ -0,0 +1,669 @@ +//! プロセスグループ・セッション関連のシステムコール + +use super::types::{EFAULT, EINVAL, ENOMEM, ENOTSUP, EPERM, ESRCH, SUCCESS}; +use crate::task::fd_table::FD_BASE; + +const POLLIN: u16 = 0x0001; +const POLLOUT: u16 = 0x0004; +const POLLRDNORM: u16 = 0x0040; +const POLLWRNORM: u16 = 0x0100; + +fn stdin_ready() -> bool { + crate::syscall::tty::has_pending_input() +} + +fn is_tty_fd(fd: i32) -> bool { + if fd < 0 { + return false; + } + if fd <= 2 { + return true; + } + if (fd as u64) < FD_BASE as u64 { + return false; + } + let pid = match current_pid() { + Some(p) => p, + None => return false, + }; + crate::task::with_process(pid, |p| { + p.fd_table().get(fd as usize).is_some_and(|fh| { + crate::syscall::fs::is_tty_like_path(fh.dir_path.as_deref().unwrap_or("")) + }) + }) + .unwrap_or(false) +} + +fn stdin_ready_for_fd(fd: i32) -> bool { + is_tty_fd(fd) && stdin_ready() +} + +fn stdout_ready(fd: i32) -> bool { + is_tty_fd(fd) +} + +fn wait_until_ready_or_timeout(mut timeout_ms: i64, mut ready_fn: impl FnMut() -> bool) -> bool { + if ready_fn() { + return true; + } + if timeout_ms == 0 { + return false; + } + if timeout_ms < 0 { + loop { + if ready_fn() { + return true; + } + crate::task::yield_now(); + } + } + while timeout_ms > 0 { + crate::syscall::process::sleep(1); + if ready_fn() { + return true; + } + timeout_ms -= 1; + } + false +} + +fn fdset_len_bytes(nfds: u64) -> Option { + let words = nfds.checked_add(63)?.checked_div(64)?; + words.checked_mul(8) +} + +fn fdset_test(ptr: u64, fd: u64) -> bool { + let word_off = (fd / 64) * 8; + let bit = (fd % 64) as u32; + let addr = match ptr.checked_add(word_off) { + Some(addr) => addr, + None => return false, + }; + crate::syscall::read_user_u64(addr) + .map(|w| (w & (1u64 << bit)) != 0) + .unwrap_or(false) +} + +fn fdset_clear_all(ptr: u64, len: u64) -> Result<(), u64> { + let zero = [0u8; 64]; + let mut written = 0u64; + while written < len { + let chunk = core::cmp::min((len - written) as usize, zero.len()); + crate::syscall::copy_to_user(ptr + written, &zero[..chunk])?; + written += chunk as u64; + } + Ok(()) +} + +fn fdset_set(ptr: u64, fd: u64) -> Result<(), u64> { + let word_off = (fd / 64) * 8; + let bit = (fd % 64) as u32; + let addr = ptr.checked_add(word_off).ok_or(EFAULT)?; + let v = crate::syscall::read_user_u64(addr)?; + crate::syscall::write_user_u64(addr, v | (1u64 << bit)) +} + +#[inline] +fn current_pid() -> Option { + crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id())) +} + +/// Getppid システムコール +pub fn getppid() -> u64 { + let pid = match current_pid() { + Some(p) => p, + None => return 0, + }; + crate::task::with_process(pid, |p| { + p.parent_id().map(|ppid| ppid.as_u64()).unwrap_or(1) + }) + .unwrap_or(1) +} + +/// Getpgid システムコール +/// +/// pid=0 の場合は呼び出しプロセス自身のグループ ID を返す。 +pub fn getpgid(pid_arg: u64) -> u64 { + let target_pid = if pid_arg == 0 { + match current_pid() { + Some(p) => p, + None => return ESRCH, + } + } else { + crate::task::ids::ProcessId::from_u64(pid_arg) + }; + + crate::task::with_process(target_pid, |p| p.pgid()).unwrap_or_else(|| ESRCH) +} + +/// Setpgid システムコール +/// +/// pid=0 は自プロセス、pgid=0 はプロセス自身の PID を使用する。 +pub fn setpgid(pid_arg: u64, pgid_arg: u64) -> u64 { + let caller = match current_pid() { + Some(p) => p, + None => return ESRCH, + }; + let target_pid = if pid_arg == 0 { + caller + } else { + crate::task::ids::ProcessId::from_u64(pid_arg) + }; + + // 呼び出し元は自分自身または直接の子プロセスのみ変更可能 + let is_child = if target_pid != caller { + crate::task::with_process(target_pid, |p| p.parent_id() == Some(caller)).unwrap_or(false) + } else { + true + }; + if !is_child { + return EPERM; + } + + let new_pgid = if pgid_arg == 0 { + target_pid.as_u64() + } else { + pgid_arg + }; + + match crate::task::with_process_mut(target_pid, |p| { + p.set_pgid(new_pgid); + }) { + Some(()) => SUCCESS, + None => ESRCH, + } +} + +/// Setsid システムコール +/// +/// 新しいセッションを作成し、呼び出しプロセスがそのリーダーになる。 +/// sid = pgid = pid に設定する。 +pub fn setsid() -> u64 { + let pid = match current_pid() { + Some(p) => p, + None => return ESRCH, + }; + let pid_val = pid.as_u64(); + crate::task::with_process_mut(pid, |p| { + p.set_pgid(pid_val); + p.set_sid(pid_val); + pid_val + }) + .unwrap_or_else(|| ESRCH) +} + +/// Getsid システムコール +pub fn getsid(pid_arg: u64) -> u64 { + let target_pid = if pid_arg == 0 { + match current_pid() { + Some(p) => p, + None => return ESRCH, + } + } else { + crate::task::ids::ProcessId::from_u64(pid_arg) + }; + + crate::task::with_process(target_pid, |p| p.sid()).unwrap_or_else(|| ESRCH) +} + +/// ioctl システムコール +/// +/// 対応コマンド: +/// - TIOCGPGRP (0x540f): フォアグラウンドプロセスグループを取得 +/// - TIOCSPGRP (0x5410): フォアグラウンドプロセスグループを設定 +/// - TIOCGWINSZ (0x5413): ウィンドウサイズ取得 +/// - TCGETS (0x5401): termios 取得 +/// - TCSETS/TCSETSW/TCSETSF (0x5402-0x5404): termios 設定 +pub fn ioctl(fd: u64, request: u64, arg: u64) -> u64 { + const TIOCGPGRP: u64 = 0x540f; + const TIOCSPGRP: u64 = 0x5410; + const TIOCGWINSZ: u64 = 0x5413; + const TCGETS: u64 = 0x5401; + const TCSETS: u64 = 0x5402; + const TCSETSW: u64 = 0x5403; + const TCSETSF: u64 = 0x5404; + const TCGETA: u64 = 0x5405; + const TCSETA: u64 = 0x5406; + const TCSETAW: u64 = 0x5407; + const TCSETAF: u64 = 0x5408; + const TCGETS2: u64 = 0x802C_542A; + const TCSETS2: u64 = 0x402C_542B; + const TCSETSW2: u64 = 0x402C_542C; + const TCSETSF2: u64 = 0x402C_542D; + // newlib(sysvi386) の termios が使う ioctl 番号 + const XCGETA: u64 = ((b'x' as u64) << 8) | 1; + const XCSETA: u64 = ((b'x' as u64) << 8) | 2; + const XCSETAW: u64 = ((b'x' as u64) << 8) | 3; + const XCSETAF: u64 = ((b'x' as u64) << 8) | 4; + const TIOCSWINSZ: u64 = 0x5414; + const FIONREAD: u64 = 0x541b; + + match request { + TIOCGPGRP => { + if arg == 0 || !crate::syscall::validate_user_ptr(arg, 4) { + return EINVAL; + } + let pgid = match current_pid() { + Some(pid) => crate::task::with_process(pid, |p| p.pgid()).unwrap_or(1), + None => return EINVAL, + }; + crate::syscall::write_user_u32(arg, pgid as u32) + .map(|_| SUCCESS) + .unwrap_or_else(|e| e) + } + TIOCSPGRP => ENOTSUP, + TIOCGWINSZ => crate::syscall::tty::get_winsize(arg), + TIOCSWINSZ => ENOTSUP, + TCGETS | TCGETS2 => crate::syscall::tty::tcgets(arg), + XCGETA => crate::syscall::tty::tcgeta(arg), + TCGETA => crate::syscall::tty::tcgeta(arg), + TCSETS | TCSETSW | TCSETSF | TCSETS2 | TCSETSW2 | TCSETSF2 => ENOTSUP, + XCSETA | XCSETAW | XCSETAF => ENOTSUP, + TCSETA | TCSETAW | TCSETAF => ENOTSUP, + FIONREAD => { + if arg == 0 || !crate::syscall::validate_user_ptr(arg, 4) { + return EINVAL; + } + let n = crate::syscall::tty::pending_input_len() as u32; + crate::syscall::write_user_u32(arg, n) + .map(|_| SUCCESS) + .unwrap_or_else(|e| e) + } + _ => EINVAL, + } +} + +/// mprotect システムコール +/// +/// x86_64 の現在のユーザー保護モデルでは READ は常に許可単位になるため、 +/// ここでは READ/WRITE/EXEC の組み合わせのうち W+X を拒否しつつ、 +/// 既存マッピングの WRITABLE / NX を更新する。 +pub fn mprotect(addr: u64, len: u64, prot: u64) -> u64 { + const PROT_READ: u64 = 0x1; + const PROT_WRITE: u64 = 0x2; + const PROT_EXEC: u64 = 0x4; + const SUPPORTED_MASK: u64 = PROT_READ | PROT_WRITE | PROT_EXEC; + const USER_SPACE_END: u64 = 0x0000_7FFF_FFFF_FFFF; + + if len == 0 { + return SUCCESS; + } + if (prot & !SUPPORTED_MASK) != 0 { + return EINVAL; + } + if addr == 0 || addr > USER_SPACE_END { + return EINVAL; + } + + let end_inclusive = match addr.checked_add(len.saturating_sub(1)) { + Some(v) if v <= USER_SPACE_END => v, + _ => return EINVAL, + }; + let start = addr & !0xfffu64; + let length = match (end_inclusive & !0xfffu64) + .checked_add(4096) + .and_then(|end| end.checked_sub(start)) + { + Some(v) if v != 0 => v, + _ => return EINVAL, + }; + + let present = prot != 0; + let writable = (prot & PROT_WRITE) != 0; + let executable = (prot & PROT_EXEC) != 0; + if present && writable && executable { + return EINVAL; + } + + let pid = match current_pid() { + Some(p) => p, + None => return ESRCH, + }; + let table_phys = match crate::task::with_process(pid, |p| p.page_table()).flatten() { + Some(pt) => pt, + None => return EINVAL, + }; + + match crate::mem::paging::protect_user_range_in_table( + table_phys, start, length, present, writable, executable, + ) { + Ok(()) => SUCCESS, + Err(crate::Kernel::Memory(crate::result::Memory::NotMapped)) => EFAULT, + Err(crate::Kernel::Memory(crate::result::Memory::OutOfMemory)) => ENOMEM, + Err(crate::Kernel::Memory(crate::result::Memory::PermissionDenied)) + | Err(crate::Kernel::Memory(crate::result::Memory::InvalidAddress)) + | Err(crate::Kernel::Memory(crate::result::Memory::AlignmentError)) + | Err(crate::Kernel::InvalidParam) => EINVAL, + Err(_) => EFAULT, + } +} + +/// poll システムコール(最小実装) +/// +/// TTY fd の read/write readiness を返す。 +pub fn poll(fds_ptr: u64, nfds: u64, timeout_arg: u64) -> u64 { + const POLLFD_SIZE: u64 = 8; // i32 fd, i16 events, i16 revents + if nfds == 0 { + return 0; + } + let total = match nfds.checked_mul(POLLFD_SIZE) { + Some(v) => v, + None => return EINVAL, + }; + if fds_ptr == 0 || !crate::syscall::validate_user_ptr(fds_ptr, total) { + return EFAULT; + } + let timeout_ms = i64::from_ne_bytes(timeout_arg.to_ne_bytes()); + + let mut eval_ready = || -> u64 { + let mut ready_count = 0u64; + for i in 0..nfds { + let base = fds_ptr + i * POLLFD_SIZE; + let fd = match crate::syscall::read_user_i32(base) { + Ok(fd) => fd, + Err(_) => continue, + }; + let events = match crate::syscall::read_user_u16(base + 4) { + Ok(events) => events, + Err(_) => continue, + }; + let mut revents: u16 = 0; + if fd >= 0 { + if (events & (POLLIN | POLLRDNORM)) != 0 && stdin_ready_for_fd(fd) { + revents |= POLLIN; + } + if (events & (POLLOUT | POLLWRNORM)) != 0 && stdout_ready(fd) { + revents |= POLLOUT; + } + } + if crate::syscall::write_user_u16(base + 6, revents).is_err() { + continue; + } + if revents != 0 { + ready_count += 1; + } + } + ready_count + }; + + let initial = eval_ready(); + if initial > 0 { + return initial; + } + let woke = wait_until_ready_or_timeout(timeout_ms, || eval_ready() > 0); + if !woke { + return 0; + } + eval_ready() +} + +/// ppoll システムコール(最小実装) +/// +/// timeout は timespec*。sigmask/sigsetsize は現状未使用。 +pub fn ppoll( + fds_ptr: u64, + nfds: u64, + timeout_ptr: u64, + _sigmask_ptr: u64, + _sigsetsize: u64, +) -> u64 { + let timeout_ms_u64 = if timeout_ptr == 0 { + u64::MAX + } else { + if !crate::syscall::validate_user_ptr(timeout_ptr, 16) { + return EFAULT; + } + let sec = match crate::syscall::read_user_i64(timeout_ptr) { + Ok(v) => v, + Err(e) => return e, + }; + let nsec = match crate::syscall::read_user_i64(timeout_ptr + 8) { + Ok(v) => v, + Err(e) => return e, + }; + if sec < 0 || nsec < 0 || nsec >= 1_000_000_000 { + return EINVAL; + } + (sec as u64) + .saturating_mul(1000) + .saturating_add((nsec as u64) / 1_000_000) + }; + poll(fds_ptr, nfds, timeout_ms_u64) +} + +/// pselect6/select システムコール(最小実装) +/// +/// readfds/writefds のうち TTY fd の readiness を判定する。 +pub fn pselect6( + nfds: u64, + readfds_ptr: u64, + writefds_ptr: u64, + _exceptfds_ptr: u64, + timeout_ptr: u64, + _sigmask_ptr: u64, +) -> u64 { + let mut timeout_ms = -1i64; + if timeout_ptr != 0 { + if !crate::syscall::validate_user_ptr(timeout_ptr, 16) { + return EFAULT; + } + let sec = match crate::syscall::read_user_i64(timeout_ptr) { + Ok(v) => v, + Err(e) => return e, + }; + let nsec = match crate::syscall::read_user_i64(timeout_ptr + 8) { + Ok(v) => v, + Err(e) => return e, + }; + if sec < 0 || nsec < 0 || nsec >= 1_000_000_000 { + return EINVAL; + } + timeout_ms = sec.saturating_mul(1000).saturating_add(nsec / 1_000_000); + } + + let set_len = match fdset_len_bytes(nfds) { + Some(v) => v, + None => return EINVAL, + }; + if readfds_ptr != 0 && !crate::syscall::validate_user_ptr(readfds_ptr, set_len) { + return EFAULT; + } + if writefds_ptr != 0 && !crate::syscall::validate_user_ptr(writefds_ptr, set_len) { + return EFAULT; + } + + let read_interest = if readfds_ptr != 0 { + let mut v: alloc::vec::Vec = alloc::vec::Vec::new(); + for fd in 0..nfds { + if fdset_test(readfds_ptr, fd) { + v.push(fd); + } + } + v + } else { + alloc::vec::Vec::new() + }; + let write_interest = if writefds_ptr != 0 { + let mut v: alloc::vec::Vec = alloc::vec::Vec::new(); + for fd in 0..nfds { + if fdset_test(writefds_ptr, fd) { + v.push(fd); + } + } + v + } else { + alloc::vec::Vec::new() + }; + + let mut eval_ready = || -> u64 { + let mut ready_count = 0u64; + if readfds_ptr != 0 { + let mut ready_fds: alloc::vec::Vec = alloc::vec::Vec::new(); + for &fd in &read_interest { + if stdin_ready_for_fd(fd as i32) { + ready_fds.push(fd); + } + } + if fdset_clear_all(readfds_ptr, set_len).is_err() { + return 0; + } + for fd in ready_fds { + if fdset_set(readfds_ptr, fd).is_ok() { + ready_count += 1; + } + } + } + if writefds_ptr != 0 { + let mut ready_fds: alloc::vec::Vec = alloc::vec::Vec::new(); + for &fd in &write_interest { + if stdout_ready(fd as i32) { + ready_fds.push(fd); + } + } + if fdset_clear_all(writefds_ptr, set_len).is_err() { + return 0; + } + for fd in ready_fds { + if fdset_set(writefds_ptr, fd).is_ok() { + ready_count += 1; + } + } + } + ready_count + }; + + let initial = eval_ready(); + if initial > 0 { + return initial; + } + let woke = wait_until_ready_or_timeout(timeout_ms, || eval_ready() > 0); + if !woke { + if readfds_ptr != 0 { + let _ = fdset_clear_all(readfds_ptr, set_len); + } + if writefds_ptr != 0 { + let _ = fdset_clear_all(writefds_ptr, set_len); + } + return 0; + } + eval_ready() +} + +/// access システムコール(ファイルアクセス可能性チェック) +/// +/// initfs/rootfs にファイルが存在すれば常に成功を返す。 +pub fn access(path_ptr: u64, _mode: u64) -> u64 { + use super::types::ENOENT; + if path_ptr == 0 { + return EINVAL; + } + let path = match crate::syscall::read_user_cstring(path_ptr, 1024) { + Ok(s) => s, + Err(e) => return e, + }; + if crate::init::fs::file_metadata(&path).is_some() { + SUCCESS + } else { + ENOENT + } +} + +/// getuid / geteuid / getgid / getegid システムコール(常に 0 = root を返す) +pub fn getuid() -> u64 { + 0 +} +pub fn getgid() -> u64 { + 0 +} +pub fn geteuid() -> u64 { + 0 +} +pub fn getegid() -> u64 { + 0 +} + +/// uname システムコール +/// +/// struct utsname のレイアウト (Linux x86_64): 各フィールド 65 バイト × 6 = 390 バイト +/// sysname, nodename, release, version, machine, domainname +pub fn uname(buf_ptr: u64) -> u64 { + const FIELD_LEN: usize = 65; + const UTSNAME_SIZE: u64 = (FIELD_LEN * 6) as u64; + if buf_ptr == 0 || !crate::syscall::validate_user_ptr(buf_ptr, UTSNAME_SIZE) { + return EINVAL; + } + let fields: [&[u8]; 6] = [ + b"mochiOS", // sysname + b"mochi", // nodename + b"0.1.0", // release + b"mochiOS 0.1.0", // version + b"x86_64", // machine + b"", // domainname + ]; + let mut buf = [0u8; UTSNAME_SIZE as usize]; + for (i, f) in fields.iter().enumerate() { + let off = i * FIELD_LEN; + let n = f.len().min(FIELD_LEN - 1); + buf[off..off + n].copy_from_slice(&f[..n]); + } + crate::syscall::copy_to_user(buf_ptr, &buf) + .map(|_| SUCCESS) + .unwrap_or_else(|e| e) +} + +/// nanosleep システムコール +/// +/// struct timespec { tv_sec: i64, tv_nsec: i64 } を受け取りスリープする。 +pub fn nanosleep(req_ptr: u64, _rem_ptr: u64) -> u64 { + if req_ptr == 0 || !crate::syscall::validate_user_ptr(req_ptr, 16) { + return EINVAL; + } + let secs = match crate::syscall::read_user_i64(req_ptr) { + Ok(v) => v, + Err(e) => return e, + }; + let nsecs = match crate::syscall::read_user_i64(req_ptr + 8) { + Ok(v) => v, + Err(e) => return e, + }; + if secs < 0 || nsecs < 0 || nsecs >= 1_000_000_000 { + return EINVAL; + } + let total_ms = (secs as u64) * 1000 + (nsecs as u64) / 1_000_000; + if total_ms > 0 { + crate::syscall::process::sleep(total_ms); + } + SUCCESS +} + +/// getrlimit システムコール(リソース上限を無限大で返す) +pub fn getrlimit(_resource: u64, rlim_ptr: u64) -> u64 { + if rlim_ptr == 0 || !crate::syscall::validate_user_ptr(rlim_ptr, 16) { + return EINVAL; + } + let mut buf = [0u8; 16]; + buf[..8].copy_from_slice(&u64::MAX.to_ne_bytes()); + buf[8..].copy_from_slice(&u64::MAX.to_ne_bytes()); + crate::syscall::copy_to_user(rlim_ptr, &buf) + .map(|_| SUCCESS) + .unwrap_or_else(|e| e) +} + +/// prlimit64 システムコール(スタブ: 無限大を返し、設定を無視) +pub fn prlimit64(_pid: u64, _resource: u64, _new_limit: u64, old_limit: u64) -> u64 { + if old_limit != 0 { + return getrlimit(0, old_limit); + } + SUCCESS +} + +/// set_tid_address システムコール +/// +/// musl libc の初期化で呼ばれる。現在のスレッド ID を返す。 +pub fn set_tid_address(_tidptr: u64) -> u64 { + match crate::task::current_thread_id() { + Some(tid) => tid.as_u64(), + None => 1, + } +} diff --git a/src/core/syscall/pipe.rs b/src/core/syscall/pipe.rs new file mode 100644 index 0000000..058f84a --- /dev/null +++ b/src/core/syscall/pipe.rs @@ -0,0 +1,283 @@ +//! パイプの実装 +//! +//! グローバルな PIPE_TABLE を使い、FD テーブルの FileHandle から参照する。 +//! 読み込み端がブロックする場合は KEYBOARD_WAITER と同様に wake_thread を使う。 + +use crate::interrupt::spinlock::SpinLock; +use core::sync::atomic::{AtomicU64, Ordering}; + +/// パイプバッファのサイズ(64 KiB) +const PIPE_BUF_SIZE: usize = 65536; + +/// 同時に存在できるパイプの最大数 +const MAX_PIPES: usize = 64; + +/// パイプバッファ +pub struct PipeBuffer { + buf: [u8; PIPE_BUF_SIZE], + /// リングバッファの書き込み位置 + write_pos: usize, + /// リングバッファの読み込み位置 + read_pos: usize, + /// バッファ内のデータ量 + len: usize, + /// 読み込み端を開いている FD の参照カウント + pub read_refs: u32, + /// 書き込み端を開いている FD の参照カウント + pub write_refs: u32, + /// 読み込み待ちスレッド ID(ブロッキング読み込み用) + waiter: AtomicU64, +} + +impl PipeBuffer { + const fn new() -> Self { + Self { + buf: [0u8; PIPE_BUF_SIZE], + write_pos: 0, + read_pos: 0, + len: 0, + read_refs: 0, + write_refs: 0, + waiter: AtomicU64::new(0), + } + } + + /// バッファにデータを書き込む。書き込んだバイト数を返す。 + pub fn write_bytes(&mut self, data: &[u8]) -> usize { + let avail = PIPE_BUF_SIZE - self.len; + let to_write = core::cmp::min(data.len(), avail); + for i in 0..to_write { + self.buf[self.write_pos] = data[i]; + self.write_pos = (self.write_pos + 1) % PIPE_BUF_SIZE; + } + self.len += to_write; + to_write + } + + /// バッファからデータを読み取る。読み取ったバイト数を返す。 + pub fn read_bytes(&mut self, dst: &mut [u8]) -> usize { + let to_read = core::cmp::min(dst.len(), self.len); + for i in 0..to_read { + dst[i] = self.buf[self.read_pos]; + self.read_pos = (self.read_pos + 1) % PIPE_BUF_SIZE; + } + self.len -= to_read; + to_read + } + + pub fn available(&self) -> usize { + self.len + } + pub fn is_full(&self) -> bool { + self.len == PIPE_BUF_SIZE + } + + /// 読み込み待ちスレッドを登録する + pub fn set_waiter(&self, tid: u64) { + self.waiter.store(tid, Ordering::SeqCst); + } + + /// 読み込み待ちスレッドを解除する + pub fn clear_waiter(&self) { + self.waiter.store(0, Ordering::SeqCst); + } + + /// 待機中のスレッドを起こす + pub fn wake_reader(&self) { + let tid = self.waiter.load(Ordering::SeqCst); + if tid != 0 { + crate::task::wake_thread(crate::task::ids::ThreadId::from_u64(tid)); + } + } +} + +/// グローバルパイプテーブル +/// SpinLock でガードした Option の配列 +static PIPE_TABLE: SpinLock<[Option; MAX_PIPES]> = { + const INIT: Option = None; + SpinLock::new([INIT; MAX_PIPES]) +}; + +/// 新しいパイプを確保してパイプ ID を返す。失敗した場合は None。 +pub fn alloc_pipe() -> Option { + let mut table = PIPE_TABLE.lock(); + for (i, slot) in table.iter_mut().enumerate() { + if slot.is_none() { + let mut pb = PipeBuffer::new(); + pb.read_refs = 1; + pb.write_refs = 1; + *slot = Some(pb); + return Some(i); + } + } + None +} + +/// パイプの書き込み端を閉じる(write_refs をデクリメントし 0 になったら待機中スレッドを起こす) +pub fn close_write_end(id: usize) { + let mut table = PIPE_TABLE.lock(); + if let Some(Some(pb)) = table.get_mut(id) { + if pb.write_refs > 0 { + pb.write_refs -= 1; + } + if pb.write_refs == 0 { + pb.wake_reader(); + } + if pb.read_refs == 0 && pb.write_refs == 0 { + table[id] = None; + } + } +} + +/// パイプの読み込み端を閉じる +pub fn close_read_end(id: usize) { + let mut table = PIPE_TABLE.lock(); + if let Some(Some(pb)) = table.get_mut(id) { + if pb.read_refs > 0 { + pb.read_refs -= 1; + } + if pb.read_refs == 0 && pb.write_refs == 0 { + table[id] = None; + } + } +} + +/// パイプに書き込む。バッファが満杯の場合は EAGAIN(簡易実装: ノンブロッキング)。 +/// 書き込んだバイト数を返す。 +pub fn pipe_write(id: usize, data: &[u8]) -> Result { + use super::types::{EAGAIN, EPIPE}; + let mut table = PIPE_TABLE.lock(); + match table.get_mut(id).and_then(|s| s.as_mut()) { + None => Err(EPIPE), + Some(pb) => { + if pb.read_refs == 0 { + return Err(EPIPE); + } + if pb.is_full() { + return Err(EAGAIN); + } + let n = pb.write_bytes(data); + pb.wake_reader(); + Ok(n) + } + } +} + +/// パイプから読み取る(ブロッキング)。 +/// データがあれば即座に返し、なければ書き込み端が閉じられるまで待機する。 +pub fn pipe_read_blocking(id: usize, dst: &mut [u8]) -> usize { + loop { + { + let mut table = PIPE_TABLE.lock(); + if let Some(Some(pb)) = table.get_mut(id) { + if pb.available() > 0 { + return pb.read_bytes(dst); + } + if pb.write_refs == 0 { + // 書き込み端がすべて閉じられ、データなし → EOF + return 0; + } + // 待機登録 + if let Some(tid) = crate::task::current_thread_id() { + pb.set_waiter(tid.as_u64()); + } + } else { + return 0; // パイプが消えた → EOF + } + } + // ロック解放後にスリープ + if let Some(tid) = crate::task::current_thread_id() { + crate::task::sleep_thread_unless_woken(tid); + crate::task::yield_now(); + } + + // ウェイターをクリアしてリトライ + { + let table = PIPE_TABLE.lock(); + if let Some(Some(pb)) = table.get(id) { + pb.clear_waiter(); + } + } + } +} + +/// pipe(2) システムコール: pipefd[0]=読み込み端, pipefd[1]=書き込み端 +pub fn pipe_syscall(pipefd_ptr: u64) -> u64 { + pipe2_syscall(pipefd_ptr, 0) +} + +/// pipe2(2) システムコール(flags: O_CLOEXEC / O_NONBLOCK に部分対応) +pub fn pipe2_syscall(pipefd_ptr: u64, flags: u64) -> u64 { + use super::types::EFAULT; + use crate::task::fd_table::{FileHandle, O_CLOEXEC}; + + const EMFILE_VAL: u64 = (-24i64) as u64; + + if pipefd_ptr == 0 || !crate::syscall::validate_user_ptr(pipefd_ptr, 8) { + return EFAULT; + } + + let pipe_id = match alloc_pipe() { + Some(id) => id, + None => return EMFILE_VAL, + }; + let cloexec = (flags & O_CLOEXEC) != 0; + + let pid = match crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id().as_u64())) + { + Some(p) => p, + None => { + close_read_end(pipe_id); + close_write_end(pipe_id); + return EFAULT; + } + }; + + let read_handle = alloc::boxed::Box::new(FileHandle { + data: alloc::boxed::Box::new([]), + pos: 0, + dir_path: None, + is_remote: false, + fd_remote: 0, + remote_refs: None, + pipe_id: Some(pipe_id), + pipe_write: false, + open_flags: 0, + }); + let write_handle = alloc::boxed::Box::new(FileHandle { + data: alloc::boxed::Box::new([]), + pos: 0, + dir_path: None, + is_remote: false, + fd_remote: 0, + remote_refs: None, + pipe_id: Some(pipe_id), + pipe_write: true, + open_flags: 1, + }); + + let pid_id = crate::task::ids::ProcessId::from_u64(pid); + let read_fd = + crate::task::with_process_mut(pid_id, |p| p.fd_table_mut().alloc(read_handle, cloexec)) + .flatten(); + let write_fd = + crate::task::with_process_mut(pid_id, |p| p.fd_table_mut().alloc(write_handle, cloexec)) + .flatten(); + + match (read_fd, write_fd) { + (Some(rfd), Some(wfd)) => { + let mut fds = [0u8; 8]; + fds[..4].copy_from_slice(&(rfd as u32).to_ne_bytes()); + fds[4..].copy_from_slice(&(wfd as u32).to_ne_bytes()); + crate::syscall::copy_to_user(pipefd_ptr, &fds) + .map(|_| super::types::SUCCESS) + .unwrap_or_else(|e| e) + } + _ => { + close_read_end(pipe_id); + close_write_end(pipe_id); + EMFILE_VAL + } + } +} diff --git a/src/core/syscall/privileged.rs b/src/core/syscall/privileged.rs new file mode 100644 index 0000000..0090acb --- /dev/null +++ b/src/core/syscall/privileged.rs @@ -0,0 +1,517 @@ +//! 特権システムコール(Service権限プロセス専用) +//! +//! これらのsyscallはPrivilegeLevel::Serviceのプロセスのみ呼び出し可能。 +//! 物理メモリ直接操作、ゼロコピーIO等の実装に使用する。 + +use super::types::{EFAULT, EINVAL, EPERM}; +use crate::task::ids::PrivilegeLevel; +use alloc::vec::Vec; +use x86_64::instructions::tlb; +use x86_64::VirtAddr; + +/// 現在スレッドのプロセス権限レベルを取得 +fn current_process_privilege() -> Option { + crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id())) + .and_then(|pid| crate::task::with_process(pid, |p| p.privilege())) +} + +/// Service権限チェック +fn require_service_privilege() -> Result<(), u64> { + match current_process_privilege() { + Some(PrivilegeLevel::Core) | Some(PrivilegeLevel::Service) => Ok(()), + _ => Err(EPERM), + } +} + +fn deallocate_frames(phys_addrs: &[u64]) { + for &phys in phys_addrs { + use x86_64::{ + structures::paging::{PhysFrame, Size4KiB}, + PhysAddr, + }; + if let Some(frame) = PhysFrame::::from_start_address(PhysAddr::new(phys)).ok() { + let _ = crate::mem::frame::deallocate_frame(frame); + } + } +} + +fn is_allowed_phys_page(phys_addr: u64) -> bool { + (phys_addr & 0xfff) == 0 && crate::mem::frame::is_usable_physical_address(phys_addr) +} + +fn shared_page_limit() -> u64 { + crate::mem::frame::get_memory_info() + .map(|(_, frames)| (frames as u64).max(128)) + .unwrap_or(128) +} + +fn map_phys_pages_into_target( + target_thread_id: u64, + phys_pages: &[u64], + virt_addr_hint: u64, +) -> Result { + if phys_pages.is_empty() || phys_pages.len() as u64 > shared_page_limit() { + return Err(EINVAL); + } + for &phys_addr in phys_pages { + if !is_allowed_phys_page(phys_addr) { + return Err(EINVAL); + } + } + + let target_pid = crate::task::thread_to_process_id(target_thread_id).ok_or(EINVAL)?; + let page_span = (phys_pages.len() as u64) + .checked_mul(0x1000) + .ok_or(EINVAL)?; + let (virt_addr, page_table, reserved_heap_old, reserved_heap_new) = if virt_addr_hint != 0 { + if virt_addr_hint & 0xfff != 0 { + return Err(EINVAL); + } + let pt = crate::task::with_process(target_pid, |p| p.page_table()) + .flatten() + .ok_or(EINVAL)?; + (virt_addr_hint, pt, None, None) + } else { + let (virt_addr, pt, old_end, new_end) = crate::task::with_process_mut(target_pid, |p| { + let base = if p.heap_end() == 0 { + 0x6000_0000_0000u64 + } else { + p.heap_end() + }; + let virt_addr = base + .checked_add(0xfff) + .map(|v| v & !0xfffu64) + .ok_or(EINVAL)?; + let new_end = virt_addr.checked_add(page_span).ok_or(EINVAL)?; + let pt = p.page_table().ok_or(EINVAL)?; + let old_end = p.heap_end(); + p.set_heap_end(new_end); + Ok::<(u64, u64, u64, u64), u64>((virt_addr, pt, old_end, new_end)) + }) + .ok_or(EINVAL)??; + (virt_addr, pt, Some(old_end), Some(new_end)) + }; + + for (i, &phys_addr) in phys_pages.iter().enumerate() { + let target_virt = virt_addr + (i as u64 * 0x1000); + if crate::mem::paging::map_page_in_table(page_table, target_virt, phys_addr, true, true) + .is_err() + { + for j in 0..i { + let rollback_virt = virt_addr + (j as u64 * 0x1000); + let _ = crate::mem::paging::unmap_page_in_table(page_table, rollback_virt); + } + if let (Some(old_end), Some(new_end)) = (reserved_heap_old, reserved_heap_new) { + let _ = crate::task::with_process_mut(target_pid, |p| { + if p.heap_end() == new_end { + p.set_heap_end(old_end); + } + }); + } + return Err(EFAULT); + } + } + + Ok(virt_addr) +} + +/// 物理ページ配列をターゲットプロセスのアドレス空間にマップ +/// +/// # Arguments +/// * arg0: target_thread_id - マップ先のスレッドID +/// * arg1: phys_pages_ptr - 物理ページアドレス配列へのポインタ (u64配列) +/// * arg2: page_count - ページ数 +/// * arg3: virt_addr_hint - 仮想アドレスのヒント (0=自動割り当て) +/// +/// # Returns +/// 成功時: マップされた仮想アドレス +/// エラー時: 負のエラーコード +pub fn map_physical_pages( + target_thread_id: u64, + phys_pages_ptr: u64, + page_count: u64, + virt_addr_hint: u64, +) -> u64 { + // 権限チェック + if let Err(e) = require_service_privilege() { + return e; + } + + // パラメータ検証 + if page_count == 0 || page_count > shared_page_limit() { + return EINVAL; + } + if phys_pages_ptr == 0 { + return EFAULT; + } + + let mut phys_pages = alloc::vec![0u64; page_count as usize]; + for i in 0..page_count as usize { + let addr = match phys_pages_ptr.checked_add((i * core::mem::size_of::()) as u64) { + Some(addr) => addr, + None => return EFAULT, + }; + match super::read_user_u64(addr) { + Ok(page) => phys_pages[i] = page, + Err(e) => return e, + } + } + + match map_phys_pages_into_target(target_thread_id, &phys_pages, virt_addr_hint) { + Ok(v) => v, + Err(e) => e, + } +} + +/// 仮想アドレスから物理アドレスを取得(Service権限強化版) +/// +/// # Arguments +/// * arg0: virt_addr - 仮想アドレス +/// * arg1: target_thread_id - 対象スレッドID (0=自プロセス) +/// +/// # Returns +/// 成功時: 物理アドレス +/// エラー時: 負のエラーコード +pub fn get_physical_addr(virt_addr: u64, target_thread_id: u64) -> u64 { + // 権限チェック + if let Err(e) = require_service_privilege() { + return e; + } + + if virt_addr == 0 { + return EINVAL; + } + + let pid = if target_thread_id == 0 { + // 自プロセス + match crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id())) + { + Some(pid) => pid, + None => return EINVAL, + } + } else { + // 指定スレッドのプロセス + match crate::task::thread_to_process_id(target_thread_id) { + Some(pid) => pid, + None => return EINVAL, + } + }; + + let page_table = match crate::task::with_process(pid, |p| p.page_table()) { + Some(Some(pt)) => pt, + _ => return EINVAL, + }; + + match crate::mem::paging::virt_to_phys_in_table(page_table, virt_addr) { + Some(phys) => phys, + None => EFAULT, + } +} + +/// 共有用物理ページを割り当て、自プロセスにマップして物理アドレスを返す +/// +/// # Arguments +/// * arg0: page_count - 割り当てるページ数 +/// * arg1: phys_addrs_out - 物理アドレス配列を書き込むユーザー空間バッファ (u64配列) +/// * arg2: phys_addrs_len - arg1 バッファの要素数 +/// * arg3: virt_addr_hint - 仮想アドレスのヒント (0=自動割り当て) +/// +/// # Returns +/// 成功時: マップされた仮想アドレス +/// エラー時: 負のエラーコード +pub fn alloc_shared_pages( + page_count: u64, + phys_addrs_out: u64, + phys_addrs_len: u64, + virt_addr_hint: u64, +) -> u64 { + // 権限チェック + if let Err(e) = require_service_privilege() { + return e; + } + + // パラメータ検証 + if page_count == 0 || page_count > shared_page_limit() { + return EINVAL; + } + if phys_addrs_out != 0 && phys_addrs_len < page_count { + return EINVAL; + } + + // 物理ページを割り当て + let mut phys_pages = alloc::vec![]; + for _ in 0..page_count { + match crate::mem::frame::allocate_frame() { + Ok(frame) => phys_pages.push(frame.start_address().as_u64()), + Err(_) => { + deallocate_frames(&phys_pages); + return super::types::ENOMEM; + } + } + } + + // 自プロセスのページテーブルを取得 + let self_pid = match crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id())) + { + Some(pid) => pid, + None => { + deallocate_frames(&phys_pages); + return EINVAL; + } + }; + + let page_span = match page_count.checked_mul(0x1000) { + Some(v) => v, + None => { + deallocate_frames(&phys_pages); + return EINVAL; + } + }; + + // 仮想アドレス決定 + let (virt_addr, page_table, reserved_heap_old, reserved_heap_new) = if virt_addr_hint != 0 { + if virt_addr_hint & 0xfff != 0 { + deallocate_frames(&phys_pages); + return EINVAL; + } + let pt = match crate::task::with_process(self_pid, |p| p.page_table()) { + Some(Some(v)) => v, + _ => { + deallocate_frames(&phys_pages); + return EINVAL; + } + }; + (virt_addr_hint, pt, None, None) + } else { + // 自動割り当て: alloc_shared_pages は「自己プロセス内共有ページ」向けに + // 0x7000_0000_0000 帯を使用する。 + match crate::task::with_process_mut(self_pid, |p| { + let base = if p.heap_end() == 0 { + 0x7000_0000_0000u64 + } else { + p.heap_end() + }; + let virt_addr = base + .checked_add(0xfff) + .map(|v| v & !0xfffu64) + .ok_or(EINVAL)?; + let new_end = virt_addr.checked_add(page_span).ok_or(EINVAL)?; + let pt = p.page_table().ok_or(EINVAL)?; + let old_end = p.heap_end(); + p.set_heap_end(new_end); + Ok((virt_addr, pt, old_end, new_end)) + }) { + Some(Ok(v)) => (v.0, v.1, Some(v.2), Some(v.3)), + Some(Err(e)) => { + deallocate_frames(&phys_pages); + return e; + } + None => { + deallocate_frames(&phys_pages); + return EINVAL; + } + } + }; + + // 各物理ページをマップ + for (i, &phys_addr) in phys_pages.iter().enumerate() { + let target_virt = virt_addr + (i as u64 * 0x1000); + if let Err(_) = + crate::mem::paging::map_page_in_table(page_table, target_virt, phys_addr, true, true) + { + // マップ失敗時はロールバック + for j in 0..i { + let rollback_virt = virt_addr + (j as u64 * 0x1000); + let _ = crate::mem::paging::unmap_page_in_table(page_table, rollback_virt); + } + if let (Some(old_end), Some(new_end)) = (reserved_heap_old, reserved_heap_new) { + let _ = crate::task::with_process_mut(self_pid, |p| { + if p.heap_end() == new_end { + p.set_heap_end(old_end); + } + }); + } + deallocate_frames(&phys_pages); + return EFAULT; + } + } + + // 物理アドレス配列をユーザー空間へ書き込み + if phys_addrs_out != 0 { + let mut copy_bytes = alloc::vec![0u8; phys_pages.len() * core::mem::size_of::()]; + for (i, page) in phys_pages.iter().enumerate() { + let off = i * core::mem::size_of::(); + copy_bytes[off..off + 8].copy_from_slice(&page.to_ne_bytes()); + } + if let Err(errno) = super::copy_to_user(phys_addrs_out, ©_bytes) { + // ロールバック + for i in 0..phys_pages.len() { + let rollback_virt = virt_addr + (i as u64 * 0x1000); + let _ = crate::mem::paging::unmap_page_in_table(page_table, rollback_virt); + } + if let (Some(old_end), Some(new_end)) = (reserved_heap_old, reserved_heap_new) { + let _ = crate::task::with_process_mut(self_pid, |p| { + if p.heap_end() == new_end { + p.set_heap_end(old_end); + } + }); + } + deallocate_frames(&phys_pages); + return errno; + } + } + + virt_addr +} + +/// 物理ページをアンマップして解放 +/// +/// # Arguments +/// * arg0: virt_addr - アンマップする仮想アドレス(ページ境界) +/// * arg1: page_count - アンマップするページ数 +/// * arg2: deallocate - 1=物理ページも解放、0=アンマップのみ +/// +/// # Returns +/// 成功時: 0 +/// エラー時: 負のエラーコード +pub fn unmap_pages(virt_addr: u64, page_count: u64, deallocate: u64) -> u64 { + // 権限チェック + if let Err(e) = require_service_privilege() { + return e; + } + + // パラメータ検証 + if page_count == 0 || page_count > shared_page_limit() { + return EINVAL; + } + if virt_addr & 0xfff != 0 { + return EINVAL; + } + + // 自プロセスのページテーブルを取得 + let self_pid = match crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id())) + { + Some(pid) => pid, + None => return EINVAL, + }; + + let page_table = match crate::task::with_process(self_pid, |p| p.page_table()) { + Some(Some(pt)) => pt, + _ => return EINVAL, + }; + + // 物理アドレスを取得してからアンマップ + let mut phys_addrs = alloc::vec![]; + if deallocate != 0 { + for i in 0..page_count { + let target_virt = virt_addr + (i * 0x1000); + if let Some(phys) = crate::mem::paging::virt_to_phys_in_table(page_table, target_virt) { + phys_addrs.push(phys); + } + } + } + + // アンマップ + for i in 0..page_count { + let target_virt = virt_addr + (i * 0x1000); + let _ = crate::mem::paging::unmap_page_in_table(page_table, target_virt); + if let Ok(vaddr) = VirtAddr::try_new(target_virt) { + tlb::flush(vaddr); + } + } + + // 物理ページを解放 + if deallocate != 0 { + let mut freed: Vec = Vec::new(); + for phys in phys_addrs { + use x86_64::{ + structures::paging::{PhysFrame, Size4KiB}, + PhysAddr, + }; + let phys_aligned = phys & !0xfff; + if freed.iter().any(|p| *p == phys_aligned) { + continue; + } + freed.push(phys_aligned); + if let Some(frame) = + PhysFrame::::from_start_address(PhysAddr::new(phys_aligned)).ok() + { + let _ = crate::mem::frame::deallocate_frame(frame); + } + } + } + + 0 +} + +/// IPC経由で物理ページをターゲットプロセスへ送信 +/// +/// # Arguments +/// * arg0: dest_thread_id - 送信先スレッドID +/// * arg1: phys_pages_ptr - 物理ページアドレス配列へのポインタ (u64配列) +/// * arg2: page_count - ページ数 +/// * arg3: map_start - マップ先の仮想アドレスヒント (0=自動) +/// +/// # Returns +/// 成功時: 0 +/// エラー時: 負のエラーコード +pub fn ipc_send_pages( + dest_thread_id: u64, + phys_pages_ptr: u64, + page_count: u64, + map_start: u64, +) -> u64 { + // 権限チェック + if let Err(e) = require_service_privilege() { + return e; + } + + // パラメータ検証 + if dest_thread_id == 0 { + return EINVAL; + } + if page_count == 0 || page_count > shared_page_limit() { + return EINVAL; + } + if phys_pages_ptr == 0 { + return EFAULT; + } + + let mut phys_pages = alloc::vec![0u64; page_count as usize]; + for i in 0..page_count as usize { + let addr = match phys_pages_ptr.checked_add((i * core::mem::size_of::()) as u64) { + Some(addr) => addr, + None => return EFAULT, + }; + match super::read_user_u64(addr) { + Ok(page) => phys_pages[i] = page, + Err(e) => return e, + } + } + + // `map_start` is only a hint and is intentionally ignored. + // Rationale: + // - Avoids accidental/ABI-mismatch garbage in the 4th argument causing bogus mappings. + // - Mirrors `map_external_pages_for_receiver()` behaviour which ignores receiver hints for safety. + // - Prevents mapping shared pages onto unexpected addresses in the target process. + if map_start != 0 { + crate::debug!( + "[ipc_send_pages] ignoring map_start hint={:#x} (auto-placing mapping)", + map_start + ); + } + let mapped_addr = match map_phys_pages_into_target(dest_thread_id, &phys_pages, 0) { + Ok(addr) => addr, + Err(e) => return e, + }; + let total_bytes = page_count * 0x1000; + if super::ipc::send_map_header_from_kernel(dest_thread_id, mapped_addr, total_bytes) { + 0 + } else { + super::types::EAGAIN + } +} diff --git a/src/core/syscall/process.rs b/src/core/syscall/process.rs new file mode 100644 index 0000000..c5c3532 --- /dev/null +++ b/src/core/syscall/process.rs @@ -0,0 +1,1005 @@ +//! プロセス管理関連のシステムコール + +use super::types::{EFAULT, EINVAL, ENOMEM, ENOSYS, SUCCESS}; +use crate::interrupt::spinlock::SpinLock; +use crate::task::ThreadId; + +/// ユーザー空間の上限アドレス (x86-64 canonical hole 下側) +const USER_SPACE_END: u64 = 0x0000_7FFF_FFFF_FFFF; +/// Linux互換: 子プロセスが存在しない +const ECHILD: u64 = (-10i64) as u64; +/// Linux互換: 操作がタイムアウトした +const ETIMEDOUT: u64 = (-110i64) as u64; +/// PIT割り込み周期 (10ms) +const TICK_MS: u64 = 10; +use crate::task::{current_thread_id, exit_current_task}; + +#[derive(Clone, Copy)] +struct FutexWaitEntry { + tid: ThreadId, + uaddr: u64, + wake_tick: u64, +} + +const MAX_FUTEX_WAITERS: usize = crate::task::ThreadQueue::MAX_THREADS; +const NO_TIMEOUT_WAKE_TICK: u64 = u64::MAX; +static FUTEX_WAIT_QUEUE: SpinLock<[Option; MAX_FUTEX_WAITERS]> = + SpinLock::new([None; MAX_FUTEX_WAITERS]); + +#[inline] +fn aslr_mix64(mut x: u64) -> u64 { + x ^= x >> 30; + x = x.wrapping_mul(0xbf58_476d_1ce4_e5b9); + x ^= x >> 27; + x = x.wrapping_mul(0x94d0_49bb_1331_11eb); + x ^ (x >> 31) +} + +fn randomized_heap_base(pid: crate::task::ProcessId, floor: u64, max_pages: u64) -> u64 { + let seed = crate::cpu::boot_entropy_u64() + ^ crate::interrupt::timer::get_ticks().rotate_left(17) + ^ pid.as_u64().rotate_left(9) + ^ floor.rotate_left(3); + floor.saturating_add((aslr_mix64(seed) % max_pages) * 4096) +} + +#[inline] +fn page_align_up(addr: u64) -> Option { + addr.checked_add(4095).map(|v| v & !4095) +} + +#[inline] +fn is_user_range(addr: u64, len: u64) -> bool { + if len == 0 { + return addr <= USER_SPACE_END; + } + let end = match addr.checked_add(len.saturating_sub(1)) { + Some(e) => e, + None => return false, + }; + addr <= USER_SPACE_END && end <= USER_SPACE_END +} + +fn register_futex_waiter(tid: ThreadId, uaddr: u64, wake_tick: u64) -> bool { + let mut queue = FUTEX_WAIT_QUEUE.lock(); + + for slot in queue.iter_mut() { + if slot.is_some_and(|entry| entry.tid == tid) { + // 1スレッドは同時に1つの futex wait のみ許可する + return false; + } + } + + for slot in queue.iter_mut() { + if slot.is_none() { + *slot = Some(FutexWaitEntry { + tid, + uaddr, + wake_tick, + }); + return true; + } + } + + false +} + +fn futex_waiter_exists(tid: ThreadId, uaddr: u64) -> bool { + let queue = FUTEX_WAIT_QUEUE.lock(); + queue + .iter() + .flatten() + .any(|entry| entry.tid == tid && entry.uaddr == uaddr) +} + +fn remove_futex_waiter_by_tid(tid: ThreadId) -> bool { + let mut queue = FUTEX_WAIT_QUEUE.lock(); + for slot in queue.iter_mut() { + if slot.is_some_and(|entry| entry.tid == tid) { + *slot = None; + return true; + } + } + false +} + +pub fn clear_futex_waiter(tid: ThreadId) { + let _ = remove_futex_waiter_by_tid(tid); +} + +/// FUTEX_WAIT のタイムアウトに達したスレッドを起床させる(タイマー割り込みから呼ばれる) +pub fn wake_due_futex_waiters(now_tick: u64) { + let mut wake_list = [None; MAX_FUTEX_WAITERS]; + let mut wake_count = 0usize; + + { + let mut queue = FUTEX_WAIT_QUEUE.lock(); + for slot in queue.iter_mut() { + if let Some(entry) = *slot { + if entry.wake_tick != NO_TIMEOUT_WAKE_TICK && now_tick >= entry.wake_tick { + *slot = None; + if wake_count < wake_list.len() { + wake_list[wake_count] = Some(entry.tid); + wake_count += 1; + } else { + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "futex wake list overflow; dropping excess wake event", + ); + } + } + } + } + } + + for tid in wake_list.iter().take(wake_count).flatten() { + crate::task::with_thread_mut(*tid, |thread| thread.set_futex_timed_out(true)); + crate::task::wake_thread(*tid); + } +} + +/// Exitシステムコール +/// +/// プロセスを終了する +/// +/// # 引数 +/// - `exit_code`: 終了コード +/// +/// # 戻り値 +/// このシステムコールは戻らない(プロセスが終了する) +pub fn exit(exit_code: u64) -> ! { + crate::sprintln!("Process exiting with code: {}", exit_code); + + // スケジューラから現在のタスクを削除して終了 + exit_current_task(exit_code) +} + +/// List processes into a user-supplied buffer. +/// arg0 = user buffer ptr, arg1 = buffer length in bytes. +pub fn list_processes(buf_ptr: u64, buf_len: u64) -> u64 { + use crate::task::{for_each_process, ProcessState}; + + const RECORD_SIZE: usize = 88; + if buf_ptr == 0 { + return 0; + } + let max_bytes = buf_len as usize; + let max_entries = max_bytes / RECORD_SIZE; + if max_entries == 0 { + return 0; + } + + let mut written = 0usize; + let mut out_buf = [0u8; RECORD_SIZE]; + + crate::task::for_each_process(|proc| { + if written >= max_entries { + return; + } + // clear buffer + for b in out_buf.iter_mut() { + *b = 0; + } + // tid and pid: use process id for both (no separate thread id here) + let pid_u = proc.id().as_u64(); + out_buf[0..8].copy_from_slice(&pid_u.to_ne_bytes()); + out_buf[8..16].copy_from_slice(&pid_u.to_ne_bytes()); + // state mapping + let state_num: u64 = match proc.state() { + ProcessState::Running => 1, + ProcessState::Sleeping => 3, + ProcessState::Zombie => 4, + ProcessState::Terminated => 4, + _ => 0, + }; + out_buf[16..24].copy_from_slice(&state_num.to_ne_bytes()); + // name at offset 32, max 64 bytes + let name = proc.name(); + let name_bytes = name.as_bytes(); + let copy_len = core::cmp::min(64, name_bytes.len()); + out_buf[32..32 + copy_len].copy_from_slice(&name_bytes[..copy_len]); + + // copy to user + let dest_ptr = buf_ptr + (written * RECORD_SIZE) as u64; + if let Err(_) = super::copy_to_user(dest_ptr, &out_buf) { + // stop on error + written = written; // no-op + return; + } + written += 1; + }); + + written as u64 +} + +/// GetPidシステムコール +/// +/// 現在のプロセスIDを取得する +/// +/// # 戻り値 +/// プロセスID +pub fn getpid() -> u64 { + if let Some(tid) = current_thread_id() { + crate::task::with_thread(tid, |thread| thread.process_id().as_u64()).unwrap_or(0) + } else { + 0 + } +} + +/// GetTidシステムコール +/// +/// 現在のスレッドIDを取得する +/// +/// # 戻り値 +/// スレッドID +pub fn gettid() -> u64 { + if let Some(tid) = current_thread_id() { + tid.as_u64() + } else { + 0 + } +} + +/// Brkシステムコール +/// +/// メモリのヒープ領域サイズを変更する +pub fn brk(addr: u64) -> u64 { + // 現在のプロセスIDを取得 + let current_tid = match current_thread_id() { + Some(tid) => tid, + None => return ENOSYS, + }; + + // プロセスIDを取得 + let pid = match crate::task::with_thread(current_tid, |t| t.process_id()) { + Some(pid) => pid, + None => return ENOSYS, + }; + + let result = crate::task::with_process_mut(pid, |process| { + crate::debug!( + "brk(pid={:?}, process='{}'): req={:#x}, heap_start={:#x}, heap_end={:#x}", + pid, + process.name(), + addr, + process.heap_start(), + process.heap_end() + ); + if process.heap_start() == 0 { + let default_heap_base = randomized_heap_base(pid, 0x4000_0000, 0x8000); + process.set_heap_start(default_heap_base); + process.set_heap_end(default_heap_base); + } + // addr == 0 なら現在の位置を返す + if addr == 0 { + return Ok(process.heap_end()); + } + + if addr < process.heap_start() { + return Err(EINVAL); + } + + let current_brk = process.heap_end(); + + // ユーザー空間の上限アドレスを超えるbrkを拒否 + if !is_user_range(addr, 1) { + return Err(EINVAL); + } + + // 縮小または変化なし + if addr <= current_brk { + process.set_heap_end(addr); + return Ok(addr); + } + + // プロセス固有のページテーブルアドレスを取得 + let pt_phys = match process.page_table() { + Some(p) => p, + None => return Err(ENOSYS), + }; + + // 拡大時にページをプロセスのページテーブルにマップ(書き込み可能、実行不可) + // 既存のヒープページを上書きしないよう、未マップ開始位置から拡張する。 + let start_addr = + if crate::mem::paging::is_user_range_mapped_in_table(pt_phys, current_brk, 1) { + current_brk.saturating_add(1) + } else { + current_brk + }; + let start_page = match page_align_up(start_addr) { + Some(v) => v, + None => return Err(EINVAL), + }; + // 一部のユーザーランタイムは brk 境界アドレスにメタデータを書き込むため、 + // `addr` がページ境界ちょうどの場合でもそのページを含めて確保する。 + let map_end = addr.saturating_add(1); + let end_page = match page_align_up(map_end) { + Some(v) if is_user_range(v.saturating_sub(1), 1) => v, + _ => return Err(EINVAL), + }; + + if end_page > start_page { + let size = end_page - start_page; + if crate::mem::paging::map_and_copy_segment_to( + pt_phys, + start_page, + 0, + size, + &[], + true, + false, + ) + .is_err() + { + return Err(ENOSYS); + } + } + + process.set_heap_end(addr); + Ok(addr) + }); + + match result { + Some(Ok(addr)) => { + crate::debug!("brk(pid={:?}) -> {:#x}", pid, addr); + addr + } + Some(Err(err)) => { + crate::debug!("brk(pid={:?}) -> err {:#x}", pid, err); + err + } + None => { + crate::debug!("brk(pid={:?}) -> ENOSYS", pid); + ENOSYS + } + } +} + +/// Forkシステムコール +/// +/// プロセスを複製する +pub fn fork() -> u64 { + let parent_tid = match current_thread_id() { + Some(tid) => tid, + None => return ENOSYS, + }; + let parent_pid = match crate::task::with_thread(parent_tid, |t| t.process_id()) { + Some(pid) => pid, + None => return ENOSYS, + }; + + let (parent_priv, parent_priority, parent_pt, heap_start, heap_end, stack_bottom, stack_top) = + match crate::task::with_process(parent_pid, |p| { + ( + p.privilege(), + p.priority(), + p.page_table(), + p.heap_start(), + p.heap_end(), + p.stack_bottom(), + p.stack_top(), + ) + }) { + Some(v) => v, + None => return ENOSYS, + }; + let parent_pt = match parent_pt { + Some(pt) => pt, + None => return ENOSYS, + }; + + let child_pt = match crate::mem::paging::clone_user_page_table(parent_pt) { + Ok(pt) => pt, + Err(_) => return ENOMEM, + }; + + let (user_rip, user_rsp, user_rflags, parent_fs) = crate::task::with_thread(parent_tid, |t| { + let (rip, rsp, rflags) = t.syscall_user_context(); + (rip, rsp, rflags, t.fs_base()) + }) + .unwrap_or((0, 0, 0, 0)); + if user_rip == 0 || user_rsp == 0 { + let _ = crate::mem::paging::destroy_user_page_table(child_pt); + return ENOSYS; + } + + // 親プロセスの FD テーブルを fork 前にクローンする + let child_fd_table = crate::task::with_process(parent_pid, |p| p.clone_fd_table_for_fork()); + + let mut child_proc = + crate::task::Process::new("fork", parent_priv, Some(parent_pid), parent_priority); + child_proc.set_page_table(child_pt); + child_proc.set_heap_start(heap_start); + child_proc.set_heap_end(heap_end); + child_proc.set_stack_bottom(stack_bottom); + child_proc.set_stack_top(stack_top); + crate::info!( + "[STACK_INIT] FORK child: stack_bottom={:#x}, stack_top={:#x}", + stack_bottom, + stack_top + ); + // 親の FD テーブルを子に継承する + if let Some(table) = child_fd_table { + child_proc.set_fd_table(table); + } + let child_pid = child_proc.id(); + if crate::task::add_process(child_proc).is_none() { + let _ = crate::mem::paging::destroy_user_page_table(child_pt); + return ENOMEM; + } + + const KERNEL_THREAD_STACK_SIZE: usize = 4096 * 4; + let kstack = match crate::task::thread::allocate_kernel_stack(KERNEL_THREAD_STACK_SIZE) { + Some(s) => s, + None => { + let _ = crate::task::remove_process(child_pid); + let _ = crate::mem::paging::destroy_user_page_table(child_pt); + return ENOMEM; + } + }; + let child_thread = crate::task::Thread::new_fork_child( + child_pid, + user_rip, + user_rsp, + user_rflags, + parent_fs, + kstack, + KERNEL_THREAD_STACK_SIZE, + ); + if crate::task::add_thread(child_thread).is_none() { + let _ = crate::task::remove_process(child_pid); + let _ = crate::mem::paging::destroy_user_page_table(child_pt); + return ENOMEM; + } + + child_pid.as_u64() +} + +/// Sleepシステムコール +/// +/// 指定されたミリ秒数の間スリープする +/// +/// # 引数 +/// - `milliseconds`: スリープ時間(ミリ秒) +/// +/// # 戻り値 +/// 成功時はSUCCESS +pub fn sleep(milliseconds: u64) -> u64 { + if milliseconds == 0 { + // sleep(0) は待機せず、協調的に実行権を譲る + crate::task::yield_now(); + return SUCCESS; + } + let wait_ticks = milliseconds + .checked_add(TICK_MS - 1) + .map(|v| v / TICK_MS) + .unwrap_or(u64::MAX); + let target = crate::syscall::time::get_ticks().saturating_add(wait_ticks); + crate::syscall::time::sleep_until(target); + SUCCESS +} + +/// Waitシステムコール (wait4) +/// +/// # 引数 +/// - `pid`: 待機するプロセスID (-1 = 任意の子プロセス) +/// - `status_ptr`: 終了ステータスを書き込むポインタ (0 = 無視) +/// - `options`: WNOHANG(0x1) = ノンブロッキング +pub fn wait(_pid: u64, status_ptr: u64, options: u64) -> u64 { + const WNOHANG: u64 = 0x1; + let pid = _pid as i64; + if options & !WNOHANG != 0 { + return EINVAL; + } + if pid < -1 || pid == 0 { + return EINVAL; + } + + if status_ptr != 0 && !super::validate_user_ptr(status_ptr, 4) { + return EFAULT; + } + + // 呼び出し元プロセス + let current_pid = match current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |t| t.process_id())) + { + Some(pid) => pid, + None => return ECHILD, + }; + + let target_pid = if pid == -1 { + None + } else { + Some(crate::task::ProcessId::from_u64(pid as u64)) + }; + + // POSIX互換の待機: ゾンビを回収、存在しなければブロックまたはWNOHANGで0 + loop { + if let Some((reaped_pid, exit_code)) = + crate::task::reap_zombie_child_process(current_pid, target_pid) + { + if status_ptr != 0 { + let status = ((exit_code & 0xff) << 8) as i32; + if crate::syscall::write_user_i32(status_ptr, status).is_err() { + return EFAULT; + } + } + return reaped_pid.as_u64(); + } + + if !crate::task::has_child_process(current_pid, target_pid) { + return ECHILD; + } + + if options & WNOHANG != 0 { + return 0; + } + + crate::task::yield_now(); + } +} + +/// Mmapシステムコール +/// +/// 匿名メモリマッピングを作成する (MAP_ANONYMOUS | MAP_PRIVATE のみサポート) +/// +/// # 引数 +/// - `addr`: ヒント仮想アドレス (0で任意) +/// - `length`: マップするサイズ +/// - `prot`: 保護フラグ (PROT_READ|PROT_WRITE = 3) +/// - `flags`: マップフラグ (MAP_ANONYMOUS=0x20, MAP_PRIVATE=0x2) +/// - `_fd`: ファイルディスクリプタ (-1 = 匿名) +/// +/// # 戻り値 +/// マップされた仮想アドレス、またはエラーコード +pub fn mmap(addr: u64, length: u64, _prot: u64, flags: u64, _fd: u64) -> u64 { + use super::types::{EINVAL, ENOMEM}; + + if length == 0 { + return EINVAL; + } + + // MAP_ANONYMOUS (0x20) のみサポート + const MAP_ANONYMOUS: u64 = 0x20; + if flags & MAP_ANONYMOUS == 0 { + return ENOSYS; + } + + let current_tid = match current_thread_id() { + Some(tid) => tid, + None => return ENOMEM, + }; + let pid = match crate::task::with_thread(current_tid, |t| t.process_id()) { + Some(pid) => pid, + None => return ENOMEM, + }; + + // ページ境界に切り上げ(オーバーフロー安全) + let size = match page_align_up(length) { + Some(v) if v > 0 => v, + _ => return EINVAL, + }; + + let result = crate::task::with_process_mut(pid, |process| { + crate::info!( + "mmap(pid={:?}, process='{}'): addr={:#x}, len={:#x}, flags={:#x}, heap_start={:#x}, heap_end={:#x}", + pid, + process.name(), + addr, + length, + flags, + process.heap_start(), + process.heap_end() + ); + // mmap用のヒープ領域を現在のbrk以降に割り当てる + // (簡易実装: brkと同じ領域を使う) + if process.heap_start() == 0 { + let default_heap_base = randomized_heap_base(pid, 0x5000_0000, 0x10000); + process.set_heap_start(default_heap_base); + process.set_heap_end(default_heap_base); + } + + // ユーザー空間の上限アドレスを超えるaddrを拒否 + if addr != 0 && addr > USER_SPACE_END { + return Err(EINVAL); + } + + let map_start = if addr != 0 { + match page_align_up(addr) { + Some(v) => v, + None => return Err(EINVAL), + } + } else { + // heap_endを mmap_base として使う(簡易実装) + // 実際は別のアドレス空間管理が必要 + let base = process.heap_end(); + match page_align_up(base) { + Some(v) => v, + None => return Err(EINVAL), + } + }; + + if !is_user_range(map_start, size) { + return Err(EINVAL); + } + + let pt_phys = match process.page_table() { + Some(p) => p, + None => return Err(ENOMEM), + }; + + if crate::mem::paging::map_and_copy_segment_to( + pt_phys, + map_start, + 0, + size, + &[], + true, + false, + ) + .is_err() + { + return Err(ENOMEM); + } + + // heap_end を更新してアドレス空間が重ならないようにする + if addr == 0 { + let new_heap_end = match map_start.checked_add(size) { + Some(v) => v, + None => return Err(EINVAL), + }; + process.set_heap_end(new_heap_end); + } + + Ok(map_start) + }); + + match result { + Some(Ok(va)) => { + crate::info!("mmap(pid={:?}) -> {:#x}", pid, va); + va + } + Some(Err(e)) => { + crate::info!("mmap(pid={:?}) -> err {:#x}", pid, e); + e + } + None => { + crate::info!("mmap(pid={:?}) -> ENOMEM", pid); + ENOMEM + } + } +} + +/// Munmapシステムコール +pub fn munmap(addr: u64, length: u64) -> u64 { + if addr == 0 || length == 0 { + return EINVAL; + } + let unmap_start = addr & !4095; + let unmap_end = match addr.checked_add(length).and_then(page_align_up) { + Some(v) => v, + None => return EINVAL, + }; + let unmap_len = match unmap_end.checked_sub(unmap_start) { + Some(v) if v > 0 => v, + _ => return EINVAL, + }; + if !is_user_range(unmap_start, unmap_len) { + return EINVAL; + } + + let tid = match current_thread_id() { + Some(t) => t, + None => return ENOSYS, + }; + let pid = match crate::task::with_thread(tid, |t| t.process_id()) { + Some(p) => p, + None => return ENOSYS, + }; + let pt_phys = match crate::task::with_process(pid, |p| p.page_table()).flatten() { + Some(p) => p, + None => return ENOSYS, + }; + + match crate::mem::paging::unmap_range_in_table(pt_phys, unmap_start, unmap_len) { + Ok(()) => SUCCESS, + Err(_) => EINVAL, + } +} + +/// Futexシステムコール +/// +/// FUTEX_WAIT / FUTEX_WAKE の待機キュー方式を実装する。 +/// timeout は「現在tickからの相対tick」として扱う(0は無期限)。 +pub fn futex(uaddr: u64, op: u32, val: u64, timeout: u64) -> u64 { + use super::types::EAGAIN; + const FUTEX_WAIT: u32 = 0; + const FUTEX_WAKE: u32 = 1; + const FUTEX_PRIVATE_FLAG: u32 = 128; + + let op_base = op & !FUTEX_PRIVATE_FLAG; + + match op_base { + FUTEX_WAIT => { + if uaddr == 0 { + return EFAULT; + } + let current_tid = match current_thread_id() { + Some(tid) => tid, + None => return ENOSYS, + }; + // ユーザー空間アドレスの有効性を検証する + if !super::validate_user_ptr(uaddr, 4) { + return EFAULT; + } + let wake_tick = if timeout == 0 { + NO_TIMEOUT_WAKE_TICK + } else { + crate::syscall::time::get_ticks().saturating_add(timeout) + }; + + crate::task::with_thread_mut(current_tid, |thread| thread.set_futex_timed_out(false)); + + // 割り込み禁止区間内で「値の確認 → キュー登録 → スリープ → 最初のyield」を + // アトミックに実行することで、wake と sleep の競合ウィンドウを排除する。 + // yield_now() 内部の switch_to_thread も CLI を実行するため、 + // without_interrupts をネストしても安全に動作する。 + let queued = x86_64::instructions::interrupts::without_interrupts(|| { + let current_val = match crate::syscall::read_user_u32(uaddr) { + Ok(v) => v, + Err(_) => return Err(EFAULT), + }; + if current_val != val as u32 { + return Err(EAGAIN); + } + if !register_futex_waiter(current_tid, uaddr, wake_tick) { + return Err(EAGAIN); + } + crate::task::sleep_thread(current_tid); + // 割り込み禁止のまま最初のコンテキストスイッチを実行し、 + // sleep_thread とyield の間に wake シグナルが失われる競合を防ぐ。 + crate::task::yield_now(); + Ok(()) + }); + if let Err(err) = queued { + return err; + } + + enum WaitResult { + Continue, + Success, + TimedOut, + } + + // 起床後に条件を確認し、まだ待機が必要な場合のみ再度 yield する。 + loop { + let result = x86_64::instructions::interrupts::without_interrupts(|| { + let timed_out = crate::task::with_thread_mut(current_tid, |thread| { + thread.take_futex_timed_out() + }) + .unwrap_or(false); + if timed_out { + return WaitResult::TimedOut; + } + + if !futex_waiter_exists(current_tid, uaddr) { + return WaitResult::Success; + } + + if wake_tick != NO_TIMEOUT_WAKE_TICK + && crate::syscall::time::get_ticks() >= wake_tick + { + if remove_futex_waiter_by_tid(current_tid) { + crate::task::with_thread_mut(current_tid, |thread| { + thread.set_futex_timed_out(false); + }); + return WaitResult::TimedOut; + } + let timed_out = crate::task::with_thread_mut(current_tid, |thread| { + thread.take_futex_timed_out() + }) + .unwrap_or(false); + return if timed_out { + WaitResult::TimedOut + } else { + WaitResult::Success + }; + } + + WaitResult::Continue + }); + + match result { + WaitResult::Continue => crate::task::yield_now(), + WaitResult::Success => return SUCCESS, + WaitResult::TimedOut => return ETIMEDOUT, + } + } + } + FUTEX_WAKE => { + if uaddr == 0 { + return EFAULT; + } + if !super::validate_user_ptr(uaddr, 4) { + return EFAULT; + } + let max_wake = core::cmp::min(val as usize, MAX_FUTEX_WAITERS); + if max_wake == 0 { + return 0; + } + + let mut wake_list = [None; MAX_FUTEX_WAITERS]; + let mut wake_count = 0usize; + { + let mut queue = FUTEX_WAIT_QUEUE.lock(); + for slot in queue.iter_mut() { + if wake_count >= max_wake { + break; + } + if let Some(entry) = *slot { + if entry.uaddr == uaddr { + *slot = None; + wake_list[wake_count] = Some(entry.tid); + wake_count += 1; + } + } + } + } + + for tid in wake_list.iter().take(wake_count).flatten() { + crate::task::with_thread_mut(*tid, |thread| thread.set_futex_timed_out(false)); + crate::task::wake_thread(*tid); + } + wake_count as u64 + } + _ => ENOSYS, + } +} + +/// arch_prctlシステムコール +/// +/// TLS 用の FS ベースレジスタを設定する +pub fn arch_prctl(code: u64, addr: u64) -> u64 { + const ARCH_SET_FS: u64 = 0x1002; + const ARCH_GET_FS: u64 = 0x1003; + + match code { + ARCH_SET_FS => { + if addr > USER_SPACE_END { + return EINVAL; + } + // glibc の起動シーケンスでは FS を切り替える最中に stack protector が + // 動くことがあるため、旧FS上のガード値を新FSへ引き継ぐ。 + // (mapped かつユーザー範囲内の場合のみ) + let old_fs = if let Some(tid) = current_thread_id() { + crate::task::with_thread(tid, |t| t.fs_base()).unwrap_or(0) + } else { + unsafe { crate::cpu::read_fs_base() } + }; + if old_fs >= 0x1000 && addr >= 0x1000 && old_fs != addr { + for off in [0x28u64, 0x30u64] { + let src = old_fs.saturating_add(off); + let dst = addr.saturating_add(off); + if super::validate_user_ptr(src, 8) && super::validate_user_ptr(dst, 8) { + if let Ok(val) = crate::syscall::read_user_u64(src) { + let _ = crate::syscall::write_user_u64(dst, val); + } + } + } + } + // FS ベースレジスタを設定 (WRFSBASE または IA32_FS_BASE MSR) + unsafe { + crate::cpu::write_fs_base(addr); + } + // 現在のスレッドに FS base を記録 (コンテキストスイッチ時に復元するため) + if let Some(tid) = current_thread_id() { + crate::task::with_thread_mut(tid, |t| t.set_fs_base(addr)); + } + SUCCESS + } + ARCH_GET_FS => { + let val = unsafe { crate::cpu::read_fs_base() }; + // addrが指すメモリに書き込む + if addr == 0 { + return EFAULT; + } + // ユーザー空間アドレスの有効性を検証する + if !super::validate_user_ptr(addr, 8) { + return EFAULT; + } + match crate::syscall::write_user_u64(addr, val) { + Ok(()) => SUCCESS, + Err(e) => e, + } + } + _ => EINVAL, + } +} + +/// sigaltstack システムコール(最小実装) +/// +/// 互換目的で成功を返す。現状 alt stack の切り替えは行わない。 +pub fn sigaltstack(_ss: u64, _old_ss: u64) -> u64 { + SUCCESS +} + +/// set_robust_list システムコール(最小実装) +/// +/// glibc 初期化互換のため成功を返す。 +pub fn set_robust_list(_head: u64, _len: u64) -> u64 { + SUCCESS +} + +/// getrandom システムコール(最小実装) +/// +/// カーネル内の軽量PRNGでバイト列を生成して返す。 +pub fn getrandom(buf_ptr: u64, len: u64, _flags: u64) -> u64 { + if len == 0 { + return 0; + } + if buf_ptr == 0 || !super::validate_user_ptr(buf_ptr, len) { + return EFAULT; + } + let mut state = crate::syscall::time::get_ticks() + ^ buf_ptr.rotate_left(17) + ^ len.rotate_left(7) + ^ 0x9E37_79B9_7F4A_7C15; + let mut out = alloc::vec![0u8; len as usize]; + for b in out.iter_mut() { + state = state + .wrapping_mul(6364136223846793005) + .wrapping_add(1442695040888963407); + *b = (state >> 24) as u8; + } + match crate::syscall::copy_to_user(buf_ptr, &out) { + Ok(()) => len, + Err(e) => e, + } +} + +/// FindProcessByNameシステムコール +/// +/// プロセス名から、IPC送信先として使えるスレッドIDを検索する +/// +/// # 引数 +/// - `name_ptr`: プロセス名のポインタ +/// - `len`: プロセス名の長さ +/// +/// # 戻り値 +/// 見つかった場合はスレッドID、見つからない場合は0 +pub fn find_process_by_name(name_ptr: u64, len: u64) -> u64 { + use crate::task; + use core::str; + + if name_ptr == 0 || len == 0 || len > 64 { + return 0; + } + + let mut name_buf = [0u8; 64]; + if crate::syscall::copy_from_user(name_ptr, &mut name_buf[..len as usize]).is_err() { + return 0; + } + let name = match str::from_utf8(&name_buf[..len as usize]) { + Ok(s) => s, + Err(_) => return 0, + }; + + let pid = match task::find_process_id_by_name(name) { + Some(pid) => pid, + None => return 0, + }; + + // IPCの宛先はプロセスIDではなくスレッドIDなので、 + // 対象プロセスに属する先頭スレッドIDを返す。 + let mut thread_id: Option = None; + task::for_each_thread(|thread| { + if thread_id.is_none() + && thread.process_id() == pid + && thread.state() != task::ThreadState::Terminated + { + thread_id = Some(thread.id().as_u64()); + } + }); + + thread_id.unwrap_or(0) +} diff --git a/src/core/syscall/signal.rs b/src/core/syscall/signal.rs new file mode 100644 index 0000000..8d49c2e --- /dev/null +++ b/src/core/syscall/signal.rs @@ -0,0 +1,570 @@ +//! シグナル関連のシステムコール +//! +//! rt_sigaction / rt_sigprocmask / kill / rt_sigreturn と、 +//! syscall リターン時のシグナル送達ロジックを実装する。 + +use super::types::{EINVAL, EPERM, ESRCH, SUCCESS}; +use crate::task::{ + current_thread_id, default_action, thread_to_process_id, with_process, with_process_mut, + DefaultAction, PrivilegeLevel, ProcessId, SigAction, SIGCHLD, SIGKILL, SIG_DFL, SIG_IGN, +}; + +// ---- rt_sigprocmask の how 引数 ---- +const SIG_BLOCK: u64 = 0; +const SIG_UNBLOCK: u64 = 1; +const SIG_SETMASK: u64 = 2; + +// ---- SIGKILL / SIGSTOP はブロック・ハンドラ変更不可 ---- +const SIGSTOP: usize = 19; +const UNCATCHABLE_MASK: u64 = (1u64 << (SIGKILL - 1)) | (1u64 << (SIGSTOP - 1)); // SIGKILL | SIGSTOP + +// ---- ユーザー空間の struct sigaction レイアウト (Linux x86-64 互換) ---- +// sa_handler: [+0] u64 +// sa_flags: [+8] u64 +// sa_restorer: [+16] u64 +// sa_mask: [+24] u64 (128-bit mask, 上位64bitは今回使わない) +const SIGACTION_SIZE: u64 = 32; + +/// rt_sigaction システムコール +/// +/// # 引数 +/// - `signum`: シグナル番号 (1–64) +/// - `new_act_ptr`: 新しいアクション (NULLなら変更しない) +/// - `old_act_ptr`: 旧アクションの書き出し先 (NULLなら無視) +pub fn rt_sigaction(signum: u64, new_act_ptr: u64, old_act_ptr: u64) -> u64 { + let sig = signum as usize; + if sig < 1 || sig > 64 { + return EINVAL; + } + // SIGKILL/SIGSTOP のハンドラは変更不可 + if (new_act_ptr != 0) && ((1u64 << (sig - 1)) & UNCATCHABLE_MASK != 0) { + return EINVAL; + } + + let pid = match current_pid() { + Some(p) => p, + None => return EINVAL, + }; + + // 旧アクションを読み出してユーザー空間に書く + if old_act_ptr != 0 { + if !crate::syscall::validate_user_ptr(old_act_ptr, SIGACTION_SIZE) { + return super::types::EFAULT; + } + let old = with_process(pid, |p| p.signal_state().action(sig)) + .unwrap_or(SigAction::default_action()); + let mut buf = [0u8; SIGACTION_SIZE as usize]; + buf[0..8].copy_from_slice(&old.handler.to_ne_bytes()); + buf[8..16].copy_from_slice(&old.flags.to_ne_bytes()); + buf[16..24].copy_from_slice(&old.restorer.to_ne_bytes()); + buf[24..32].copy_from_slice(&old.mask.to_ne_bytes()); + if crate::syscall::copy_to_user(old_act_ptr, &buf).is_err() { + return super::types::EFAULT; + } + } + + // 新アクションをカーネルに保存 + if new_act_ptr != 0 { + if !crate::syscall::validate_user_ptr(new_act_ptr, SIGACTION_SIZE) { + return super::types::EFAULT; + } + let handler = match crate::syscall::read_user_u64(new_act_ptr) { + Ok(v) => v, + Err(e) => return e, + }; + let flags = match crate::syscall::read_user_u64(new_act_ptr + 8) { + Ok(v) => v, + Err(e) => return e, + }; + let restorer = match crate::syscall::read_user_u64(new_act_ptr + 16) { + Ok(v) => v, + Err(e) => return e, + }; + let mask = match crate::syscall::read_user_u64(new_act_ptr + 24) { + Ok(v) => v, + Err(e) => return e, + }; + // mask の uncatchable ビットは強制クリア + let mask = mask & !UNCATCHABLE_MASK; + let action = SigAction { + handler, + flags, + restorer, + mask, + }; + with_process_mut(pid, |p| p.signal_state_mut().set_action(sig, action)); + } + + SUCCESS +} + +/// rt_sigprocmask システムコール +/// +/// # 引数 +/// - `how`: SIG_BLOCK / SIG_UNBLOCK / SIG_SETMASK +/// - `set_ptr`: 操作対象マスク (NULLなら変更しない) +/// - `oldset_ptr`: 旧マスクの書き出し先 (NULLなら無視) +pub fn rt_sigprocmask(how: u64, set_ptr: u64, oldset_ptr: u64) -> u64 { + let pid = match current_pid() { + Some(p) => p, + None => return EINVAL, + }; + + // 旧マスクを返す + if oldset_ptr != 0 { + if !crate::syscall::validate_user_ptr(oldset_ptr, 8) { + return super::types::EFAULT; + } + let old_mask = with_process(pid, |p| p.signal_state().mask).unwrap_or(0); + if crate::syscall::write_user_u64(oldset_ptr, old_mask).is_err() { + return super::types::EFAULT; + } + } + + if set_ptr == 0 { + return SUCCESS; + } + if !crate::syscall::validate_user_ptr(set_ptr, 8) { + return super::types::EFAULT; + } + let new_set = match crate::syscall::read_user_u64(set_ptr) { + Ok(v) => v & !UNCATCHABLE_MASK, + Err(e) => return e, + }; + + with_process_mut(pid, |p| { + let mask = &mut p.signal_state_mut().mask; + match how { + SIG_BLOCK => *mask |= new_set, + SIG_UNBLOCK => *mask &= !new_set, + SIG_SETMASK => *mask = new_set, + _ => {} + } + }); + + SUCCESS +} + +/// kill システムコール +/// +/// # 引数 +/// - `pid_raw`: ターゲット PID (正数=指定PID, 0=同じプロセスグループ, -1=すべて) +/// - `sig_raw`: シグナル番号 (0=存在確認のみ) +pub fn kill(pid_raw: u64, sig_raw: u64) -> u64 { + let target_pid_raw = pid_raw as i64; + let sig = sig_raw as usize; + + if sig > 64 { + return EINVAL; + } + + // sig == 0: プロセス存在確認のみ + if sig == 0 { + if target_pid_raw > 0 { + let target = ProcessId::from_u64(target_pid_raw as u64); + let exists = with_process(target, |_| ()).is_some(); + if !exists { + return ESRCH; + } + if !caller_can_signal_target(target) { + return EPERM; + } + return SUCCESS; + } else if target_pid_raw == -1 { + return if caller_can_broadcast_signal() { + SUCCESS + } else { + EPERM + }; + } else { + return EINVAL; + } + } + + if target_pid_raw > 0 { + let target = ProcessId::from_u64(target_pid_raw as u64); + if with_process(target, |_| ()).is_none() { + return ESRCH; + } + if !caller_can_signal_target(target) { + return EPERM; + } + deliver_signal_to_pid(target, sig) + } else if target_pid_raw == -1 { + if !caller_can_broadcast_signal() { + return EPERM; + } + // 全プロセス(カーネル除く)に送る + let mut found = false; + let current_pid = current_pid(); + let mut pids = alloc::vec::Vec::new(); + crate::task::for_each_process(|p| pids.push(p.id())); + for pid in pids { + if Some(pid) != current_pid { + if deliver_signal_to_pid(pid, sig) == SUCCESS { + found = true; + } + } + } + if found { + SUCCESS + } else { + ESRCH + } + } else { + EINVAL + } +} + +/// tkill システムコール +/// +/// # 引数 +/// - `tid_raw`: ターゲット TID +/// - `sig_raw`: シグナル番号 (0=存在確認のみ) +pub fn tkill(tid_raw: u64, sig_raw: u64) -> u64 { + let sig = sig_raw as usize; + if sig > 64 { + return EINVAL; + } + let target_pid = match thread_to_process_id(tid_raw) { + Some(pid) => pid, + None => return ESRCH, + }; + if !caller_can_signal_target(target_pid) { + return EPERM; + } + if sig == 0 { + return SUCCESS; + } + deliver_signal_to_pid(target_pid, sig) +} + +/// tgkill システムコール +/// +/// # 引数 +/// - `tgid_raw`: ターゲットスレッドグループID (PID) +/// - `tid_raw`: ターゲット TID +/// - `sig_raw`: シグナル番号 (0=存在確認のみ) +pub fn tgkill(tgid_raw: u64, tid_raw: u64, sig_raw: u64) -> u64 { + let sig = sig_raw as usize; + if sig > 64 { + return EINVAL; + } + let target_pid = match thread_to_process_id(tid_raw) { + Some(pid) => pid, + None => return ESRCH, + }; + if tgid_raw != 0 && target_pid.as_u64() != tgid_raw { + return ESRCH; + } + if !caller_can_signal_target(target_pid) { + return EPERM; + } + if sig == 0 { + return SUCCESS; + } + deliver_signal_to_pid(target_pid, sig) +} + +/// 指定プロセスにシグナルを送達する(カーネル内部からも呼ばれる) +pub fn deliver_signal_to_pid(pid: ProcessId, sig: usize) -> u64 { + if sig < 1 || sig > 64 { + return EINVAL; + } + + let exists = with_process(pid, |_| ()).is_some(); + if !exists { + return ESRCH; + } + + // SIGKILL はハンドラを無視して即終了 + if sig == SIGKILL { + kill_process_immediately(pid, sig as u64); + return SUCCESS; + } + + // SYSCALL 経路では return-to-user 前のシグナル送達フックがまだないため、 + // 「自分宛て + 非ブロック + 既定動作=Terminate」はここで即時終了させる。 + if current_pid().is_some_and(|cur| cur == pid) { + let (blocked, action) = match with_process(pid, |p| { + let state = p.signal_state(); + let blocked = (state.mask & (1u64 << (sig - 1))) != 0; + (blocked, state.action(sig)) + }) { + Some(v) => v, + None => return ESRCH, + }; + + if !blocked { + if action.is_ignored() { + return SUCCESS; + } + if action.is_default() && matches!(default_action(sig), DefaultAction::Terminate) { + crate::task::exit_current_task(sig as u64); + } + if action.is_default() && matches!(default_action(sig), DefaultAction::Ignore) { + return SUCCESS; + } + } + } + + // pending ビットをセット + with_process_mut(pid, |p| p.signal_state_mut().set_pending(sig)); + + // ブロッキング待機しているスレッドを起床させる(シグナルを受け取れるよう) + wake_first_thread_of(pid); + + SUCCESS +} + +/// 子プロセス終了時に親プロセスへ SIGCHLD を送達する(scheduler から呼ばれる) +pub fn deliver_sigchld_to_parent(child_pid: ProcessId) { + let parent_pid = match with_process(child_pid, |p| p.parent_id()) { + Some(Some(pid)) => pid, + _ => return, + }; + deliver_signal_to_pid(parent_pid, SIGCHLD); +} + +// ---- syscall リターン時のシグナル送達 ---------------------------------------- + +/// int 0x80 リターン時に呼ばれる: pending シグナルの送達とシグナルフレームの設定 +/// +/// # 引数 +/// - `kstack`: int 0x80 ハンドラが積んだレジスタ保存領域の先頭ポインタ +/// - `syscall_ret`: syscall の戻り値 +/// +/// # Returns +/// 最終的な syscall 戻り値(シグナル送達時は変更される場合がある) +/// +/// # kstack レイアウト(push 順の逆、低アドレスが先頭) +/// ```text +/// [0] r15, [1] r14, [2] r13, [3] r12, +/// [4] r11, [5] r10, [6] r9, [7] r8, +/// [8] rdi (arg0), [9] rsi, [10] rbp, [11] rbx, +/// [12] rdx, [13] rcx, [14] rax (syscall number), +/// --- CPU 割り込みフレーム --- +/// [15] user RIP, [16] user CS, [17] user RFLAGS, [18] user RSP, [19] user SS +/// ``` +#[no_mangle] +pub extern "sysv64" fn signal_and_return(kstack: *mut u64, syscall_ret: u64) -> u64 { + // kstack[14] = [rsp+112] = 元の syscall 番号(dispatch 呼び出し前の push rax) + let syscall_num = unsafe { kstack.add(14).read() }; + + // rt_sigreturn (15): シグナルフレームから元のコンテキストを復元 + if syscall_num == crate::syscall::SyscallNumber::RtSigreturn as u64 { + rt_sigreturn(kstack); + return 0; + } + + // シグナルを持つ current process を取得 + let pid = match current_pid() { + Some(p) => p, + None => return syscall_ret, + }; + + // 送達すべきシグナルを1つ取り出す + let sig = match with_process_mut(pid, |p| p.signal_state_mut().take_next_deliverable()) { + Some(Some(s)) => s, + _ => return syscall_ret, + }; + + let action = + with_process(pid, |p| p.signal_state().action(sig)).unwrap_or(SigAction::default_action()); + + if action.is_ignored() { + return syscall_ret; + } + + if !action.has_user_handler() { + // SIG_DFL + match default_action(sig) { + DefaultAction::Terminate => { + crate::task::exit_current_task(sig as u64); + } + DefaultAction::Ignore => return syscall_ret, + } + } + + // ユーザーハンドラへリダイレクト + unsafe { setup_signal_frame(kstack, sig, &action) }; + + // ハンドラには syscall の戻り値ではなくシグナル番号が RDI に入る(フレーム設定済み) + // RAX はハンドラには見えないが一応 0 にする + 0 +} + +/// int 0x80 割り込みフレームにシグナルフレームを積み、ハンドラへリダイレクトする +/// +/// # Safety +/// `kstack` は有効な割り込みスタックフレームを指している必要がある。 +unsafe fn setup_signal_frame(kstack: *mut u64, sig: usize, action: &SigAction) { + // 割り込みフレームから user RIP / RSP / RFLAGS を取得 + let user_rip = kstack.add(15).read(); + let user_rflags = kstack.add(17).read(); + let user_rsp = kstack.add(18).read(); + + // ユーザースタック上にシグナルフレームを構築 + // レイアウト(低アドレス → 高アドレス, 新 RSP は先頭): + // [new_rsp + 0]: sa_restorer ← ハンドラの戻り先(return address) + // [new_rsp + 8]: saved RIP + // [new_rsp + 16]: saved RSP + // [new_rsp + 24]: saved RFLAGS + // + // ハンドラ呼び出し規約: x86-64 では `call` の直後は RSP % 16 == 8 なので、 + // 戻り番地を積んだ直後のスタックトップとして new_rsp を渡す。 + const FRAME_BYTES: u64 = 32; + let aligned = (user_rsp.wrapping_sub(FRAME_BYTES)) & !15u64; + let new_rsp = aligned.wrapping_sub(8); // call 直後を模倣: RSP % 16 == 8 + + // フレームをユーザースタックに書き込む + let ok = write_signal_frame(new_rsp, action.restorer, user_rip, user_rsp, user_rflags); + if !ok { + // ユーザースタックが不正 → 強制終了 + crate::task::exit_current_task(11); // SIGSEGV + } + + // ハンドラ実行中は action.mask のシグナルをブロック + if let Some(pid) = current_pid() { + with_process_mut(pid, |p| { + p.signal_state_mut().mask |= action.mask; + }); + } + + // 割り込みフレームを書き換えてハンドラへリダイレクト + kstack.add(8).write(sig as u64); // RDI = シグナル番号(ハンドラの第1引数) + kstack.add(15).write(action.handler); // user RIP → ハンドラ + kstack.add(18).write(new_rsp); // user RSP → シグナルフレーム先頭 +} + +/// シグナルフレームをユーザースタックに書き込む +fn write_signal_frame( + new_rsp: u64, + restorer: u64, + saved_rip: u64, + saved_rsp: u64, + saved_rflags: u64, +) -> bool { + // 書き込みアドレスの検証(32バイト) + if !crate::syscall::validate_user_ptr(new_rsp, 32) { + return false; + } + let mut frame = [0u8; 32]; + frame[0..8].copy_from_slice(&restorer.to_ne_bytes()); + frame[8..16].copy_from_slice(&saved_rip.to_ne_bytes()); + frame[16..24].copy_from_slice(&saved_rsp.to_ne_bytes()); + frame[24..32].copy_from_slice(&saved_rflags.to_ne_bytes()); + crate::syscall::copy_to_user(new_rsp, &frame).is_ok() +} + +/// rt_sigreturn システムコール +/// +/// シグナルハンドラから戻るときに呼ばれる。 +/// シグナルフレームから保存された RIP / RSP / RFLAGS を復元する。 +/// +/// # 引数 +/// - `kstack`: int 0x80 割り込みスタックフレーム先頭ポインタ +pub fn rt_sigreturn(kstack: *mut u64) { + // ハンドラが `ret` した後、restorer が int 0x80 (RAX=15) を実行する。 + // `ret` で sa_restorer を pop したので、user RSP は +8 されている。 + // つまり user_rsp は saved_rip の直前を指している。 + let user_rsp = unsafe { kstack.add(18).read() }; + + if !crate::syscall::validate_user_ptr(user_rsp, 24) { + crate::task::exit_current_task(11); // SIGSEGV + } + + let saved_rip = match crate::syscall::read_user_u64(user_rsp) { + Ok(v) => v, + Err(_) => { + crate::task::exit_current_task(11); + } + }; + let saved_rsp = match crate::syscall::read_user_u64(user_rsp + 8) { + Ok(v) => v, + Err(_) => { + crate::task::exit_current_task(11); + } + }; + let saved_rflags = match crate::syscall::read_user_u64(user_rsp + 16) { + Ok(v) => v, + Err(_) => { + crate::task::exit_current_task(11); + } + }; + + unsafe { + kstack.add(15).write(saved_rip); + kstack.add(17).write(saved_rflags); + kstack.add(18).write(saved_rsp); + } + + // ハンドラ実行中にブロックしていたマスクを元に戻す(簡易: マスクをクリア) + // TODO: シグナルフレームに旧マスクを保存して正確に復元する + if let Some(pid) = current_pid() { + with_process_mut(pid, |p| p.signal_state_mut().mask = 0); + } +} + +// ---- ヘルパー関数 ------------------------------------------------------- + +fn current_pid() -> Option { + let tid = current_thread_id()?; + crate::task::with_thread(tid, |t| t.process_id()) +} + +fn current_privilege() -> Option { + let pid = current_pid()?; + with_process(pid, |p| p.privilege()) +} + +fn caller_can_signal_target(target: ProcessId) -> bool { + let caller = match current_pid() { + Some(pid) => pid, + None => return false, + }; + if caller == target { + return true; + } + matches!(current_privilege(), Some(PrivilegeLevel::Core)) +} + +fn caller_can_broadcast_signal() -> bool { + matches!(current_privilege(), Some(PrivilegeLevel::Core)) +} + +/// 指定プロセスの最初のスレッドを起床させる +fn wake_first_thread_of(pid: ProcessId) { + let mut tid_opt = None; + crate::task::for_each_thread(|t| { + if tid_opt.is_none() && t.process_id() == pid { + tid_opt = Some(t.id()); + } + }); + if let Some(tid) = tid_opt { + crate::task::wake_thread(tid); + } +} + +/// プロセスを即座に強制終了する(SIGKILL 用) +fn kill_process_immediately(pid: ProcessId, exit_code: u64) { + // 現在のプロセスなら exit_current_task で終了 + if let Some(cur_pid) = current_pid() { + if cur_pid == pid { + crate::task::exit_current_task(exit_code); + } + } + // 他プロセスのスレッドをすべて Terminated にして Zombie に遷移させる + let mut tids = alloc::vec::Vec::new(); + crate::task::for_each_thread(|t| { + if t.process_id() == pid { + tids.push(t.id()); + } + }); + for tid in tids { + crate::task::terminate_thread(tid); + } + crate::task::mark_process_exited(pid, exit_code); + // 親に SIGCHLD を届ける + deliver_sigchld_to_parent(pid); +} diff --git a/src/core/syscall/syscall_entry.rs b/src/core/syscall/syscall_entry.rs new file mode 100644 index 0000000..5f759ac --- /dev/null +++ b/src/core/syscall/syscall_entry.rs @@ -0,0 +1,351 @@ +//! SYSCALL/SYSRET 命令サポート +//! +//! Linux x86_64 ABI: syscall 命令を使ったシステムコールをサポートする。 +//! int 0x80 との違いは SYSCALL 専用の MSR 設定が必要な点。 +//! +//! SYSCALL 時のレジスタ: +//! RAX = syscall 番号 +//! RDI = arg0, RSI = arg1, RDX = arg2, R10 = arg3, R8 = arg4, R9 = arg5 +//! RCX = ユーザーの RIP (SYSCALL が自動保存) +//! R11 = ユーザーの RFLAGS (SYSCALL が自動保存) +//! RSP = まだユーザースタック + +/// SYSCALL/SYSRET に必要な MSR を初期化する +/// +/// カーネル GDT 構成 (x86_64-unknown-uefi / MS ABI): +/// index 0: null +/// index 1: kernel code (CS) → selector = 0x08 +/// index 2: kernel data (SS) → selector = 0x10 +/// index 3: user data (SS) → selector = 0x18 +/// index 4: user code (CS) → selector = 0x20 +/// index 5: TSS (2 entries) +/// +/// STAR MSR レイアウト: +/// [47:32] = SYSCALL CS (カーネル CS, SS は CS+8) +/// [63:48] = SYSRET CS (ユーザー CS-16, 実際の user CS は +16, SS は +8) +pub fn init_syscall() { + // IA32_EFER の SCE ビットを有効化 (SYSCALL/SYSRET を使えるようにする) + const IA32_EFER: u32 = 0xC000_0080; + const SCE_BIT: u64 = 1; + + // IA32_STAR: [47:32] = kernel CS selector, [63:48] = user CS selector - 16 + // カーネル: CS=0x08, SS=0x10 + // ユーザー: CS=0x23 (0x20|3), SS=0x1b (0x18|3) + // STAR[47:32] = 0x0008 (kernel CS) + // STAR[63:48] = 0x0010 (= user_cs - 16 で SYSRET時に +16 される → 0x20, RPL=3 付与で 0x23) + const IA32_STAR: u32 = 0xC000_0081; + let star_val: u64 = ((0x0008u64) << 32) | ((0x0010u64) << 48); + + // IA32_LSTAR: SYSCALL 時のエントリポイント (64ビット) + const IA32_LSTAR: u32 = 0xC000_0082; + let lstar_val = syscall_entry as *const () as u64; + + // IA32_FMASK: SYSCALL 時に RFLAGS からクリアするビット + // IF (bit 9) をクリアして割り込み禁止にする + const IA32_FMASK: u32 = 0xC000_0084; + let fmask_val: u64 = 0x200; // IF ビット + + unsafe { + // EFER.SCE を設定 + let efer = read_msr(IA32_EFER); + write_msr(IA32_EFER, efer | SCE_BIT); + + write_msr(IA32_STAR, star_val); + write_msr(IA32_LSTAR, lstar_val); + write_msr(IA32_FMASK, fmask_val); + } + + // 初期カーネルスタックは現在のRSPを使用し、後続のコンテキストスイッチで更新する + let kstack_top: u64; + unsafe { + core::arch::asm!("mov {}, rsp", out(reg) kstack_top, options(nomem, nostack, preserves_flags)); + } + crate::percpu::init_boot_cpu(kstack_top); + + crate::info!("SYSCALL/SYSRET initialized: LSTAR={:#x}", lstar_val); +} + +/// SYSCALL カーネルスタックを更新する (コンテキストスイッチ時に呼ぶ) +pub fn update_kernel_rsp(rsp: u64) { + // SeqCst を使用してメモリ順序を保証する (MED-05) + crate::percpu::set_syscall_kernel_rsp(rsp); +} + +/// KPTI: 現在CR3がユーザーならカーネルCR3へ切り替え、元のCR3を返す +pub fn switch_to_kernel_page_table() -> u64 { + let kernel_cr3 = crate::percpu::kernel_cr3(); + if kernel_cr3 == 0 { + return 0; + } + let (current_cr3, _) = x86_64::registers::control::Cr3::read(); + let current = current_cr3.start_address().as_u64(); + if current == kernel_cr3 { + return 0; + } + crate::mem::paging::switch_page_table(kernel_cr3); + current +} + +/// KPTI: 以前のCR3へ戻す(0はno-op) +pub fn restore_page_table(previous_cr3: u64) { + if previous_cr3 != 0 { + crate::mem::paging::switch_page_table(previous_cr3); + } +} + +/// KPTI: 現在スレッドがユーザー権限なら、そのプロセスのユーザーCR3へ切り替える +pub fn switch_to_current_thread_user_page_table() { + let tid = match crate::task::current_thread_id() { + Some(t) => t, + None => return, + }; + let pid = match crate::task::with_thread(tid, |t| t.process_id()) { + Some(p) => p, + None => return, + }; + let is_core = crate::task::with_process(pid, |p| p.privilege()) + .is_some_and(|lvl| lvl == crate::task::PrivilegeLevel::Core); + if is_core { + return; + } + if let Some(user_pt) = crate::task::with_process(pid, |p| p.page_table()).flatten() { + crate::mem::paging::switch_page_table(user_pt); + } +} + +/// KPTI: SYSCALL/INT入口でカーネルCR3へ切り替える +pub fn kpti_enter_for_current_thread() { + let previous = switch_to_kernel_page_table(); + if let Some(tid) = crate::task::current_thread_id() { + crate::task::with_thread_mut(tid, |t| t.set_syscall_user_cr3(previous)); + } +} + +/// KPTI: SYSCALL/INT出口でユーザーCR3へ戻す +pub fn kpti_leave_for_current_thread() { + let restore = crate::task::current_thread_id() + .and_then(|tid| { + crate::task::with_thread_mut(tid, |t| { + let cr3 = t.syscall_user_cr3(); + t.set_syscall_user_cr3(0); + cr3 + }) + }) + .unwrap_or(0); + restore_page_table(restore); +} + +/// 例外/IRQ 入口で、ユーザー文脈から入った場合のみ kernel CR3 と hardening 状態をそろえる。 +pub fn kpti_enter_for_trap(from_user: bool) -> bool { + if !from_user { + return false; + } + let previous = switch_to_kernel_page_table(); + if previous != 0 { + crate::cpu::reassert_runtime_hardening(); + } + previous != 0 +} + +/// 例外/IRQ からの復帰で、ユーザー文脈へ戻る場合は復帰先スレッドの user CR3 を再設定する。 +pub fn kpti_leave_after_trap(entered_from_user: bool) { + if entered_from_user { + switch_to_current_thread_user_page_table(); + } +} + +/// SYSCALL エントリポイント (naked function) +/// +/// 呼ばれた時点: +/// RSP = ユーザースタック (そのまま) +/// RCX = ユーザー RIP +/// R11 = ユーザー RFLAGS +/// RAX = syscall 番号 +/// RDI/RSI/RDX/R10/R8/R9 = 引数 +/// 割り込み: 禁止 (FMASK で IF クリア済み) +/// +/// # Safety +/// CPU が SYSCALL エントリ規約どおりのレジスタ状態でこの関数へ入ることを前提とする。 +#[unsafe(naked)] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn syscall_entry() { + core::arch::naked_asm!( + // ユーザーRSPを per-CPU 一時領域へ退避してからカーネルスタックへ切り替える + "swapgs", + "mov qword ptr gs:[{user_rsp_tmp_off}], rsp", + "mov rsp, qword ptr gs:[{sys_rsp_off}]", + // ユーザー復帰時に必要な GPR を保存(Linux syscall ABI: RCX/R11以外も保持) + "push rax", // syscall 番号 + "push rdi", // arg0 + "push rsi", // arg1 + "push rdx", // arg2 + "push r10", // arg3 + "push r8", // arg4 + "push r9", // arg5 + "push rcx", // user RIP + "push r11", // user RFLAGS + "push rbp", + "push rbx", + "push r12", + "push r13", + "push r14", + "push r15", + "push qword ptr gs:[{user_rsp_tmp_off}]", // user RSP + // call 前の 16-byte alignment を満たす + "sub rsp, 8", + + // カーネルデータセグメントを設定 + "mov cx, 0x10", + "mov ds, cx", + "mov es, cx", + + // fork/clone のときだけ現在スレッドへユーザーコンテキストを記録 + // align slot ありレイアウト: + // [rsp+8]=user RSP, [rsp+64]=user RFLAGS, [rsp+72]=user RIP, [rsp+128]=syscall num + "mov rax, [rsp + 128]", + "cmp rax, 56", + "je 3f", + "cmp rax, 57", + "jne 4f", + "3:", + "mov rdi, rax", + "mov rsi, [rsp + 72]", // user RIP + "mov rdx, [rsp + 8]", // user RSP + "mov rcx, [rsp + 64]", // user RFLAGS + "call {save_ctx_fn}", + "4:", + + // 割り込みを再有効化 (カーネルスタックに切り替え済みなので安全) + "sti", + + // syscall 引数を system V ABI に並べ替えて dispatch を呼ぶ + // dispatch(num, arg0, arg1, arg2, arg3, arg4) + // align slot ありレイアウト: + // [rsp+128]=num, [rsp+120]=arg0, [rsp+112]=arg1, [rsp+104]=arg2, [rsp+96]=arg3, [rsp+88]=arg4 + "mov rdi, [rsp + 128]", + "mov rsi, [rsp + 120]", + "mov rdx, [rsp + 112]", + "mov rcx, [rsp + 104]", + "mov r8, [rsp + 96]", + "mov r9, [rsp + 88]", + "call {dispatch}", + + // 割り込みを禁止 (ユーザーコンテキスト復元前) + "cli", + // 戻り値を保存スロットへ退避(元 num スロットを再利用) + "mov [rsp + 128], rax", + // align slot を捨てる + "add rsp, 8", + + // 保存したユーザーコンテキストを復元 + "pop rax", // user RSP(一時) + "pop r15", + "pop r14", + "pop r13", + "pop r12", + "pop rbx", + "pop rbp", + "pop r11", // user RFLAGS (SYSRET 用) + "pop rcx", // user RIP (SYSRET 用) + "mov qword ptr gs:[{user_rsp_tmp_off}], rax", + + // ユーザー FS ベースを現在スレッド状態から再取得して復元 (TLS) + "push rcx", + "push r11", + "push rax", + "call {fs_base_fn}", + "mov r8, rax", + "mov rdx, rax", + "shr rdx, 32", + "mov ecx, 0xC0000100", // IA32_FS_BASE MSR (ecx を上書きしても安全) + "mov rax, r8", + "wrmsr", + "pop rax", + "pop r11", + "pop rcx", + + // CVE-2012-0217 緩和策: SYSRETQ 前にユーザー RIP/RSP の正規アドレスチェック + // Intel CPU では SYSRETQ 実行時にRCX/RSPが非正規アドレス(bit 63:47 が不一致)だと + // Ring 0 で #GP が発生し、攻撃者が制御フローを握る恐れがある (CVE-2012-0217) + // ユーザー空間の正規アドレス: bit 63:47 = 0b000...0 (0x0000_7FFF_FFFF_FFFF 以下) + "mov rax, qword ptr gs:[{user_rsp_tmp_off}]", + "sar rax, 47", // 算術右シフト47bit: 正規なら全ビット0 + "test rax, rax", + "jnz 2f", // 非正規アドレス → プロセスを終了 + "mov rdx, rcx", + "sar rdx, 47", + "test rdx, rdx", + "jnz 2f", + + // ユーザーデータセグメントを再設定(後で rdx は復元される) + "mov dx, 0x1b", + "mov ds, dx", + "mov es, dx", + + // Linux syscall ABI に合わせて volatile 引数レジスタも復元 + "pop r9", + "pop r8", + "pop r10", + "pop rdx", + "pop rsi", + "pop rdi", + "pop rax", // syscall 戻り値 + + // ユーザー RSP に切り替えて SYSRETQ + "mov rsp, qword ptr gs:[{user_rsp_tmp_off}]", + "swapgs", + "sysretq", + + // 非正規RIP/RSP検出: カーネルスタックに戻してプロセスを終了 + "2:", + "mov rsp, qword ptr gs:[{sys_rsp_off}]", + "call {kill_fn}", + + sys_rsp_off = const crate::percpu::GS_SYSCALL_KERNEL_RSP_OFFSET, + user_rsp_tmp_off = const crate::percpu::GS_SYSCALL_USER_RSP_TMP_OFFSET, + save_ctx_fn = sym super::save_user_context_for_fork, + fs_base_fn = sym current_thread_fs_base_for_sysret, + dispatch = sym super::syscall_dispatch_sysv, + kill_fn = sym kill_non_canonical_rsp, + ); +} + +extern "sysv64" fn current_thread_fs_base_for_sysret() -> u64 { + if let Some(tid) = crate::task::current_thread_id() { + if let Some(fs_base) = crate::task::with_thread(tid, |t| t.fs_base()) { + return fs_base; + } + } + unsafe { crate::cpu::read_fs_base() } +} + +/// CVE-2012-0217 緩和策: 非正規RIP/RSPを持つプロセスを終了させる +unsafe extern "C" fn kill_non_canonical_rsp() -> ! { + crate::warn!("CVE-2012-0217: non-canonical user RIP/RSP detected, killing process"); + crate::task::exit_current_task(u64::MAX) +} + +unsafe fn read_msr(msr: u32) -> u64 { + let lo: u32; + let hi: u32; + core::arch::asm!( + "rdmsr", + in("ecx") msr, + out("eax") lo, + out("edx") hi, + options(nomem, nostack) + ); + ((hi as u64) << 32) | (lo as u64) +} + +unsafe fn write_msr(msr: u32, val: u64) { + let lo = val as u32; + let hi = (val >> 32) as u32; + core::arch::asm!( + "wrmsr", + in("ecx") msr, + in("eax") lo, + in("edx") hi, + options(nomem, nostack) + ); +} diff --git a/src/core/syscall/task.rs b/src/core/syscall/task.rs new file mode 100644 index 0000000..a267cf6 --- /dev/null +++ b/src/core/syscall/task.rs @@ -0,0 +1,82 @@ +//! タスク関連システムコール + +pub fn yield_now() -> u64 { + crate::task::yield_now(); + 0 +} + +/// 現在のスレッドを終了 +pub fn exit(_code: u64) -> u64 { + if let Some(id) = crate::task::current_thread_id() { + crate::task::terminate_thread(id); + 0 + } else { + crate::syscall::EINVAL + } +} + +/// 現在のスレッドIDを取得 +pub fn get_thread_id() -> u64 { + match crate::task::current_thread_id() { + Some(id) => id.as_u64(), + None => crate::syscall::EINVAL, + } +} + +/// スレッドIDからプロセスの権限レベルを取得 +/// +/// # 引数 +/// - `tid_val`: スレッドID (u64) +/// +/// # 戻り値 +/// 0=Core, 1=Service, 2=User, またはエラー (#22: ディスクサービスの特権検証に使用) +pub fn get_thread_privilege(tid_val: u64) -> u64 { + // スレッドIDに対応するプロセスIDを探す + let mut found_pid: Option = None; + crate::task::for_each_thread(|t| { + if found_pid.is_none() && t.id().as_u64() == tid_val { + found_pid = Some(t.process_id()); + } + }); + + let pid = match found_pid { + Some(p) => p, + None => return crate::syscall::EINVAL, + }; + + match crate::task::with_process(pid, |p| p.privilege()) { + Some(crate::task::PrivilegeLevel::Core) => 0, + Some(crate::task::PrivilegeLevel::Service) => 1, + Some(crate::task::PrivilegeLevel::User) => 2, + None => crate::syscall::EINVAL, + } +} + +/// スレッド名からIDを取得 +pub fn get_thread_id_by_name(name_ptr: u64, name_len: u64) -> u64 { + const MAX_NAME_LEN: usize = 64; + if name_ptr == 0 { + return crate::syscall::EINVAL; + } + let name_len = name_len as usize; + if name_len == 0 || name_len > MAX_NAME_LEN { + return crate::syscall::EINVAL; + } + let mut name_buf = [0u8; MAX_NAME_LEN]; + if crate::syscall::copy_from_user(name_ptr, &mut name_buf[..name_len]).is_err() { + return crate::syscall::EFAULT; + } + let name = match core::str::from_utf8(&name_buf[..name_len]) { + Ok(s) => s, + Err(_) => return crate::syscall::EINVAL, + }; + + let mut found: Option = None; + crate::task::for_each_thread(|t| { + if found.is_none() && t.name() == name { + found = Some(t.id().as_u64()); + } + }); + + found.unwrap_or_else(|| crate::syscall::ENOENT) +} diff --git a/src/core/syscall/time.rs b/src/core/syscall/time.rs new file mode 100644 index 0000000..644ced1 --- /dev/null +++ b/src/core/syscall/time.rs @@ -0,0 +1,147 @@ +//! 時間関連システムコール + +use super::types::{EAGAIN, EFAULT, EINVAL, SUCCESS}; +use crate::interrupt::spinlock::SpinLock; +use crate::task::ThreadId; + +#[derive(Clone, Copy)] +struct SleepEntry { + tid: ThreadId, + wake_tick: u64, +} + +const MAX_SLEEPERS: usize = crate::task::ThreadQueue::MAX_THREADS; +static SLEEP_QUEUE: SpinLock<[Option; MAX_SLEEPERS]> = + SpinLock::new([None; MAX_SLEEPERS]); + +fn register_sleep_entry(tid: ThreadId, wake_tick: u64) -> bool { + let mut queue = SLEEP_QUEUE.lock(); + + for slot in queue.iter_mut() { + if slot.is_some_and(|entry| entry.tid == tid) { + *slot = Some(SleepEntry { tid, wake_tick }); + return true; + } + } + + for slot in queue.iter_mut() { + if slot.is_none() { + *slot = Some(SleepEntry { tid, wake_tick }); + return true; + } + } + + false +} + +pub fn wake_due_sleepers(now_tick: u64) { + let mut wake_list = [None; MAX_SLEEPERS]; + let mut wake_count = 0usize; + + { + let mut queue = SLEEP_QUEUE.lock(); + for slot in queue.iter_mut() { + if let Some(entry) = *slot { + if now_tick >= entry.wake_tick { + if wake_count < wake_list.len() { + wake_list[wake_count] = Some(entry.tid); + wake_count += 1; + } + *slot = None; + } + } + } + } + + for tid in wake_list.iter().take(wake_count).flatten() { + crate::task::wake_thread(*tid); + } +} + +/// GetTicksシステムコール +/// +/// カーネル起動からのティック数を取得 +/// +/// # 戻り値 +/// ティック数 +pub fn get_ticks() -> u64 { + crate::interrupt::timer::get_ticks() +} + +/// clock_gettimeシステムコール (Linux互換) +/// +/// # 引数 +/// - `clk_id`: クロックID (0=CLOCK_REALTIME, 1=CLOCK_MONOTONIC) +/// - `ts_ptr`: timespec構造体へのポインタ +/// +/// # 戻り値 +/// 成功時は0 +pub fn clock_gettime(clk_id: u64, ts_ptr: u64) -> u64 { + const CLOCK_REALTIME: u64 = 0; + const CLOCK_MONOTONIC: u64 = 1; + const CLOCK_PROCESS_CPUTIME_ID: u64 = 2; + const CLOCK_THREAD_CPUTIME_ID: u64 = 3; + + if ts_ptr == 0 { + return EINVAL; + } + // ユーザー空間アドレスの有効性を検証する (timespec = 16バイト) + if !crate::syscall::validate_user_ptr(ts_ptr, 16) { + return EFAULT; + } + + // タイマーティックを使って時刻を計算 (1ティック = 10ms) + let ticks = get_ticks(); + let sec = ticks / 100; + let nsec = (ticks % 100) * 10_000_000; + + match clk_id { + CLOCK_REALTIME | CLOCK_MONOTONIC | CLOCK_PROCESS_CPUTIME_ID | CLOCK_THREAD_CPUTIME_ID => { + // timespec { tv_sec: i64, tv_nsec: i64 } + let mut buf = [0u8; 16]; + buf[0..8].copy_from_slice(&(sec as i64).to_ne_bytes()); + buf[8..16].copy_from_slice(&(nsec as i64).to_ne_bytes()); + match crate::syscall::copy_to_user(ts_ptr, &buf) { + Ok(()) => SUCCESS, + Err(e) => e, + } + } + _ => EINVAL, + } +} + +/// SleepUntilシステムコール +/// +/// 指定されたティック数まで待機する +/// +/// # 引数 +/// - `ticks`: 待機する絶対ティック数 +/// +/// # 戻り値 +/// 成功時は0 +pub fn sleep_until(ticks: u64) -> u64 { + if get_ticks() >= ticks { + return SUCCESS; + } + + let current_tid = match crate::task::current_thread_id() { + Some(tid) => tid, + None => return EINVAL, + }; + + let queued = x86_64::instructions::interrupts::without_interrupts(|| { + if !register_sleep_entry(current_tid, ticks) { + return false; + } + crate::task::sleep_thread(current_tid); + true + }); + if !queued { + return EAGAIN; + } + + while get_ticks() < ticks { + crate::task::yield_now(); + } + SUCCESS +} diff --git a/src/core/syscall/tty.rs b/src/core/syscall/tty.rs new file mode 100644 index 0000000..9230ea1 --- /dev/null +++ b/src/core/syscall/tty.rs @@ -0,0 +1,710 @@ +//! シンプルなTTYレイヤ +//! +//! 1台のコンソールを想定し、termios/winsize と stdin の最小ラインディシプリンを提供する。 + +use crate::interrupt::spinlock::SpinLock; +use crate::syscall::{copy_to_user, EFAULT, EINVAL, SUCCESS}; +use alloc::vec::Vec; + +const TERMIOS_SIZE: u64 = 36; +const TERMIO_SIZE: u64 = 18; +const WIN_SIZE: u64 = 8; + +const IFLAG_ICRNL: u32 = 0x0100; +const LFLAG_ISIG: u32 = 0x0001; +const LFLAG_ICANON: u32 = 0x0002; +const LFLAG_ECHO: u32 = 0x0008; +const CC_VTIME: usize = 5; +const CC_VMIN: usize = 6; + +const SC_LSHIFT: u8 = 0x2A; +const SC_RSHIFT: u8 = 0x36; +const SC_CAPSLOCK: u8 = 0x3A; +const SC_BACKSPACE: u8 = 0x0E; +const SC_ENTER: u8 = 0x1C; +const SC_TAB: u8 = 0x0F; +const SC_ESC: u8 = 0x01; +const SC_RELEASE: u8 = 0x80; +const SC_E0: u8 = 0xE0; +const OUT_CSI_MAX_SEQ: usize = 32; + +#[rustfmt::skip] +const MAP_NORMAL: [u8; 128] = [ + 0, 0x1B, b'1', b'2', b'3', b'4', b'5', b'6', + b'7', b'8', b'9', b'0', b'-', b'=', 0x08, b'\t', + b'q', b'w', b'e', b'r', b't', b'y', b'u', b'i', + b'o', b'p', b'[', b']', b'\n', 0, b'a', b's', + b'd', b'f', b'g', b'h', b'j', b'k', b'l', b';', + b':', b'`', 0, b'\\',b'z', b'x', b'c', b'v', + b'b', b'n', b'm', b',', b'.', b'/', 0, b'*', + 0, b' ', 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, b'7', + b'8', b'9', b'-', b'4', b'5', b'6', b'+', b'1', + b'2', b'3', b'0', b'.', 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +]; + +#[rustfmt::skip] +const MAP_SHIFT: [u8; 128] = [ + 0, 0x1B, b'!', b'@', b'#', b'$', b'%', b'^', + b'&', b'*', b'(', b')', b'_', b'+', 0x08, b'\t', + b'Q', b'W', b'E', b'R', b'T', b'Y', b'U', b'I', + b'O', b'P', b'{', b'}', b'\n', 0, b'A', b'S', + b'D', b'F', b'G', b'H', b'J', b'K', b'L', b':', + b'*', b'~', 0, b'|', b'Z', b'X', b'C', b'V', + b'B', b'N', b'M', b'<', b'>', b'?', 0, b'*', + 0, b' ', 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, b'7', + b'8', b'9', b'-', b'4', b'5', b'6', b'+', b'1', + b'2', b'3', b'0', b'.', 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +]; + +#[derive(Clone, Copy)] +struct TtyState { + iflag: u32, + oflag: u32, + cflag: u32, + lflag: u32, + line: u8, + cc: [u8; 19], + ws_row: u16, + ws_col: u16, + ws_xpixel: u16, + ws_ypixel: u16, + shift: bool, + caps: bool, + e0_prefix: bool, + out_esc_pending: bool, + out_csi_mode: bool, + out_csi_seq: [u8; OUT_CSI_MAX_SEQ], + out_csi_len: usize, + cursor_row: u16, // 1-origin + cursor_col: u16, // 1-origin +} + +impl TtyState { + const fn new() -> Self { + let mut cc = [0u8; 19]; + cc[6] = 1; // VMIN + cc[5] = 0; // VTIME + Self { + iflag: 0, + oflag: 0, + cflag: 0x30 | 0x80 | 0x800, + // 対話アプリ(vim等)を優先し、既定は非canonical/非echoで開始する。 + // shell.service は独自の行編集を行うため、この既定値でも影響しない。 + lflag: LFLAG_ISIG, + line: 0, + cc, + ws_row: 24, + ws_col: 80, + ws_xpixel: 0, + ws_ypixel: 0, + shift: false, + caps: false, + e0_prefix: false, + out_esc_pending: false, + out_csi_mode: false, + out_csi_seq: [0; OUT_CSI_MAX_SEQ], + out_csi_len: 0, + cursor_row: 1, + cursor_col: 1, + } + } +} + +static TTY_STATE: SpinLock = SpinLock::new(TtyState::new()); +static INPUT_QUEUE: crate::util::fifo::Fifo = crate::util::fifo::Fifo::new(); + +fn push_bytes(bytes: &[u8]) { + for &b in bytes { + let _ = INPUT_QUEUE.push(b); + } +} + +fn push_ascii_u16(out: &mut Vec, mut n: u16) { + if n == 0 { + out.push(b'0'); + return; + } + let mut buf = [0u8; 5]; + let mut i = buf.len(); + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + out.extend_from_slice(&buf[i..]); +} + +fn clamp_cursor(state: &mut TtyState) { + let rows = state.ws_row.max(1); + let cols = state.ws_col.max(1); + if state.cursor_row == 0 { + state.cursor_row = 1; + } else if state.cursor_row > rows { + state.cursor_row = rows; + } + if state.cursor_col == 0 { + state.cursor_col = 1; + } else if state.cursor_col > cols { + state.cursor_col = cols; + } +} + +fn parse_ascii_u16(bytes: &[u8]) -> Option { + let mut value = 0u16; + for &b in bytes { + if !b.is_ascii_digit() { + return None; + } + value = value.saturating_mul(10).saturating_add((b - b'0') as u16); + } + Some(value) +} + +fn csi_params(seq: &[u8]) -> (Option, [u16; 8], usize) { + let mut params = [0u16; 8]; + let mut count = 0usize; + let mut start = 0usize; + let private = match seq.first().copied() { + Some(b'?') | Some(b'>') | Some(b'!') => { + start = 1; + Some(seq[0]) + } + _ => None, + }; + let mut i = start; + let mut part_start = start; + while i <= seq.len() && count < params.len() { + if i == seq.len() || seq[i] == b';' { + if i == part_start { + params[count] = 0; + count += 1; + } else if let Some(v) = parse_ascii_u16(&seq[part_start..i]) { + params[count] = v; + count += 1; + } + part_start = i + 1; + } + i += 1; + } + (private, params, count) +} + +fn csi_param_or(params: &[u16; 8], count: usize, idx: usize, default: u16) -> u16 { + let v = if idx < count { params[idx] } else { default }; + if v == 0 { + default + } else { + v + } +} + +fn parse_decrqm_mode(seq: &[u8]) -> Option { + // `CSI ? Pm $ p` の Pm を抜き出す + if seq.len() < 3 || seq[0] != b'?' || *seq.last()? != b'$' { + return None; + } + parse_ascii_u16(&seq[1..seq.len() - 1]) +} + +fn handle_output_csi(state: &mut TtyState, final_byte: u8, replies: &mut Vec) { + let rows = state.ws_row.max(1); + let cols = state.ws_col.max(1); + let seq = &state.out_csi_seq[..state.out_csi_len]; + let (private, params, count) = csi_params(seq); + match final_byte { + b'A' => { + let n = csi_param_or(¶ms, count, 0, 1); + state.cursor_row = state.cursor_row.saturating_sub(n); + if state.cursor_row == 0 { + state.cursor_row = 1; + } + } + b'B' => { + let n = csi_param_or(¶ms, count, 0, 1); + state.cursor_row = core::cmp::min(state.cursor_row.saturating_add(n), rows); + } + b'C' => { + let n = csi_param_or(¶ms, count, 0, 1); + state.cursor_col = core::cmp::min(state.cursor_col.saturating_add(n), cols); + } + b'D' => { + let n = csi_param_or(¶ms, count, 0, 1); + state.cursor_col = state.cursor_col.saturating_sub(n); + if state.cursor_col == 0 { + state.cursor_col = 1; + } + } + b'H' | b'f' => { + state.cursor_row = csi_param_or(¶ms, count, 0, 1); + state.cursor_col = csi_param_or(¶ms, count, 1, 1); + clamp_cursor(state); + } + b'G' => { + state.cursor_col = csi_param_or(¶ms, count, 0, 1); + clamp_cursor(state); + } + b'd' => { + state.cursor_row = csi_param_or(¶ms, count, 0, 1); + clamp_cursor(state); + } + b'J' => { + let mode = if count > 0 { params[0] } else { 0 }; + if mode == 2 { + state.cursor_row = 1; + state.cursor_col = 1; + } + } + b'n' => { + // DSR: Device Status/CPR + let p0 = csi_param_or(¶ms, count, 0, 0); + if p0 == 5 { + // "OK" status report + if private == Some(b'?') { + replies.extend_from_slice(b"\x1b[?0n"); + } else { + replies.extend_from_slice(b"\x1b[0n"); + } + } else if p0 == 6 { + // Cursor Position Report + replies.extend_from_slice(b"\x1b["); + if private == Some(b'?') { + replies.push(b'?'); + } + push_ascii_u16(replies, state.cursor_row.max(1)); + replies.push(b';'); + push_ascii_u16(replies, state.cursor_col.max(1)); + replies.push(b'R'); + } + } + b'c' => { + // DA / Secondary DA (xterm 互換の代表値を返す) + if private == Some(b'>') { + replies.extend_from_slice(b"\x1b[>0;136;0c"); + } else { + replies.extend_from_slice(b"\x1b[?1;2c"); + } + } + b'p' => { + // DECRQM: `CSI ? Pm $ p` への応答 + if private == Some(b'?') { + if let Some(mode) = parse_decrqm_mode(seq) { + replies.extend_from_slice(b"\x1b[?"); + push_ascii_u16(replies, mode); + // 0 = unsupported / unknown + replies.extend_from_slice(b";0$y"); + } + } + } + _ => {} + } +} + +pub fn process_output(bytes: &[u8]) { + if bytes.is_empty() { + return; + } + let mut replies: Vec = Vec::new(); + { + let mut state = TTY_STATE.lock(); + let rows = state.ws_row.max(1); + let cols = state.ws_col.max(1); + for &b in bytes { + if state.out_csi_mode { + if (0x20..=0x3F).contains(&b) { + if state.out_csi_len < state.out_csi_seq.len() { + let idx = state.out_csi_len; + state.out_csi_seq[idx] = b; + state.out_csi_len += 1; + } else { + state.out_csi_mode = false; + state.out_csi_len = 0; + } + continue; + } + if (0x40..=0x7E).contains(&b) { + handle_output_csi(&mut state, b, &mut replies); + state.out_csi_mode = false; + state.out_csi_len = 0; + continue; + } + state.out_csi_mode = false; + state.out_csi_len = 0; + continue; + } + + if state.out_esc_pending { + state.out_esc_pending = false; + if b == b'[' { + state.out_csi_mode = true; + state.out_csi_len = 0; + continue; + } + continue; + } + + match b { + 0x1B => state.out_esc_pending = true, + b'\r' => state.cursor_col = 1, + b'\n' => { + if state.cursor_row < rows { + state.cursor_row += 1; + } + } + 0x08 => { + if state.cursor_col > 1 { + state.cursor_col -= 1; + } + } + b'\t' => { + let cur0 = state.cursor_col.saturating_sub(1); + let next = ((cur0 / 8) + 1) * 8 + 1; + state.cursor_col = core::cmp::min(next, cols); + } + 0x20..=0x7E => { + if state.cursor_col >= cols { + state.cursor_col = 1; + if state.cursor_row < rows { + state.cursor_row += 1; + } + } else { + state.cursor_col += 1; + } + } + _ => {} + } + } + clamp_cursor(&mut state); + } + if !replies.is_empty() { + push_bytes(&replies); + } +} + +fn decode_scancode_into_queue(sc: u8) { + let mut state = TTY_STATE.lock(); + if state.e0_prefix { + state.e0_prefix = false; + if (sc & SC_RELEASE) != 0 { + return; + } + match sc { + 0x48 => push_bytes(b"\x1b[A"), // Up + 0x50 => push_bytes(b"\x1b[B"), // Down + 0x4D => push_bytes(b"\x1b[C"), // Right + 0x4B => push_bytes(b"\x1b[D"), // Left + 0x47 => push_bytes(b"\x1b[H"), // Home + 0x4F => push_bytes(b"\x1b[F"), // End + 0x49 => push_bytes(b"\x1b[5~"), + 0x51 => push_bytes(b"\x1b[6~"), + 0x52 => push_bytes(b"\x1b[2~"), + 0x53 => push_bytes(b"\x1b[3~"), + _ => {} + } + return; + } + + if sc == SC_E0 { + state.e0_prefix = true; + return; + } + + if (sc & SC_RELEASE) != 0 { + let make = sc & !SC_RELEASE; + if make == SC_LSHIFT || make == SC_RSHIFT { + state.shift = false; + } + return; + } + + match sc { + SC_LSHIFT | SC_RSHIFT => { + state.shift = true; + return; + } + SC_CAPSLOCK => { + state.caps = !state.caps; + return; + } + SC_BACKSPACE => { + let _ = INPUT_QUEUE.push(0x7F); + return; + } + SC_ENTER => { + let _ = INPUT_QUEUE.push(b'\r'); + return; + } + SC_TAB => { + let _ = INPUT_QUEUE.push(b'\t'); + return; + } + SC_ESC => { + let _ = INPUT_QUEUE.push(0x1B); + return; + } + _ => {} + } + + let idx = sc as usize; + if idx >= MAP_NORMAL.len() { + return; + } + let normal = MAP_NORMAL[idx]; + if normal == 0 { + return; + } + let use_shift = state.shift ^ (state.caps && normal.is_ascii_alphabetic()); + let ch = if use_shift { MAP_SHIFT[idx] } else { normal }; + if ch != 0 { + let _ = INPUT_QUEUE.push(ch); + } +} + +fn feed_from_scancode_queue_nonblocking() { + while let Some(sc) = crate::util::ps2kbd::pop_scancode() { + decode_scancode_into_queue(sc); + } +} + +pub fn has_pending_input() -> bool { + feed_from_scancode_queue_nonblocking(); + !INPUT_QUEUE.is_empty() +} + +pub fn pending_input_len() -> usize { + feed_from_scancode_queue_nonblocking(); + INPUT_QUEUE.len() +} + +fn next_input_byte_blocking() -> u8 { + loop { + if let Some(b) = INPUT_QUEUE.pop() { + return b; + } + feed_from_scancode_queue_nonblocking(); + if let Some(b) = INPUT_QUEUE.pop() { + return b; + } + let sc = crate::syscall::keyboard::read_char_blocking(); + decode_scancode_into_queue(sc); + } +} + +fn next_input_byte_nonblocking() -> Option { + if let Some(b) = INPUT_QUEUE.pop() { + return Some(b); + } + feed_from_scancode_queue_nonblocking(); + INPUT_QUEUE.pop() +} + +fn next_input_byte_timeout(timeout_ms: u64) -> Option { + if let Some(b) = next_input_byte_nonblocking() { + return Some(b); + } + if timeout_ms == 0 { + return None; + } + let mut remain = timeout_ms; + while remain > 0 { + crate::syscall::process::sleep(1); + if let Some(b) = next_input_byte_nonblocking() { + return Some(b); + } + remain -= 1; + } + None +} + +#[inline] +fn normalize_input_byte(b: u8, iflag: u32) -> u8 { + if b == b'\r' && (iflag & IFLAG_ICRNL) != 0 { + b'\n' + } else { + b + } +} + +pub fn tcgets(arg: u64) -> u64 { + if arg == 0 || !crate::syscall::validate_user_ptr(arg, TERMIOS_SIZE) { + return EINVAL; + } + let state = *TTY_STATE.lock(); + let mut buf = [0u8; TERMIOS_SIZE as usize]; + buf[0..4].copy_from_slice(&state.iflag.to_ne_bytes()); + buf[4..8].copy_from_slice(&state.oflag.to_ne_bytes()); + buf[8..12].copy_from_slice(&state.cflag.to_ne_bytes()); + buf[12..16].copy_from_slice(&state.lflag.to_ne_bytes()); + buf[16] = state.line; + buf[17..36].copy_from_slice(&state.cc); + crate::syscall::copy_to_user(arg, &buf) + .map(|_| SUCCESS) + .unwrap_or_else(|e| e) +} + +pub fn tcsets(arg: u64) -> u64 { + if arg == 0 || !crate::syscall::validate_user_ptr(arg, TERMIOS_SIZE) { + return EINVAL; + } + let mut buf = [0u8; TERMIOS_SIZE as usize]; + if let Err(e) = crate::syscall::copy_from_user(arg, &mut buf) { + return e; + } + let iflag = u32::from_ne_bytes([buf[0], buf[1], buf[2], buf[3]]); + let oflag = u32::from_ne_bytes([buf[4], buf[5], buf[6], buf[7]]); + let cflag = u32::from_ne_bytes([buf[8], buf[9], buf[10], buf[11]]); + let lflag = u32::from_ne_bytes([buf[12], buf[13], buf[14], buf[15]]); + let line = buf[16]; + let mut cc = [0u8; 19]; + cc.copy_from_slice(&buf[17..36]); + let mut state = TTY_STATE.lock(); + state.iflag = iflag; + state.oflag = oflag; + state.cflag = cflag; + state.lflag = lflag; + state.line = line; + state.cc = cc; + if (state.lflag & LFLAG_ICANON) == 0 { + state.cc[CC_VMIN] = 1; + state.cc[CC_VTIME] = 0; + } + SUCCESS +} + +pub fn tcgeta(arg: u64) -> u64 { + if arg == 0 || !crate::syscall::validate_user_ptr(arg, TERMIO_SIZE) { + return EINVAL; + } + let state = *TTY_STATE.lock(); + let mut buf = [0u8; TERMIO_SIZE as usize]; + let iflag = (state.iflag & 0xFFFF) as u16; + let oflag = (state.oflag & 0xFFFF) as u16; + let cflag = (state.cflag & 0xFFFF) as u16; + let lflag = (state.lflag & 0xFFFF) as u16; + buf[0..2].copy_from_slice(&iflag.to_ne_bytes()); + buf[2..4].copy_from_slice(&oflag.to_ne_bytes()); + buf[4..6].copy_from_slice(&cflag.to_ne_bytes()); + buf[6..8].copy_from_slice(&lflag.to_ne_bytes()); + buf[8] = state.line; + buf[9..18].copy_from_slice(&state.cc[..9]); + crate::syscall::copy_to_user(arg, &buf) + .map(|_| SUCCESS) + .unwrap_or_else(|e| e) +} + +pub fn tcseta(arg: u64) -> u64 { + if arg == 0 || !crate::syscall::validate_user_ptr(arg, TERMIO_SIZE) { + return EINVAL; + } + let mut buf = [0u8; TERMIO_SIZE as usize]; + if let Err(e) = crate::syscall::copy_from_user(arg, &mut buf) { + return e; + } + let iflag = u16::from_ne_bytes([buf[0], buf[1]]) as u32; + let oflag = u16::from_ne_bytes([buf[2], buf[3]]) as u32; + let cflag = u16::from_ne_bytes([buf[4], buf[5]]) as u32; + let lflag = u16::from_ne_bytes([buf[6], buf[7]]) as u32; + let line = buf[8]; + let mut cc9 = [0u8; 9]; + cc9.copy_from_slice(&buf[9..18]); + let mut state = TTY_STATE.lock(); + state.iflag = iflag; + state.oflag = oflag; + state.cflag = cflag; + state.lflag = lflag; + state.line = line; + state.cc[..9].copy_from_slice(&cc9); + if (state.lflag & LFLAG_ICANON) == 0 { + state.cc[CC_VMIN] = 1; + state.cc[CC_VTIME] = 0; + } + SUCCESS +} + +pub fn get_winsize(arg: u64) -> u64 { + if arg == 0 || !crate::syscall::validate_user_ptr(arg, WIN_SIZE) { + return EINVAL; + } + let state = *TTY_STATE.lock(); + let mut buf = [0u8; WIN_SIZE as usize]; + buf[0..2].copy_from_slice(&state.ws_row.to_ne_bytes()); + buf[2..4].copy_from_slice(&state.ws_col.to_ne_bytes()); + buf[4..6].copy_from_slice(&state.ws_xpixel.to_ne_bytes()); + buf[6..8].copy_from_slice(&state.ws_ypixel.to_ne_bytes()); + crate::syscall::copy_to_user(arg, &buf) + .map(|_| SUCCESS) + .unwrap_or_else(|e| e) +} + +pub fn set_winsize(arg: u64) -> u64 { + if arg == 0 || !crate::syscall::validate_user_ptr(arg, WIN_SIZE) { + return EINVAL; + } + let mut buf = [0u8; WIN_SIZE as usize]; + if let Err(e) = crate::syscall::copy_from_user(arg, &mut buf) { + return e; + } + let row = u16::from_ne_bytes([buf[0], buf[1]]); + let col = u16::from_ne_bytes([buf[2], buf[3]]); + let xpixel = u16::from_ne_bytes([buf[4], buf[5]]); + let ypixel = u16::from_ne_bytes([buf[6], buf[7]]); + let mut state = TTY_STATE.lock(); + if row != 0 { + state.ws_row = row; + } + if col != 0 { + state.ws_col = col; + } + state.ws_xpixel = xpixel; + state.ws_ypixel = ypixel; + clamp_cursor(&mut state); + SUCCESS +} + +pub fn read_stdin(buf_ptr: u64, len: u64) -> u64 { + if buf_ptr == 0 || len == 0 { + return EFAULT; + } + if !crate::syscall::validate_user_ptr(buf_ptr, len) { + return EFAULT; + } + + let state = *TTY_STATE.lock(); + let canonical = (state.lflag & LFLAG_ICANON) != 0; + let iflag = state.iflag; + let mut out = alloc::vec::Vec::with_capacity(len as usize); + if canonical { + let first = normalize_input_byte(next_input_byte_blocking(), iflag); + out.push(first); + while (out.len() as u64) < len { + if out.last().copied() == Some(b'\n') { + break; + } + let b = normalize_input_byte(next_input_byte_blocking(), iflag); + out.push(b); + } + } else { + // 対話アプリ優先: noncanonical は raw 1バイト即返却。 + let b = match next_input_byte_nonblocking() { + Some(v) => v, + None => next_input_byte_blocking(), + }; + out.push(normalize_input_byte(b, iflag)); + } + + if let Err(errno) = copy_to_user(buf_ptr, &out) { + return errno; + } + out.len() as u64 +} diff --git a/src/core/syscall/types.rs b/src/core/syscall/types.rs new file mode 100644 index 0000000..5827577 --- /dev/null +++ b/src/core/syscall/types.rs @@ -0,0 +1,278 @@ +/// システムコール番号 (Linux x86_64 互換) +#[repr(u64)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SyscallNumber { + /// 読み込み + Read = 0, + /// 書き込み + Write = 1, + /// ベクタ読み込み + Readv = 19, + /// ベクタ書き込み + Writev = 20, + /// ファイルを開く + Open = 2, + /// ファイルを閉じる + Close = 3, + /// ファイル情報取得 (path) + Stat = 4, + /// ファイル情報取得 + Fstat = 5, + /// poll + Poll = 7, + /// ファイルシーク + Lseek = 8, + /// メモリマップ + Mmap = 9, + /// メモリアンマップ + Munmap = 11, + /// メモリブレーク + Brk = 12, + /// シグナル処理(スタブ) + RtSigaction = 13, + /// シグナルリターン + RtSigreturn = 15, + /// シグナルマスク(スタブ) + RtSigprocmask = 14, + /// clone (スレッド生成) + Clone = 56, + /// Fork + Fork = 57, + /// Execve + Execve = 59, + /// Wait + Wait = 61, + /// 現在のプロセスIDを取得 + GetPid = 39, + /// 現在のスレッドIDを取得 + GetTid = 186, + /// arch_prctl (TLS用FSベース設定) + ArchPrctl = 158, + /// clock_gettime + ClockGettime = 228, + /// futex + Futex = 202, + /// プロセス終了 + Exit = 60, + /// exit_group (全スレッドを終了) + ExitGroup = 231, + /// kill (シグナルを送る) + Kill = 62, + /// tkill (スレッドID宛てシグナル) + Tkill = 200, + /// tgkill (スレッドグループ+スレッドID宛てシグナル) + Tgkill = 234, + /// getcwd + Getcwd = 79, + /// truncate + Truncate = 76, + /// ftruncate + Ftruncate = 77, + /// getppid + GetPpid = 110, + /// sigaltstack + Sigaltstack = 131, + /// statfs + Statfs = 137, + /// setpgid + Setpgid = 109, + /// getpgid + Getpgid = 121, + /// setsid + Setsid = 112, + /// getsid + Getsid = 124, + /// ioctl + Ioctl = 16, + /// access + Access = 21, + /// select + Select = 23, + /// getuid + Getuid = 102, + /// getgid + Getgid = 104, + /// geteuid + Geteuid = 107, + /// getegid + Getegid = 108, + /// lstat (stat のシンボリックリンク非追跡版、ここでは stat と同一実装) + Lstat = 6, + /// readlink (スタブ) + Readlink = 89, + /// unlink + Unlink = 87, + /// fcntl (FD フラグ操作) + Fcntl = 72, + /// fsync + Fsync = 74, + /// fdatasync + Fdatasync = 75, + /// pipe + Pipe = 22, + /// dup + Dup = 32, + /// dup2 + Dup2 = 33, + /// mprotect + Mprotect = 10, + /// nanosleep + Nanosleep = 35, + /// uname + Uname = 63, + /// getrlimit + Getrlimit = 97, + /// set_tid_address + SetTidAddress = 218, + /// openat + Openat = 257, + /// getdents64 + Getdents64 = 217, + /// prlimit64 + Prlimit64 = 302, + /// pipe2 + Pipe2 = 293, + /// newfstatat (fstatat) + Newfstatat = 262, + /// unlinkat + Unlinkat = 263, + /// faccessat + Faccessat = 269, + /// pselect6 + Pselect6 = 270, + /// ppoll + Ppoll = 271, + /// set_robust_list + SetRobustList = 273, + /// readlinkat + Readlinkat = 267, + /// getrandom + Getrandom = 318, + + // mochiOS独自syscall (Linux未使用番号帯: 512+) + /// スケジューラへ譲る + Yield = 512, + /// タイマーティック数を取得 + GetTicks = 513, + /// IPC送信 + IpcSend = 514, + /// IPC受信 + IpcRecv = 515, + /// initfsから実行可能ファイルを実行 + Exec = 516, + /// スリープ (ms単位) + Sleep = 517, + /// 名前からプロセスIDを検索 + FindProcessByName = 518, + /// カーネルログを出力 + Log = 519, + /// I/Oポート入力 + PortIn = 520, + /// I/Oポート出力 + PortOut = 521, + /// ディレクトリ作成 + Mkdir = 522, + /// ディレクトリ削除 + Rmdir = 523, + /// ディレクトリエントリ読み取り + Readdir = 524, + /// カレントディレクトリ変更 + Chdir = 525, + /// キーボードから1文字読み取る(mochiOS 固有) + KeyboardRead = 526, + /// スレッドIDからプロセスの権限レベルを取得 (0=Core, 1=Service, 2=User) + GetThreadPrivilege = 527, + /// フレームバッファ情報を取得 (info_ptr: *mut FbInfo) + GetFramebufferInfo = 528, + /// フレームバッファをユーザー空間にマップ、マップ済み仮想アドレスを返す + MapFramebuffer = 529, + /// メモリ上の ELF バッファから新プロセスを起動 + ExecFromBuffer = 530, + /// コンソールカーソルのピクセルY位置を設定 + SetConsoleCursor = 531, + /// コンソールカーソルのピクセルY位置を取得 + GetConsoleCursor = 532, + /// IPC受信(ブロッキング版):メッセージが届くまでスリープして待機 + IpcRecvWait = 533, + /// キーボード入力の監視用タップ(通常入力を消費しない) + KeyboardReadTap = 534, + /// PS/2 マウスの3バイトパケットを読み取る(b0 | b1<<8 | b2<<16) + MouseRead = 535, + /// 物理アドレス範囲をユーザー空間にマップ + MapPhysicalRange = 536, + /// ユーザー仮想アドレスを物理アドレスへ変換 + VirtToPhys = 537, + /// I/Oポートから 16-bit ワード列を一括読み取り + PortInWords = 538, + /// I/Oポートへ 16-bit ワード列を一括書き込み + PortOutWords = 539, + /// キーボード入力キューへ raw スキャンコードを注入(Service/Core専用) + KeyboardInject = 540, + /// マウス入力キューへ 3バイトパケットを注入(Service/Core専用) + MouseInject = 541, + /// メモリ上の ELF バッファと実行パス名から新プロセスを起動 + ExecFromBufferNamed = 542, + /// メモリ上の ELF バッファと実行パス名+引数から新プロセスを起動 + ExecFromBufferNamedArgs = 543, + /// メモリ上の ELF バッファと実行パス名+引数+要求元スレッドIDから新プロセスを起動 + ExecFromBufferNamedArgsWithRequester = 544, + /// Execute by streaming ELF image into kernel (path_ptr, args_ptr) + ExecFromFsStream = 545, + /// 物理ページ配列をターゲットプロセスのアドレス空間にマップ(Service権限専用) + MapPhysicalPages = 546, + /// 仮想アドレスから物理アドレスを取得(Service権限専用) + GetPhysicalAddr = 547, + /// 共有用物理ページを割り当て、自プロセスにマップして物理アドレスを返す(Service権限専用) + AllocSharedPages = 548, + /// 物理ページをアンマップして解放(Service権限専用) + UnmapPages = 549, + /// IPC経由で物理ページをターゲットプロセスへ送信(Service権限専用) + IpcSendPages = 550, + /// PS/2 マウスの3バイトパケットを読み取る(ブロッキング版) + MouseReadWait = 551, + /// プロセス一覧を取得(ユーザーバッファへ書き込む) + ListProcesses = 552, +} + +/// 成功 +pub const SUCCESS: u64 = 0; + +// Linux互換エラーコード(負の値を u64 にキャストして返す) +/// 操作が許可されていない +pub const EPERM: u64 = (-1i64) as u64; +/// ファイルが見つからない +pub const ENOENT: u64 = (-2i64) as u64; +/// ディレクトリではない +pub const ENOTDIR: u64 = (-20i64) as u64; +/// プロセスが見つからない +pub const ESRCH: u64 = (-3i64) as u64; +/// I/Oエラー +pub const EIO: u64 = (-5i64) as u64; +/// 不正なファイルディスクリプタ +pub const EBADF: u64 = (-9i64) as u64; +/// 不正なアドレス +pub const EFAULT: u64 = (-14i64) as u64; +/// デバイスが見つからない +pub const ENXIO: u64 = (-6i64) as u64; +/// 無効な引数 +pub const EINVAL: u64 = (-22i64) as u64; +/// 未実装 +pub const ENOSYS: u64 = (-38i64) as u64; +/// データがない / ノンブロッキングで読み出しできない +pub const ENODATA: u64 = (-61i64) as u64; +/// 受信/送信できない(キュー空/満杯) +pub const EAGAIN: u64 = (-11i64) as u64; +/// メモリ不足 +pub const ENOMEM: u64 = (-12i64) as u64; +/// ファイルが既に存在する +pub const EEXIST: u64 = (-17i64) as u64; +/// デバイスでない (TTY 操作に非 TTY FD を使用した) +pub const ENOTTY: u64 = (-25i64) as u64; +/// 引数が範囲外 +pub const ERANGE: u64 = (-34i64) as u64; +/// 操作がサポートされていない +pub const ENOTSUP: u64 = (-95i64) as u64; +/// パイプが壊れている +pub const EPIPE: u64 = (-32i64) as u64; +/// ファイルディスクリプタが多すぎる +pub const EMFILE: u64 = (-24i64) as u64; diff --git a/src/core/syscall/vga.rs b/src/core/syscall/vga.rs new file mode 100644 index 0000000..7f0e2ee --- /dev/null +++ b/src/core/syscall/vga.rs @@ -0,0 +1,125 @@ +//! フレームバッファ関連のシステムコール + +use super::types::{EFAULT, EINVAL, ENOMEM, SUCCESS}; + +/// ユーザー空間に返すフレームバッファ情報構造体のレイアウト +/// +/// ```text +/// offset size field +/// 0 4 width (ピクセル単位) +/// 4 4 height (ピクセル単位) +/// 8 4 stride (1行あたりの u32 ピクセル数) +/// 12 4 _pad +/// ``` +const FB_INFO_SIZE: u64 = 16; + +/// フレームバッファ情報をユーザー空間の構造体に書き込む +/// +/// # Arguments +/// * `info_ptr` - `FbInfo` 構造体へのユーザー空間ポインタ +/// +/// # Returns +/// 成功時は `SUCCESS`、失敗時はエラーコード +pub fn get_framebuffer_info(info_ptr: u64) -> u64 { + if info_ptr == 0 { + return EFAULT; + } + if !crate::syscall::validate_user_ptr(info_ptr, FB_INFO_SIZE) { + return EFAULT; + } + + let fb_info = match crate::util::vga::get_info() { + Some(i) => i, + None => return EINVAL, + }; + + let mut buf = [0u8; FB_INFO_SIZE as usize]; + buf[0..4].copy_from_slice(&(fb_info.width as u32).to_ne_bytes()); + buf[4..8].copy_from_slice(&(fb_info.height as u32).to_ne_bytes()); + buf[8..12].copy_from_slice(&(fb_info.stride as u32).to_ne_bytes()); + buf[12..16].copy_from_slice(&0u32.to_ne_bytes()); + match crate::syscall::copy_to_user(info_ptr, &buf) { + Ok(()) => SUCCESS, + Err(e) => e, + } +} + +/// フレームバッファ物理メモリを呼び出し元プロセスのアドレス空間にマップする +/// +/// # Returns +/// マップされた仮想アドレス、または失敗時はエラーコード +pub fn map_framebuffer() -> u64 { + let fb_info = match crate::util::vga::get_info() { + Some(i) => i, + None => return EINVAL, + }; + + let phys_addr = fb_info.addr; + let phys_base = phys_addr & !0xfffu64; + let phys_offset = phys_addr & 0xfffu64; + // stride は u32 ピクセル単位、1ピクセル = 4バイト + let fb_size = match (fb_info.height as u64) + .checked_mul(fb_info.stride as u64) + .and_then(|v| v.checked_mul(4)) + { + Some(v) => v, + None => return EINVAL, + }; + if fb_size == 0 { + return EINVAL; + } + // 先頭の物理オフセット分も含めてページ境界まで拡張する + let map_size = fb_size + .checked_add(phys_offset) + .and_then(|v| v.checked_add(0xfff)) + .map(|v| v & !0xfffu64) + .unwrap_or(0); + + if map_size == 0 { + return EINVAL; + } + + let tid = match crate::task::current_thread_id() { + Some(t) => t, + None => return ENOMEM, + }; + let pid = match crate::task::with_thread(tid, |t| t.process_id()) { + Some(p) => p, + None => return ENOMEM, + }; + + let result = crate::task::with_process_mut(pid, |process| { + // mmap ベースアドレスとして heap_end を流用する + if process.heap_start() == 0 { + let default_base = 0x5000_0000u64; + process.set_heap_start(default_base); + process.set_heap_end(default_base); + } + + let base = process.heap_end(); + let map_start = base.checked_add(0xfff).map(|v| v & !0xfffu64).unwrap_or(0); + if map_start == 0 || map_start > 0x0000_7FFF_FFFF_FFFF { + return Err(ENOMEM); + } + + let pt_phys = match process.page_table() { + Some(p) => p, + None => return Err(ENOMEM), + }; + + let new_end = map_start.checked_add(map_size).ok_or(ENOMEM)?; + + crate::mem::paging::map_physical_range_to_user(pt_phys, map_start, phys_base, map_size) + .map_err(|_| ENOMEM)?; + + process.set_heap_end(new_end); + + Ok(map_start + phys_offset) + }); + + match result { + Some(Ok(va)) => va, + Some(Err(e)) => e, + None => ENOMEM, + } +} diff --git a/src/core/task/context.rs b/src/core/task/context.rs new file mode 100644 index 0000000..e21eea4 --- /dev/null +++ b/src/core/task/context.rs @@ -0,0 +1,437 @@ +use crate::task::ids::ThreadId; +use crate::task::process::with_process; +use crate::task::thread::THREAD_QUEUE; + +/// CPU コンテキスト(callee-saved 等を保存) +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct Context { + pub rsp: u64, + pub rbp: u64, + pub rbx: u64, + pub r12: u64, + pub r13: u64, + pub r14: u64, + pub r15: u64, + pub rdi: u64, // MS ABI Callee-saved + pub rsi: u64, // MS ABI Callee-saved + /// 命令ポインタ(戻り先アドレス) + pub rip: u64, + pub rflags: u64, +} + +impl Default for Context { + fn default() -> Self { + Self::new() + } +} + +impl Context { + pub const fn new() -> Self { + Self { + rsp: 0, + rbp: 0, + rbx: 0, + r12: 0, + r13: 0, + r14: 0, + r15: 0, + rdi: 0, + rsi: 0, + rip: 0, + rflags: 0, + } + } +} + +/// 初回スイッチ時に使用するダミーコンテキスト(保存先として使われるが値は参照されない) +static mut INITIAL_DUMMY_CONTEXT: Context = Context::new(); +/// 現在のスレッドから次のスレッドへコンテキストを切り替える +/// +/// Context構造体のレイアウト: +/// offset 0x00: rsp +/// offset 0x08: rbp +/// offset 0x10: rbx +/// offset 0x18: r12 +/// offset 0x20: r13 +/// offset 0x28: r14 +/// offset 0x30: r15 +/// offset 0x38: rdi +/// offset 0x40: rsi +/// offset 0x48: rip +/// offset 0x50: rflags +/// +/// # Safety +/// `old_context`/`new_context` は有効な `Context` 領域を指し、呼び出し規約に従って +/// コンテキスト切替可能な状態である必要がある。 +#[unsafe(naked)] +#[no_mangle] +pub unsafe extern "C" fn switch_context(old_context: *mut Context, new_context: *const Context) { + core::arch::naked_asm!( + "cli", + // save current (ret address is at [rsp]) + "lea rax, [rsp + 0x08]", + // system V AMD64 ABI (used by x86_64-unknown-none): + // 第1引数 (old_context) = rdi + // 第2引数 (new_context) = rsi + "mov [rdi + 0x00], rax", // rsp + "mov [rdi + 0x08], rbp", // rbp + "mov [rdi + 0x10], rbx", // rbx + "mov [rdi + 0x18], r12", // r12 + "mov [rdi + 0x20], r13", // r13 + "mov [rdi + 0x28], r14", // r14 + "mov [rdi + 0x30], r15", // r15 + "mov [rdi + 0x38], rdi", // rdi + "mov [rdi + 0x40], rsi", // rsi + // 戻り先アドレス(call命令でスタックにpushされている)を保存 + "mov rax, [rsp]", + "mov [rdi + 0x48], rax", // rip + // RFLAGSを保存 + "pushfq", + "pop rax", + "mov [rdi + 0x50], rax", // rflags + // 新しいコンテキストを復元 + "mov rax, [rsi + 0x48]", // 新しいrip + "mov r11, [rsi + 0x50]", // 新しいrflags + "mov rbx, [rsi + 0x10]", // rbx + "mov r12, [rsi + 0x18]", // r12 + "mov r13, [rsi + 0x20]", // r13 + "mov r14, [rsi + 0x28]", // r14 + "mov r15, [rsi + 0x30]", // r15 + "mov rdi, [rsi + 0x38]", // rdi + "mov rbp, [rsi + 0x08]", // rbp + "mov rsp, [rsi + 0x00]", // rsp (rsiを最後に使う) + "mov rsi, [rsi + 0x40]", // rsi (rspセット後、rsiを上書きしてOK) + // RFLAGSを復元 + "push r11", + "popfq", + "jmp rax", + ); +} + +/// 別スレッドへ切替(通常呼び出し経路) +/// +/// # Safety +/// 呼び出し側は `next_id` が有効な実行可能スレッドであることを保証する必要がある。 +pub unsafe fn switch_to_thread(current_id: Option, next_id: ThreadId) { + // コンテキストスイッチ中は割り込みを禁止する + // ロック解放からコンテキストスイッチまでの間に割り込みが入ると不整合が起きる可能性があるため + x86_64::instructions::interrupts::disable(); + + crate::debug!( + "switch_to_thread: current_id={:?}, next_id={:?}", + current_id, + next_id + ); + + let mut queue = THREAD_QUEUE.lock(); + + let (old_ctx_ptr, current_process_id, current_priv) = if let Some(id) = current_id { + if let Some(thread) = queue.get_mut(id) { + if !thread.is_kernel_stack_guard_intact() { + let bottom = thread.kernel_stack_bottom(); + let top = thread.kernel_stack_top(); + drop(queue); + crate::error!( + "Kernel stack guard corrupted: tid={:?}, kstack=[{:#x}..{:#x})", + id, + bottom, + top + ); + crate::audit::log( + crate::audit::AuditEventKind::Quarantine, + "context switch detected kernel stack corruption", + ); + crate::task::terminate_thread(id); + return; + } + let ptr = thread.context_mut() as *mut Context; + crate::debug!( + " Current context ptr: {:p}, rsp={:#x}, rip={:#x}", + ptr, + thread.context().rsp, + thread.context().rip + ); + let pid = thread.process_id(); + let priv_level = + with_process(pid, |p| p.privilege()).unwrap_or(crate::task::PrivilegeLevel::Core); + (ptr, Some(pid), priv_level) + } else { + return; // 現在のスレッドが見つからない + } + } else { + // 現在のスレッドがない場合(初回スイッチ)はダミーに書き込む(値は捨てられる) + crate::debug!(" No current thread (initial switch)"); + ( + unsafe { core::ptr::addr_of_mut!(INITIAL_DUMMY_CONTEXT) }, + None, + crate::task::PrivilegeLevel::Core, + ) + }; + + // 次のスレッドのコンテキストへのポインタとカーネルスタックトップを取得 + let ( + new_context_ptr, + next_kstack_top, + next_process_id, + next_fs_base, + _next_in_syscall, + next_priv, + ) = if let Some(thread) = queue.get(next_id) { + let ptr = thread.context() as *const Context; + let kstack = thread.kernel_stack_top(); + let pid = thread.process_id(); + let fs = thread.fs_base(); + let in_syscall = thread.in_syscall(); + let priv_level = + with_process(pid, |p| p.privilege()).unwrap_or(crate::task::PrivilegeLevel::Core); + crate::debug!( + " Next context ptr: {:p}, rsp={:#x}, rip={:#x}, kstack={:#x}", + ptr, + thread.context().rsp, + thread.context().rip, + kstack + ); + (ptr, kstack, pid, fs, in_syscall, priv_level) + } else { + return; // 次のスレッドが見つからない + }; + + drop(queue); + + // 実際に切り替える直前に current thread を更新する。 + // これにより「currentだけ先に更新される競合窓」を避ける。 + crate::task::set_current_thread(Some(next_id)); + + // TSSのRSP0とSYSCALL用カーネルスタックを更新 + crate::mem::tss::set_rsp0(next_kstack_top); + crate::syscall::syscall_entry::update_kernel_rsp(next_kstack_top); + // SYSCALL 入口の swapgs により IA32_KERNEL_GS_BASE がユーザー値へ一時退避されるため、 + // ブロッキング syscall 中に他スレッドへ切り替える前に per-CPU GS ベースへ戻しておく。 + crate::percpu::install_current_cpu_gs_base(); + let predictor_domain_changed = + current_process_id != Some(next_process_id) || current_priv != next_priv; + if predictor_domain_changed { + crate::cpu::branch_predictor_barrier(); + } + crate::cpu::reassert_runtime_hardening(); + + // 次のスレッドの FS ベースを復元 (TLS) + unsafe { + crate::cpu::write_fs_base(next_fs_base); + } + + // switch_context はカーネル管理領域上の Context とカーネルスタックを読む。 + // ユーザー CR3 に切り替えてから実行すると、KPTI で外した kernel heap 参照が + // kernel-mode page fault になるため、実際の user CR3 への切替は iretq 直前に行う。 + let kernel_cr3 = crate::percpu::kernel_cr3(); + if kernel_cr3 != 0 { + crate::mem::paging::switch_page_table(kernel_cr3); + } + + crate::debug!("About to perform context switch..."); + switch_context(old_ctx_ptr, new_context_ptr); +} + +/// カーネルから直接ユーザーモードに入るためのヘルパ(最初のユーザスレッド用) +/// +/// # Safety +/// `ctx` はユーザーモード復帰に必要な有効なレジスタ値/セグメント値を含んでいる必要がある。 +pub unsafe fn enter_user_from_kernel(ctx: &Context) -> ! { + let user_cs = crate::mem::gdt::user_code_selector() as u64; + let user_ds = crate::mem::gdt::user_data_selector() as u64; + + core::arch::asm!( + "cli", + "mov rbx, {rbx}", + "mov r12, {r12}", + "mov r13, {r13}", + "mov r14, {r14}", + "mov r15, {r15}", + "mov rbp, {rbp}", + // iretq が CS/RIP/RFLAGS->(RSP/SS) を期待するので順に push + "push {ss}", + "push {user_rsp}", + "push {rflags}", + "push {cs}", + "push {rip}", + // restore user GS base from IA32_KERNEL_GS_BASE + "swapgs", + "iretq", + rbx = in(reg) ctx.rbx, + r12 = in(reg) ctx.r12, + r13 = in(reg) ctx.r13, + r14 = in(reg) ctx.r14, + r15 = in(reg) ctx.r15, + rbp = in(reg) ctx.rbp, + ss = in(reg) user_ds, + user_rsp = in(reg) ctx.rsp, + rflags = in(reg) ctx.rflags, + cs = in(reg) user_cs, + rip = in(reg) ctx.rip, + options(noreturn) + ) +} + +/// 割込み内からの切替。呼び出し側で割込み時のレジスタを `saved` に収めて渡す。 +/// +/// # Safety +/// `saved` は現在スレッドの正しい保存コンテキストであり、`next_id` は有効な +/// 実行可能スレッドである必要がある。 +pub unsafe fn switch_to_thread_from_isr( + current_id: Option, + next_id: ThreadId, + saved: Context, +) { + let mut queue = THREAD_QUEUE.lock(); + + let (old_ctx_ptr, current_process_id, current_priv) = if let Some(id) = current_id { + if let Some(thread) = queue.get_mut(id) { + if !thread.is_kernel_stack_guard_intact() { + let bottom = thread.kernel_stack_bottom(); + let top = thread.kernel_stack_top(); + drop(queue); + crate::error!( + "Kernel stack guard corrupted (ISR): tid={:?}, kstack=[{:#x}..{:#x})", + id, + bottom, + top + ); + crate::audit::log( + crate::audit::AuditEventKind::Quarantine, + "isr context switch detected kernel stack corruption", + ); + crate::task::terminate_thread(id); + return; + } + let pid = thread.process_id(); + let priv_level = + with_process(pid, |p| p.privilege()).unwrap_or(crate::task::PrivilegeLevel::Core); + (thread.context_mut() as *mut Context, Some(pid), priv_level) + } else { + return; + } + } else { + ( + unsafe { core::ptr::addr_of_mut!(INITIAL_DUMMY_CONTEXT) }, + None, + crate::task::PrivilegeLevel::Core, + ) + }; + + let (new_ctx_ptr, next_priv, next_kstack_top, next_fs_base, next_process_id, _next_in_syscall) = + if let Some(thread) = queue.get(next_id) { + let ptr = thread.context() as *const Context; + let proc = thread.process_id(); + let priv_level = + with_process(proc, |p| p.privilege()).unwrap_or(crate::task::PrivilegeLevel::Core); + let kstack = thread.kernel_stack_top(); + let fs = thread.fs_base(); + let in_syscall = thread.in_syscall(); + (ptr, priv_level, kstack, fs, proc, in_syscall) + } else { + return; + }; + + if !old_ctx_ptr.is_null() { + unsafe { + *old_ctx_ptr = saved; + } + } + + drop(queue); + + // ISR 経路でも、実際の遷移直前に current thread を更新する。 + crate::task::set_current_thread(Some(next_id)); + + // TSSのRSP0を更新 + crate::mem::tss::set_rsp0(next_kstack_top); + + // SYSCALL用カーネルスタックも更新 (次のスレッドのカーネルスタックを使う) + crate::syscall::syscall_entry::update_kernel_rsp(next_kstack_top); + // SYSCALL 入口の swapgs により IA32_KERNEL_GS_BASE がユーザー値へ一時退避されるため、 + // ブロッキング syscall 中に他スレッドへ切り替える前に per-CPU GS ベースへ戻しておく。 + crate::percpu::install_current_cpu_gs_base(); + let predictor_domain_changed = + current_process_id != Some(next_process_id) || current_priv != next_priv; + if predictor_domain_changed { + crate::cpu::branch_predictor_barrier(); + } + crate::cpu::reassert_runtime_hardening(); + + // 次のスレッドの FS ベースを復元 (TLS) + crate::cpu::write_fs_base(next_fs_base); + + // ISR 経路でもコンテキスト復元中は必ず kernel CR3 を使う。 + // ユーザー空間への復帰時は trap/syscall の出口または usermode trampoline が + // 復帰対象スレッドの user CR3 を設定する。 + let kernel_cr3 = crate::percpu::kernel_cr3(); + if kernel_cr3 != 0 { + crate::mem::paging::switch_page_table(kernel_cr3); + } + + if next_priv == crate::task::PrivilegeLevel::Core { + core::arch::asm!( + "cli", + "mov rsp, rax", // rsp = saved.rsp + "mov rbp, {rbp_val}", // Restore rbp + "mov rbx, {rbx_val}", // Restore rbx + "push rcx", // push rflags + "popfq", // restore rflags + "jmp rdx", // jump to rip + + // Fixed registers + in("rax") saved.rsp, + in("rcx") saved.rflags, + in("rdx") saved.rip, + + in("r12") saved.r12, + in("r13") saved.r13, + in("r14") saved.r14, + in("r15") saved.r15, + in("rdi") saved.rdi, + in("rsi") saved.rsi, + + // Compiler allocated registers (will use r8-r11) + rbp_val = in(reg) saved.rbp, + rbx_val = in(reg) saved.rbx, + + options(noreturn) + ); + } else { + // ユーザーモードへ iretq で遷移するための準備 + let user_cs = crate::mem::gdt::user_code_selector() as u64; + let user_ds = crate::mem::gdt::user_data_selector() as u64; + + core::arch::asm!( + "cli", + "mov rbx, {rbx}", + "mov r12, {r12}", + "mov r13, {r13}", + "mov r14, {r14}", + "mov r15, {r15}", + "mov rbp, {rbp}", + // iretq が CS/RIP/RFLAGS->(RSP/SS) を期待するので順に push + "push {ss}", + "push {user_rsp}", + "push {rflags}", + "push {cs}", + "push {rip}", + // restore user GS base from IA32_KERNEL_GS_BASE + "swapgs", + "iretq", + rbx = in(reg) saved.rbx, + r12 = in(reg) saved.r12, + r13 = in(reg) saved.r13, + r14 = in(reg) saved.r14, + r15 = in(reg) saved.r15, + rbp = in(reg) saved.rbp, + ss = in(reg) user_ds, + user_rsp = in(reg) saved.rsp, + rflags = in(reg) saved.rflags, + cs = in(reg) user_cs, + rip = in(reg) saved.rip, + options(noreturn) + ); + } +} diff --git a/src/core/task/elf.rs b/src/core/task/elf.rs new file mode 100644 index 0000000..329b1c4 --- /dev/null +++ b/src/core/task/elf.rs @@ -0,0 +1,560 @@ +//! ELFローダ + +use crate::init; +use crate::mem::{paging, user}; +use crate::result::{Kernel, Memory, Process, Result}; +use crate::task::{ + add_process, add_thread, remove_process, PrivilegeLevel, Process as TaskProcess, Thread, +}; +use core::sync::atomic::{AtomicU64, Ordering}; + +const ELF_MAGIC: [u8; 4] = [0x7F, b'E', b'L', b'F']; +const PT_LOAD: u32 = 1; +const PT_DYNAMIC: u32 = 2; +const PF_X: u32 = 0x1; +const PF_W: u32 = 0x2; + +const ET_DYN: u16 = 3; +const EM_X86_64: u16 = 0x3E; + +const DT_NULL: i64 = 0; +const DT_RELA: i64 = 7; +const DT_RELASZ: i64 = 8; +const DT_RELAENT: i64 = 9; + +const R_X86_64_RELATIVE: u32 = 8; + +const PIE_LOAD_BIAS: u64 = 0x2000_0000; +const PIE_ASLR_WINDOW_PAGES: u64 = 0x4000; // 64MiB +static PIE_ASLR_COUNTER: AtomicU64 = AtomicU64::new(0); + +#[repr(C)] +#[derive(Clone, Copy)] +struct Elf64Header { + e_ident: [u8; 16], + e_type: u16, + e_machine: u16, + e_version: u32, + e_entry: u64, + e_phoff: u64, + e_shoff: u64, + e_flags: u32, + e_ehsize: u16, + e_phentsize: u16, + e_phnum: u16, + e_shentsize: u16, + e_shnum: u16, + e_shstrndx: u16, +} + +#[repr(C)] +#[derive(Clone, Copy)] +struct Elf64Phdr { + p_type: u32, + p_flags: u32, + p_offset: u64, + p_vaddr: u64, + p_paddr: u64, + p_filesz: u64, + p_memsz: u64, + p_align: u64, +} + +#[derive(Debug, Clone, Copy)] +pub struct LoadedElf { + pub entry: u64, + pub load_bias: u64, + pub stack_top: u64, + pub stack_bottom: u64, +} + +struct ServiceSpawnGuard { + pid: crate::task::ProcessId, + page_table: u64, + kernel_stack: Option, + disarmed: bool, +} + +impl ServiceSpawnGuard { + fn new(pid: crate::task::ProcessId, page_table: u64) -> Self { + Self { + pid, + page_table, + kernel_stack: None, + disarmed: false, + } + } + + fn set_kernel_stack(&mut self, kernel_stack: u64) { + self.kernel_stack = Some(kernel_stack); + } + + fn disarm(&mut self) { + self.disarmed = true; + } +} + +impl Drop for ServiceSpawnGuard { + fn drop(&mut self) { + if self.disarmed { + return; + } + if let Some(stack) = self.kernel_stack { + crate::task::free_kernel_stack(stack); + } + let _ = remove_process(self.pid); + let _ = paging::destroy_user_page_table(self.page_table); + } +} + +#[inline] +fn aslr_mix64(mut x: u64) -> u64 { + x ^= x >> 30; + x = x.wrapping_mul(0xbf58_476d_1ce4_e5b9); + x ^= x >> 27; + x = x.wrapping_mul(0x94d0_49bb_1331_11eb); + x ^ (x >> 31) +} + +fn next_pie_load_bias() -> u64 { + if PIE_ASLR_COUNTER.load(Ordering::Relaxed) == 0 { + let mut init = crate::cpu::boot_entropy_u64() ^ 0xa726_f38d_c941_5e2b; + if init == 0 { + init = 1; + } + let _ = PIE_ASLR_COUNTER.compare_exchange(0, init, Ordering::SeqCst, Ordering::Relaxed); + } + let ctr = PIE_ASLR_COUNTER.fetch_add(0x9e37_79b9_7f4a_7c15, Ordering::Relaxed); + let ticks = crate::interrupt::timer::get_ticks(); + let hw = crate::cpu::hw_random_u64().unwrap_or(0); + let boot = crate::cpu::boot_entropy_u64(); + let offset_pages = + aslr_mix64(ctr ^ ticks.rotate_left(13) ^ hw.rotate_left(11) ^ boot) % PIE_ASLR_WINDOW_PAGES; + PIE_LOAD_BIAS + offset_pages * 4096 +} + +fn current_user_page_table() -> Result { + let pid = crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |thread| thread.process_id())) + .ok_or(Kernel::Memory(Memory::NotMapped))?; + crate::task::with_process(pid, |proc| proc.page_table()) + .flatten() + .ok_or(Kernel::Memory(Memory::NotMapped)) +} + +fn write_user_bytes_in_table(table_phys: u64, user_addr: u64, bytes: &[u8]) -> Result<()> { + paging::copy_to_user_in_table(table_phys, user_addr, bytes) +} + +fn write_user_u64_in_table(table_phys: u64, user_addr: u64, value: u64) -> Result<()> { + write_user_bytes_in_table(table_phys, user_addr, &value.to_ne_bytes()) +} + +pub fn load_elf(data: &[u8]) -> Result { + let table_phys = current_user_page_table()?; + load_elf_into(table_phys, data) +} + +pub fn load_elf_into(table_phys: u64, data: &[u8]) -> Result { + let header = parse_header(data)?; + validate_header(header)?; + + let load_bias = if header.e_type == ET_DYN { + next_pie_load_bias() + } else { + 0 + }; + + let phoff = header.e_phoff as usize; + let phentsize = header.e_phentsize as usize; + let phnum = header.e_phnum as usize; + + for i in 0..phnum { + let off = match i.checked_mul(phentsize).and_then(|x| phoff.checked_add(x)) { + Some(o) => o, + None => return Err(Kernel::InvalidParam), + }; + let phdr = read_phdr(data, off)?; + if phdr.p_type != PT_LOAD { + continue; + } + + let filesz = phdr.p_filesz as usize; + let memsz = phdr.p_memsz as usize; + if memsz == 0 { + continue; + } + if filesz > memsz { + return Err(Kernel::Memory(Memory::InvalidAddress)); + } + + let file_end = match (phdr.p_offset as usize).checked_add(filesz) { + Some(v) => v, + None => return Err(Kernel::Memory(Memory::InvalidAddress)), + }; + if file_end > data.len() { + return Err(Kernel::Memory(Memory::InvalidAddress)); + } + + let vaddr = phdr.p_vaddr.wrapping_add(load_bias); + let seg_src = &data[phdr.p_offset as usize..file_end]; + let writable = (phdr.p_flags & PF_W) != 0; + let executable = (phdr.p_flags & PF_X) != 0; + if writable && executable { + return Err(Kernel::Memory(Memory::PermissionDenied)); + } + paging::map_and_copy_segment_to( + table_phys, + vaddr, + filesz as u64, + phdr.p_memsz, + seg_src, + writable, + executable, + )?; + } + + if load_bias != 0 { + apply_relocations_to(table_phys, data, header, load_bias)?; + } + + let stack = user::alloc_user_stack_in_table(table_phys, 8)?; + + Ok(LoadedElf { + entry: header.e_entry.wrapping_add(load_bias), + load_bias, + stack_top: stack.top, + stack_bottom: stack.bottom, + }) +} + +pub fn spawn_service(path: &str, name: &'static str) -> Result<()> { + let data = init::fs::read(path).ok_or(Kernel::InvalidParam)?; + let new_pt_phys = paging::create_user_page_table()?; + + let mut process = TaskProcess::new(name, PrivilegeLevel::Service, None, 1); + process.set_page_table(new_pt_phys); + let pid = process.id(); + + if add_process(process).is_none() { + let _ = paging::destroy_user_page_table(new_pt_phys); + return Err(Kernel::Process(Process::MaxProcessesReached)); + } + let mut guard = ServiceSpawnGuard::new(pid, new_pt_phys); + + let loaded = load_elf_into(new_pt_phys, &data)?; + + let stack_size = (loaded.stack_top - loaded.stack_bottom) as usize; + let kernel_stack_size = stack_size + .checked_add(4095) + .map(|v| v & !4095usize) + .ok_or(Kernel::Memory(Memory::OutOfMemory))?; + let kernel_stack_addr = crate::task::allocate_kernel_stack(kernel_stack_size) + .ok_or(Kernel::Memory(Memory::OutOfMemory))?; + guard.set_kernel_stack(kernel_stack_addr); + + let mut thread = Thread::new_usermode( + pid, + name, + loaded.entry, + loaded.stack_top, + kernel_stack_addr, + kernel_stack_size, + ); + + // Build initial user stack: argc/argv/envp/auxv and strings + // We'll place strings at lower addresses and pointers/auxv above them. + let mut sp = loaded.stack_top; + + // argv[0] = path + let argv0 = path.as_bytes(); + // store argv0 string + sp = sp.saturating_sub((argv0.len() + 1) as u64); + if let Err(err) = write_user_bytes_in_table(new_pt_phys, sp, argv0) { + let _ = remove_process(pid); + let _ = paging::destroy_user_page_table(new_pt_phys); + return Err(err); + } + if let Err(err) = write_user_bytes_in_table(new_pt_phys, sp + argv0.len() as u64, &[0]) { + let _ = remove_process(pid); + let _ = paging::destroy_user_page_table(new_pt_phys); + return Err(err); + } + let argv0_addr = sp; + + // Align stack to 16 bytes + sp &= !0xF; + + // auxv entries: (key, val) pairs + const AT_NULL: u64 = 0; + const AT_PHDR: u64 = 3; + const AT_PHENT: u64 = 4; + const AT_PHNUM: u64 = 5; + const AT_PAGESZ: u64 = 6; + const AT_ENTRY: u64 = 9; + + // Fetch ELF header again to compute phdr addr and counts + let header = parse_header(&data)?; + let load_bias = loaded.load_bias; + let at_phdr = load_bias.wrapping_add(header.e_phoff); + let at_phent = header.e_phentsize as u64; + let at_phnum = header.e_phnum as u64; + + // push auxv (key,val) ... AT_NULL + let mut push_u64 = |val: u64| -> Result<()> { + let new_sp = sp + .checked_sub(8) + .ok_or(Kernel::Memory(Memory::InvalidAddress))?; + if new_sp < loaded.stack_bottom { + return Err(Kernel::Memory(Memory::InvalidAddress)); + } + sp = new_sp; + write_user_u64_in_table(new_pt_phys, sp, val) + }; + + // AT_NULL + push_u64(0)?; + push_u64(AT_NULL)?; + + // AT_ENTRY + push_u64(loaded.entry)?; + push_u64(AT_ENTRY)?; + + // AT_PAGESZ + push_u64(4096)?; + push_u64(AT_PAGESZ)?; + + // AT_PHNUM + push_u64(at_phnum)?; + push_u64(AT_PHNUM)?; + + // AT_PHENT + push_u64(at_phent)?; + push_u64(AT_PHENT)?; + + // AT_PHDR + push_u64(at_phdr)?; + push_u64(AT_PHDR)?; + + // envp NULL terminator (no env) + push_u64(0)?; + + // argv pointers (argv[0], NULL) + push_u64(0)?; // argv NULL + push_u64(argv0_addr)?; + + // argc + push_u64(1)?; + + // final alignment: ensure %16 == 0 + sp &= !0xF; + + thread.context_mut().rsp = sp; + thread.context_mut().rbp = 0; + + if add_thread(thread).is_none() { + return Err(Kernel::Process(Process::MaxProcessesReached)); + } + + guard.disarm(); + Ok(()) +} + +fn parse_header(data: &[u8]) -> Result { + if data.len() < core::mem::size_of::() { + return Err(Kernel::InvalidParam); + } + let ptr = data.as_ptr() as *const Elf64Header; + Ok(unsafe { *ptr }) +} + +fn validate_header(header: Elf64Header) -> Result<()> { + if header.e_ident[0..4] != ELF_MAGIC { + return Err(Kernel::InvalidParam); + } + if header.e_ident[4] != 2 || header.e_ident[5] != 1 { + return Err(Kernel::InvalidParam); + } + if header.e_machine != EM_X86_64 { + return Err(Kernel::InvalidParam); + } + if header.e_phentsize as usize != core::mem::size_of::() { + return Err(Kernel::InvalidParam); + } + Ok(()) +} + +#[repr(C)] +#[derive(Clone, Copy)] +struct Elf64Dyn { + d_tag: i64, + d_val: u64, +} + +#[repr(C)] +#[derive(Clone, Copy)] +struct Elf64Rela { + r_offset: u64, + r_info: u64, + r_addend: i64, +} + +fn apply_relocations_to( + table_phys: u64, + data: &[u8], + header: Elf64Header, + load_bias: u64, +) -> Result<()> { + let mut rela_addr = None; + let mut rela_size = None; + let mut rela_ent = None; + let mut load_ranges: alloc::vec::Vec<(u64, u64)> = alloc::vec::Vec::new(); + + let phoff = header.e_phoff as usize; + let phentsize = header.e_phentsize as usize; + let phnum = header.e_phnum as usize; + for i in 0..phnum { + let off = match i.checked_mul(phentsize).and_then(|x| phoff.checked_add(x)) { + Some(o) => o, + None => return Err(Kernel::InvalidParam), + }; + let phdr = read_phdr(data, off)?; + if phdr.p_type != PT_LOAD || phdr.p_memsz == 0 { + continue; + } + let start = phdr.p_vaddr.wrapping_add(load_bias); + let end = match start.checked_add(phdr.p_memsz) { + Some(v) => v, + None => return Err(Kernel::InvalidParam), + }; + load_ranges.push((start, end)); + } + + if let Some((dyn_off, dyn_size)) = dynamic_file_range(data, header)? { + let count = dyn_size / core::mem::size_of::(); + for i in 0..count { + let off = dyn_off + i * core::mem::size_of::(); + let dyn_ent = read_dyn(data, off)?; + match dyn_ent.d_tag { + DT_NULL => break, + DT_RELA => rela_addr = Some(dyn_ent.d_val), + DT_RELASZ => rela_size = Some(dyn_ent.d_val as usize), + DT_RELAENT => rela_ent = Some(dyn_ent.d_val as usize), + _ => {} + } + } + } + + let rela_addr = match rela_addr { + Some(v) => v, + None => return Ok(()), + }; + let rela_size = match rela_size { + Some(v) => v, + None => return Ok(()), + }; + let rela_ent = rela_ent.unwrap_or(core::mem::size_of::()); + if rela_ent < core::mem::size_of::() || rela_size % rela_ent != 0 { + return Err(Kernel::InvalidParam); + } + + let rela_off = vaddr_to_offset(data, header, rela_addr)?; + let count = rela_size / rela_ent; + for i in 0..count { + let off = rela_off + i * rela_ent; + let rela = read_rela(data, off)?; + let r_type = (rela.r_info & 0xffffffff) as u32; + if r_type == R_X86_64_RELATIVE { + let reloc_vaddr = load_bias.wrapping_add(rela.r_offset); + let reloc_end = match reloc_vaddr.checked_add(core::mem::size_of::() as u64) { + Some(v) => v, + None => return Err(Kernel::InvalidParam), + }; + if !load_ranges + .iter() + .any(|(start, end)| reloc_vaddr >= *start && reloc_end <= *end) + { + return Err(Kernel::InvalidParam); + } + let value = load_bias.wrapping_add(rela.r_addend as u64); + write_user_u64_in_table(table_phys, reloc_vaddr, value)?; + } + } + + Ok(()) +} + +fn dynamic_file_range(data: &[u8], header: Elf64Header) -> Result> { + let phoff = header.e_phoff as usize; + let phentsize = header.e_phentsize as usize; + let phnum = header.e_phnum as usize; + + for i in 0..phnum { + let off = match i.checked_mul(phentsize).and_then(|x| phoff.checked_add(x)) { + Some(o) => o, + None => return Err(Kernel::InvalidParam), + }; + let phdr = read_phdr(data, off)?; + if phdr.p_type == PT_DYNAMIC { + return Ok(Some((phdr.p_offset as usize, phdr.p_filesz as usize))); + } + } + + Ok(None) +} + +fn vaddr_to_offset(data: &[u8], header: Elf64Header, vaddr: u64) -> Result { + let phoff = header.e_phoff as usize; + let phentsize = header.e_phentsize as usize; + let phnum = header.e_phnum as usize; + + for i in 0..phnum { + let off = match i.checked_mul(phentsize).and_then(|x| phoff.checked_add(x)) { + Some(o) => o, + None => return Err(Kernel::InvalidParam), + }; + let phdr = read_phdr(data, off)?; + if phdr.p_type != PT_LOAD { + continue; + } + let start = phdr.p_vaddr; + let end = match phdr.p_vaddr.checked_add(phdr.p_memsz) { + Some(v) => v, + None => return Err(Kernel::InvalidParam), + }; + if vaddr >= start && vaddr < end { + let delta = vaddr - start; + let file_off = phdr.p_offset + delta; + if file_off as usize >= data.len() { + return Err(Kernel::InvalidParam); + } + return Ok(file_off as usize); + } + } + + Err(Kernel::InvalidParam) +} + +fn read_dyn(data: &[u8], offset: usize) -> Result { + if offset + core::mem::size_of::() > data.len() { + return Err(Kernel::InvalidParam); + } + let ptr = unsafe { data.as_ptr().add(offset) as *const Elf64Dyn }; + Ok(unsafe { *ptr }) +} + +fn read_rela(data: &[u8], offset: usize) -> Result { + if offset + core::mem::size_of::() > data.len() { + return Err(Kernel::InvalidParam); + } + let ptr = unsafe { data.as_ptr().add(offset) as *const Elf64Rela }; + Ok(unsafe { *ptr }) +} + +fn read_phdr(data: &[u8], offset: usize) -> Result { + if offset + core::mem::size_of::() > data.len() { + return Err(Kernel::InvalidParam); + } + let ptr = unsafe { data.as_ptr().add(offset) as *const Elf64Phdr }; + Ok(unsafe { *ptr }) +} diff --git a/src/core/task/fd_table.rs b/src/core/task/fd_table.rs new file mode 100644 index 0000000..9be7400 --- /dev/null +++ b/src/core/task/fd_table.rs @@ -0,0 +1,269 @@ +//! プロセスごとのファイルディスクリプタテーブル + +use alloc::boxed::Box; +use alloc::string::String; +use alloc::sync::Arc; +use core::sync::atomic::{AtomicUsize, Ordering}; + +/// stdin / stdout / stderr の予約 FD 番号 +pub const FD_BASE: usize = 3; + +/// プロセスあたりの最大 FD 数 +pub const PROCESS_MAX_FDS: usize = 256; + +/// FD フラグ: exec 時にクローズする +pub const FD_CLOEXEC: u8 = 0x01; + +/// open() フラグ: O_CLOEXEC (Linux: 0o2000000 = 0x80000) +pub const O_CLOEXEC: u64 = 0x80000; + +/// オープンファイルの状態を保持するハンドル +pub struct FileHandle { + /// ファイル内容(initfs からロード済み、パイプの場合は空) + pub data: Box<[u8]>, + /// 現在の読み取り/書き込み位置(パイプの場合はエントリインデックス兼用) + pub pos: usize, + /// Some(path) であればディレクトリ fd + pub dir_path: Option, + /// true の場合、データはリモート FD バックエンドで管理される(fd_remote 値を参照) + pub is_remote: bool, + /// リモートバックエンド側のファイルディスクリプタ(is_remote=true のとき有効) + pub fd_remote: u64, + /// is_remote=true の場合の参照カウント(close時の二重クローズ防止) + pub remote_refs: Option>, + /// Some(id) であればパイプ fd(グローバル PIPE_TABLE のインデックス) + pub pipe_id: Option, + /// パイプの書き込み端の場合 true + pub pipe_write: bool, + /// open()/openat() のファイル状態フラグ(F_GETFL/F_SETFL 用) + pub open_flags: u64, +} + +impl FileHandle { + pub fn new_pipe_read(pipe_id: usize) -> Self { + Self { + data: Box::new([]), + pos: 0, + dir_path: None, + is_remote: false, + fd_remote: 0, + remote_refs: None, + pipe_id: Some(pipe_id), + pipe_write: false, + open_flags: 0, + } + } + + pub fn new_pipe_write(pipe_id: usize) -> Self { + Self { + data: Box::new([]), + pos: 0, + dir_path: None, + is_remote: false, + fd_remote: 0, + remote_refs: None, + pipe_id: Some(pipe_id), + pipe_write: true, + open_flags: 1, + } + } + + #[inline] + pub fn clone_remote_refs(&self) -> Option> { + if !self.is_remote { + return None; + } + self.remote_refs.as_ref().map(|refs| { + refs.fetch_add(1, Ordering::AcqRel); + refs.clone() + }) + } +} + +impl Drop for FileHandle { + fn drop(&mut self) { + if !self.is_remote { + return; + } + if let Some(refs) = self.remote_refs.as_ref() { + if refs.fetch_sub(1, Ordering::AcqRel) == 1 { + crate::syscall::fs::close_remote_fd_from_kernel(self.fd_remote); + } + } else { + crate::syscall::fs::close_remote_fd_from_kernel(self.fd_remote); + } + } +} + +/// プロセスごとのファイルディスクリプタテーブル +/// +/// エントリは `Box` の生ポインタ(0 = 未使用)。 +/// サイズが大きいため必ず `Box` として使用すること。 +pub struct FdTable { + /// FD ごとの FileHandle 生ポインタ (0 = 空き) + pub(crate) entries: [u64; PROCESS_MAX_FDS], + /// FD ごとのフラグ (FD_CLOEXEC など) + pub(crate) flags: [u8; PROCESS_MAX_FDS], +} + +impl FdTable { + /// ヒープ上に FdTable をゼロ初期化して作成する。 + /// + /// `Box::new(FdTable { ... })` はスタック上への一時配置を招くため、 + /// `alloc_zeroed` で直接ヒープに確保する。 + pub fn new_boxed() -> Box { + unsafe { + let layout = core::alloc::Layout::new::(); + let ptr = alloc::alloc::alloc_zeroed(layout) as *mut Self; + Box::from_raw(ptr) + } + } + + /// 新しい FileHandle を割り当て、使用した FD 番号 (>= FD_BASE) を返す。 + /// + /// 空きスロットがない場合は `None`。 + pub fn alloc(&mut self, handle: Box, cloexec: bool) -> Option { + let ptr = Box::into_raw(handle) as u64; + for i in FD_BASE..PROCESS_MAX_FDS { + if self.entries[i] == 0 { + self.entries[i] = ptr; + self.flags[i] = if cloexec { FD_CLOEXEC } else { 0 }; + return Some(i); + } + } + // スロット不足: ハンドルを解放 + unsafe { + drop(Box::from_raw(ptr as *mut FileHandle)); + } + None + } + + /// FD に対応する FileHandle の生ポインタを返す(所有権は移動しない)。 + /// + /// # Safety + /// 呼び出し元はポインタが有効な間に close_fd() を呼ばないことを保証すること。 + pub fn get_raw(&self, fd: usize) -> Option<*mut FileHandle> { + if fd < FD_BASE || fd >= PROCESS_MAX_FDS { + return None; + } + let ptr = self.entries[fd]; + if ptr == 0 { + None + } else { + Some(ptr as *mut FileHandle) + } + } + + /// FD に対応する FileHandle の参照を返す。 + pub fn get(&self, fd: usize) -> Option<&FileHandle> { + self.get_raw(fd).map(|ptr| unsafe { &*ptr }) + } + + /// FD に対応する FileHandle の可変参照を返す。 + pub fn get_mut(&mut self, fd: usize) -> Option<&mut FileHandle> { + self.get_raw(fd).map(|ptr| unsafe { &mut *ptr }) + } + + /// FD の所有権を取り出す(close に相当)。 + pub fn take(&mut self, fd: usize) -> Option> { + if fd < FD_BASE || fd >= PROCESS_MAX_FDS { + return None; + } + let ptr = self.entries[fd]; + if ptr == 0 { + return None; + } + self.entries[fd] = 0; + self.flags[fd] = 0; + Some(unsafe { Box::from_raw(ptr as *mut FileHandle) }) + } + + /// FD を閉じる。閉じた場合 `true`、既に空きの場合 `false`。 + pub fn close_fd(&mut self, fd: usize) -> bool { + self.take(fd).is_some() + } + + /// FD_CLOEXEC が設定されているすべての FD を閉じる(execve 時に呼ぶ)。 + pub fn close_cloexec_fds(&mut self) { + for i in FD_BASE..PROCESS_MAX_FDS { + if self.entries[i] != 0 && (self.flags[i] & FD_CLOEXEC) != 0 { + let ptr = self.entries[i]; + self.entries[i] = 0; + self.flags[i] = 0; + unsafe { + drop(Box::from_raw(ptr as *mut FileHandle)); + } + } + } + } + + /// すべての FD を閉じる(Drop で自動的に呼ばれる)。 + pub fn close_all(&mut self) { + for i in FD_BASE..PROCESS_MAX_FDS { + if self.entries[i] != 0 { + let ptr = self.entries[i]; + self.entries[i] = 0; + unsafe { + drop(Box::from_raw(ptr as *mut FileHandle)); + } + } + } + } + + /// fork 用: 全エントリを複製して新しい FdTable を返す。 + /// + /// 親子は独立したファイル位置を持つ(簡易コピーセマンティクス)。 + pub fn clone_for_fork(&self) -> Box { + let mut new_table = FdTable::new_boxed(); + for i in FD_BASE..PROCESS_MAX_FDS { + let ptr = self.entries[i]; + if ptr == 0 { + continue; + } + let fh = unsafe { &*(ptr as *const FileHandle) }; + let new_fh = Box::new(FileHandle { + data: fh.data.clone(), + pos: fh.pos, + dir_path: fh.dir_path.clone(), + is_remote: fh.is_remote, + fd_remote: fh.fd_remote, + remote_refs: fh.clone_remote_refs(), + pipe_id: fh.pipe_id, + pipe_write: fh.pipe_write, + open_flags: fh.open_flags, + }); + new_table.entries[i] = Box::into_raw(new_fh) as u64; + new_table.flags[i] = self.flags[i]; + } + new_table + } + + /// FD のフラグを取得する。FD が未使用の場合 `None`。 + pub fn get_flags(&self, fd: usize) -> Option { + if fd < FD_BASE || fd >= PROCESS_MAX_FDS { + return None; + } + if self.entries[fd] == 0 { + return None; + } + Some(self.flags[fd]) + } + + /// FD のフラグを設定する。FD が有効な場合 `true`。 + pub fn set_flags(&mut self, fd: usize, flags: u8) -> bool { + if fd < FD_BASE || fd >= PROCESS_MAX_FDS { + return false; + } + if self.entries[fd] == 0 { + return false; + } + self.flags[fd] = flags; + true + } +} + +impl Drop for FdTable { + fn drop(&mut self) { + self.close_all(); + } +} diff --git a/src/core/task/ids.rs b/src/core/task/ids.rs new file mode 100644 index 0000000..207bb83 --- /dev/null +++ b/src/core/task/ids.rs @@ -0,0 +1,104 @@ +use core::sync::atomic::{AtomicU64, Ordering}; + +/// プロセスID生成用カウンタ +static NEXT_PROCESS_ID: AtomicU64 = AtomicU64::new(1); + +/// スレッドID生成用カウンタ +static NEXT_THREAD_ID: AtomicU64 = AtomicU64::new(1); + +/// プロセスID +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ProcessId(u64); + +impl ProcessId { + /// 新しいプロセスIDを生成 + pub fn new() -> Self { + Self(NEXT_PROCESS_ID.fetch_add(1, Ordering::Relaxed)) + } + + /// プロセスIDの値を取得 + pub fn as_u64(&self) -> u64 { + self.0 + } + + /// 数値からProcessIdを生成(外部入力をIDとして扱う用途) + pub const fn from_u64(id: u64) -> Self { + Self(id) + } +} + +impl Default for ProcessId { + fn default() -> Self { + Self::new() + } +} + +/// スレッドID +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ThreadId(u64); + +impl ThreadId { + /// 新しいスレッドIDを生成 + pub fn new() -> Self { + Self(NEXT_THREAD_ID.fetch_add(1, Ordering::Relaxed)) + } + + /// スレッドIDの値を取得 + pub fn as_u64(&self) -> u64 { + self.0 + } + + /// 数値からThreadIdを生成 + pub const fn from_u64(id: u64) -> Self { + Self(id) + } +} + +impl Default for ThreadId { + fn default() -> Self { + Self::new() + } +} + +/// スレッドの状態 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ThreadState { + /// 実行可能(スケジューラ待ち) + Ready, + /// 実行中 + Running, + /// ブロック中(I/O待ちなど) + Blocked, + /// スリープ中 + Sleeping, + /// 終了済み + Terminated, +} + +/// プロセスの状態 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ProcessState { + /// 実行中(少なくとも1つのスレッドがRunning/Ready) + Running, + /// スリープ中(すべてのスレッドがSleeping) + Sleeping, + /// ゾンビ(終了したが親に回収されていない) + Zombie, + /// 終了済み + Terminated, +} + +/// タスクが保有する権限レベル。ServiceとUserは区別のためであり、両方ともRing3で動作する。 +/// +/// - Core: カーネルモード(Ring0)で動作するタスク。システムの中核機能を担当。 +/// - Service: ユーザーモード(Ring3)で動作するが、システムサービスやドライバを担当。 +/// - User: ユーザーモード(Ring3)で動作。一般的なアプリケーションを担当。 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PrivilegeLevel { + /// コアレベルタスク(Ring0) + Core, + /// サービスレベルタスク(Ring3) + Service, + /// ユーザーレベルタスク(Ring3) + User, +} diff --git a/src/core/task/mod.rs b/src/core/task/mod.rs new file mode 100644 index 0000000..2b0b59c --- /dev/null +++ b/src/core/task/mod.rs @@ -0,0 +1,41 @@ +//! タスク管理モジュール +//! +//! マルチタスク機能を提供(プロセスとスレッドの管理) + +pub mod context; +mod elf; +pub mod fd_table; +pub mod ids; +pub mod process; +pub mod scheduler; +pub mod signal; +pub mod thread; +pub mod usermode; + +pub use context::{switch_context, switch_to_thread, Context}; +pub use fd_table::{FdTable, FileHandle, FD_BASE, PROCESS_MAX_FDS}; +pub use ids::{PrivilegeLevel, ProcessId, ProcessState, ThreadId, ThreadState}; +pub use process::{ + add_process, find_process_id_by_name, for_each_process, has_child_process, mark_process_exited, + process_count, reap_zombie_child_process, remove_process, with_process, with_process_mut, + Process, ProcessTable, +}; +pub use scheduler::{ + block_current_thread, disable_scheduler, enable_scheduler, exit_current_task, init_scheduler, + is_scheduler_enabled, schedule, schedule_and_switch, scheduler_tick, set_time_slice, + sleep_thread, sleep_thread_unless_woken, start_scheduling, terminate_thread, wake_thread, + yield_now, Scheduler, +}; +pub use signal::{ + default_action, sigreturn_stub_addr, DefaultAction, SigAction, SignalState, SA_RESTORER, + SIGCHLD, SIGINT, SIGKILL, SIGNAL_FRAME_MAGIC, SIGTERM, SIG_DFL, SIG_IGN, + USER_SIGRETURN_STUB_OFFSET, +}; +pub use thread::{ + add_thread, allocate_kernel_stack, count_threads_by_state, current_thread_id, for_each_thread, + free_kernel_stack, peek_next_thread, remove_thread, set_current_thread, thread_count, + thread_id_exists, thread_slot_index, thread_slot_index_and_generation, + thread_slot_index_and_generation_by_u64, thread_slot_index_by_u64, thread_to_process_id, + with_thread, with_thread_mut, Thread, ThreadQueue, +}; +pub use usermode::{jump_to_usermode, jump_to_usermode_fork_child}; diff --git a/src/core/task/process.rs b/src/core/task/process.rs new file mode 100644 index 0000000..d8474db --- /dev/null +++ b/src/core/task/process.rs @@ -0,0 +1,598 @@ +use crate::interrupt::spinlock::SpinLock; +use alloc::format; +use alloc::string::String; +use alloc::string::ToString; + +use super::fd_table::FdTable; +use super::ids::{PrivilegeLevel, ProcessId, ProcessState}; +use super::signal::SignalState; + +/// プロセス構造体 +/// +/// メモリ空間とリソースを管理する実行単位。 +/// 1つ以上のスレッドを持つ。 +pub struct Process { + /// プロセスID + id: ProcessId, + /// プロセス名 (固定長バッファ) + name: [u8; 32], + /// 有効な名前の長さ + name_len: usize, + /// プロセスの状態 + state: ProcessState, + /// 権限レベル + privilege: PrivilegeLevel, + /// 親プロセスID(存在する場合) + parent_id: Option, + /// ページテーブルのアドレス(メモリ空間)。Noneの場合はカーネル空間を共有。 + page_table: Option, + /// ヒープ開始アドレス + heap_start: u64, + /// 現在のヒープ終了アドレス (program break) + heap_end: u64, + /// ユーザースタックの現在の最低マップアドレス(下向きに伸びる) + stack_bottom: u64, + /// ユーザースタックのトップアドレス(初期 RSP 付近) + stack_top: u64, + /// カレントワーキングディレクトリ(固定バッファ、ヒープ確保不要) + cwd: [u8; 256], + cwd_len: usize, + /// 優先度(0が最高、値が大きいほど低い) + priority: u8, + /// 終了コード(生存中はNone) + exit_code: Option, + /// プロセスグループID(0 = 自身の PID と同じ) + pgid: u64, + /// セッションID(0 = 自身の PID と同じ) + sid: u64, + /// シグナル状態(ハンドラ・マスク・pending)— ヒープに置いてスタック消費を抑える + signal_state: alloc::boxed::Box, + /// プロセスごとのファイルディスクリプタテーブル — ヒープに置いてスタック消費を抑える + fd_table: alloc::boxed::Box, +} + +impl Process { + /// 新しいプロセスを作成 + /// + /// # Arguments + /// * `name` - プロセス名 + /// * `privilege` - 権限レベル + /// * `parent_id` - 親プロセスID + /// * `priority` - プロセスの優先度 + pub fn new( + name: &str, + privilege: PrivilegeLevel, + parent_id: Option, + priority: u8, + ) -> Self { + let mut name_buf = [0u8; 32]; + let bytes = name.as_bytes(); + let len = core::cmp::min(bytes.len(), 32); + name_buf[..len].copy_from_slice(&bytes[..len]); + + // デフォルトのヒープ領域(仮)。exec時に再設定されるべき。 + // 0x40000000番地あたりを開始にする例が多いが、ここでは0にしておく。 + let heap_start = 0; + + Self { + id: ProcessId::new(), + name: name_buf, + name_len: len, + state: ProcessState::Running, + privilege, + parent_id, + page_table: None, // TODO: ページテーブル実装後に設定 + heap_start, + heap_end: heap_start, + stack_bottom: 0, + stack_top: 0, + cwd: { + let mut b = [0u8; 256]; + b[0] = b'/'; + b + }, + cwd_len: 1, + priority, + exit_code: None, + pgid: 0, + sid: 0, + signal_state: alloc::boxed::Box::new(SignalState::new()), + fd_table: FdTable::new_boxed(), + } + } + + /// プロセスIDを取得 + pub fn id(&self) -> ProcessId { + self.id + } + + /// プロセス名を取得 + pub fn name(&self) -> &str { + core::str::from_utf8(&self.name[..self.name_len]).unwrap_or("???") + } + + /// プロセスの状態を取得 + pub fn state(&self) -> ProcessState { + self.state + } + + /// プロセスの状態を設定 + pub fn set_state(&mut self, state: ProcessState) { + self.state = state; + } + + /// 権限レベルを取得 + pub fn privilege(&self) -> PrivilegeLevel { + self.privilege + } + + /// 親プロセスIDを取得 + pub fn parent_id(&self) -> Option { + self.parent_id + } + + /// 優先度を取得 + pub fn priority(&self) -> u8 { + self.priority + } + + /// 終了コードを取得 + pub fn exit_code(&self) -> Option { + self.exit_code + } + + /// 終了状態へ遷移 + pub fn mark_exited(&mut self, exit_code: u64) { + self.state = ProcessState::Zombie; + self.exit_code = Some(exit_code); + } + + /// ページテーブルアドレスを取得 + pub fn page_table(&self) -> Option { + self.page_table + } + + /// ページテーブルアドレスを設定 + pub fn set_page_table(&mut self, page_table: u64) { + self.page_table = Some(page_table); + } + + /// ヒープ終了アドレスを取得 + pub fn heap_end(&self) -> u64 { + self.heap_end + } + + /// ヒープ終了アドレスを設定 + pub fn set_heap_end(&mut self, addr: u64) { + self.heap_end = addr; + } + + /// ヒープ開始アドレスを取得 + pub fn heap_start(&self) -> u64 { + self.heap_start + } + + /// ヒープ開始アドレスを設定 + pub fn set_heap_start(&mut self, addr: u64) { + self.heap_start = addr; + } + + pub fn stack_bottom(&self) -> u64 { + self.stack_bottom + } + pub fn stack_top(&self) -> u64 { + self.stack_top + } + pub fn set_stack_bottom(&mut self, addr: u64) { + self.stack_bottom = addr; + } + pub fn set_stack_top(&mut self, addr: u64) { + self.stack_top = addr; + } + + pub fn cwd(&self) -> &str { + core::str::from_utf8(&self.cwd[..self.cwd_len]).unwrap_or("/") + } + + pub fn set_cwd(&mut self, path: &str) { + let bytes = path.as_bytes(); + let len = bytes.len().min(255); + self.cwd[..len].copy_from_slice(&bytes[..len]); + self.cwd_len = len; + } + + /// シグナル状態への読み取りアクセス + pub fn signal_state(&self) -> &SignalState { + &self.signal_state + } + + /// シグナル状態への可変アクセス + pub fn signal_state_mut(&mut self) -> &mut SignalState { + &mut self.signal_state + } + + /// FD テーブルへの読み取りアクセス + pub fn fd_table(&self) -> &FdTable { + &self.fd_table + } + + /// FD テーブルへの可変アクセス + pub fn fd_table_mut(&mut self) -> &mut FdTable { + &mut self.fd_table + } + + /// fork 用: FD テーブルをクローンして新しい Box を返す + pub fn clone_fd_table_for_fork(&self) -> alloc::boxed::Box { + self.fd_table.clone_for_fork() + } + + /// FD テーブルを差し替える(fork の子プロセス初期化で使用) + pub fn set_fd_table(&mut self, table: alloc::boxed::Box) { + self.fd_table = table; + } + + /// プロセスグループ ID を取得(0 は自身の PID を意味する) + pub fn pgid(&self) -> u64 { + if self.pgid == 0 { + self.id.as_u64() + } else { + self.pgid + } + } + + /// プロセスグループ ID を設定 + pub fn set_pgid(&mut self, pgid: u64) { + self.pgid = pgid; + } + + /// セッション ID を取得(0 は自身の PID を意味する) + pub fn sid(&self) -> u64 { + if self.sid == 0 { + self.id.as_u64() + } else { + self.sid + } + } + + /// セッション ID を設定 + pub fn set_sid(&mut self, sid: u64) { + self.sid = sid; + } +} + +impl core::fmt::Debug for Process { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut debug_struct = f.debug_struct("Process"); + debug_struct + .field("id", &self.id) + .field("name", &self.name()) + .field("state", &self.state) + .field("privilege", &self.privilege) + .field("parent_id", &self.parent_id) + .field("priority", &self.priority) + .field("exit_code", &self.exit_code); + + if let Some(pt) = self.page_table { + debug_struct.field("page_table", &format_args!("{:#x}", pt)); + } else { + debug_struct.field("page_table", &None::); + } + + debug_struct.finish() + } +} + +/// プロセステーブル +/// +/// システム内のすべてのプロセスを管理する +pub struct ProcessTable { + /// プロセスの配列(最大容量) + processes: [Option; Self::MAX_PROCESSES], + /// 現在のプロセス数 + count: usize, +} + +impl ProcessTable { + /// プロセステーブルの最大容量 + pub const MAX_PROCESSES: usize = 64; + + /// 新しいプロセステーブルを作成 + pub const fn new() -> Self { + const INIT: Option = None; + Self { + processes: [INIT; Self::MAX_PROCESSES], + count: 0, + } + } + + /// プロセスを追加 + /// + /// # Returns + /// 成功時はプロセスIDを返す。テーブルが満杯の場合はNone + pub fn add(&mut self, process: Process) -> Option { + if self.count >= Self::MAX_PROCESSES { + return None; + } + + let id = process.id(); + + // 空きスロットを探す + for slot in &mut self.processes { + if slot.is_none() { + *slot = Some(process); + self.count += 1; + return Some(id); + } + } + + None + } + + /// プロセスIDでプロセスを取得 + pub fn get(&self, id: ProcessId) -> Option<&Process> { + self.processes + .iter() + .find_map(|slot| slot.as_ref().filter(|p| p.id() == id)) + } + + /// プロセスIDでプロセスの可変参照を取得 + pub fn get_mut(&mut self, id: ProcessId) -> Option<&mut Process> { + self.processes + .iter_mut() + .find_map(|slot| slot.as_mut().filter(|p| p.id() == id)) + } + + /// プロセスを削除 + /// + /// # Returns + /// 削除されたプロセスを返す。存在しない場合はNone + pub fn remove(&mut self, id: ProcessId) -> Option { + for slot in &mut self.processes { + if let Some(ref process) = slot { + if process.id() == id { + self.count -= 1; + return slot.take(); + } + } + } + None + } + + /// すべてのプロセスを反復処理 + pub fn iter(&self) -> impl Iterator { + self.processes.iter().filter_map(|slot| slot.as_ref()) + } + + /// すべてのプロセスを可変反復処理 + pub fn iter_mut(&mut self) -> impl Iterator { + self.processes.iter_mut().filter_map(|slot| slot.as_mut()) + } + + /// 名前でプロセスを検索 + pub fn find_by_name(&self, name: &str) -> Option<&Process> { + // 名前比較: まず完全一致を試し、それでも見つからない場合はいくつかの互換候補を試す。 + // 目的: バイナリ名 (net.elf -> net) とクレート名 (netdrv) の食い違いに耐性を持たせる。 + // マッチ順序: + // 1. 完全一致 + // 2. stored_name without ".elf" == name + // 3. stored_name == name + ".elf" + // 4. stored_name == "/bin/drivers/" + name + ".elf" + // 5. stored_name contains name as substring (fallback) + + // 1) 完全一致 + if let Some(p) = self + .processes + .iter() + .filter_map(|s| s.as_ref()) + .find(|p| p.name() == name) + { + return Some(p); + } + + // 2) stored without .elf + if let Some(p) = self.processes.iter().filter_map(|s| s.as_ref()).find(|p| { + p.name() + .strip_suffix(".elf") + .map(|s| s == name) + .unwrap_or(false) + }) { + return Some(p); + } + + // 3) stored == name + .elf + let mut name_elf = String::new(); + name_elf.push_str(name); + name_elf.push_str(".elf"); + if let Some(p) = self + .processes + .iter() + .filter_map(|s| s.as_ref()) + .find(|p| p.name() == name_elf) + { + return Some(p); + } + + // 4) drivers path variant + let mut drivers_path = String::from("/bin/drivers/"); + drivers_path.push_str(name); + drivers_path.push_str(".elf"); + if let Some(p) = self + .processes + .iter() + .filter_map(|s| s.as_ref()) + .find(|p| p.name() == drivers_path) + { + return Some(p); + } + + // 5) Try drv -> base mapping (e.g., netdrv -> net) + if name.ends_with("drv") && name.len() > 3 { + let base = &name[..name.len() - 3]; + // try base, base.elf, drivers path + if let Some(p) = self + .processes + .iter() + .filter_map(|s| s.as_ref()) + .find(|p| p.name() == base || p.name() == format!("{}.elf", base)) + { + return Some(p); + } + let mut drivers_path = String::from("/bin/drivers/"); + drivers_path.push_str(base); + drivers_path.push_str(".elf"); + if let Some(p) = self + .processes + .iter() + .filter_map(|s| s.as_ref()) + .find(|p| p.name() == drivers_path) + { + return Some(p); + } + } + + // 6) fallback: substring match + self.processes + .iter() + .filter_map(|s| s.as_ref()) + .find(|p| p.name().contains(name)) + } + + fn is_child_match(process: &Process, parent: ProcessId, target: Option) -> bool { + if process.parent_id() != Some(parent) { + return false; + } + if let Some(target_id) = target { + process.id() == target_id + } else { + true + } + } + + /// 対象に一致する子プロセスが存在するかを返す + pub fn has_child(&self, parent: ProcessId, target: Option) -> bool { + self.processes + .iter() + .filter_map(|slot| slot.as_ref()) + .any(|p| Self::is_child_match(p, parent, target)) + } + + /// ゾンビ子プロセスを1つ回収する + pub fn reap_zombie_child( + &mut self, + parent: ProcessId, + target: Option, + ) -> Option<(ProcessId, u64, Option)> { + for slot in &mut self.processes { + let should_reap = slot.as_ref().is_some_and(|proc| { + Self::is_child_match(proc, parent, target) && proc.state() == ProcessState::Zombie + }); + if !should_reap { + continue; + } + + if let Some(proc) = slot.take() { + let pid = proc.id(); + let exit_code = proc.exit_code().unwrap_or(0); + let page_table = proc.page_table(); + self.count = self.count.saturating_sub(1); + return Some((pid, exit_code, page_table)); + } + } + None + } + + /// 現在のプロセス数を取得 + pub fn count(&self) -> usize { + self.count + } +} + +impl Default for ProcessTable { + fn default() -> Self { + Self::new() + } +} + +/// グローバルプロセステーブル +static PROCESS_TABLE: SpinLock = SpinLock::new(ProcessTable::new()); + +/// プロセステーブルにプロセスを追加 +pub fn add_process(process: Process) -> Option { + PROCESS_TABLE.lock().add(process) +} + +/// プロセスを削除 +pub fn remove_process(id: ProcessId) -> Option { + PROCESS_TABLE.lock().remove(id) +} + +/// プロセスIDでプロセス情報を取得(読み取り専用操作) +pub fn with_process(id: ProcessId, f: F) -> Option +where + F: FnOnce(&Process) -> R, +{ + let table = PROCESS_TABLE.lock(); + table.get(id).map(f) +} + +/// プロセスIDでプロセス情報を可変操作 +pub fn with_process_mut(id: ProcessId, f: F) -> Option +where + F: FnOnce(&mut Process) -> R, +{ + let mut table = PROCESS_TABLE.lock(); + table.get_mut(id).map(f) +} + +/// 名前からプロセスIDを検索 +pub fn find_process_id_by_name(name: &str) -> Option { + let table = PROCESS_TABLE.lock(); + table.find_by_name(name).map(|p| p.id()) +} + +/// すべてのプロセスに対して処理を実行 +pub fn for_each_process(mut f: F) +where + F: FnMut(&Process), +{ + let table = PROCESS_TABLE.lock(); + for process in table.iter() { + f(process); + } +} + +/// プロセスを終了状態(Zombie)へ遷移させる +pub fn mark_process_exited(id: ProcessId, exit_code: u64) { + let mut table = PROCESS_TABLE.lock(); + if let Some(proc) = table.get_mut(id) { + proc.mark_exited(exit_code); + } +} + +/// 一致する子プロセスが存在するか確認する +pub fn has_child_process(parent: ProcessId, target: Option) -> bool { + PROCESS_TABLE.lock().has_child(parent, target) +} + +/// 一致するゾンビ子プロセスを回収する +pub fn reap_zombie_child_process( + parent: ProcessId, + target: Option, +) -> Option<(ProcessId, u64)> { + let (pid, exit_code, page_table) = PROCESS_TABLE.lock().reap_zombie_child(parent, target)?; + if let Some(table_phys) = page_table { + if let Err(e) = crate::mem::paging::destroy_user_page_table(table_phys) { + crate::warn!( + "Failed to destroy child page table while reaping pid={:?}: {:?}", + pid, + e + ); + } + } + Some((pid, exit_code)) +} + +/// 現在のプロセス数を取得 +pub fn process_count() -> usize { + PROCESS_TABLE.lock().count() +} diff --git a/src/core/task/scheduler.rs b/src/core/task/scheduler.rs new file mode 100644 index 0000000..f397cdb --- /dev/null +++ b/src/core/task/scheduler.rs @@ -0,0 +1,452 @@ +use crate::interrupt::spinlock::SpinLock; + +use super::context::switch_to_thread; +use super::ids::{ThreadId, ThreadState}; +use super::thread::{ + current_thread_id, remove_thread, set_current_thread, with_thread, with_thread_mut, + THREAD_QUEUE, +}; + +/// スケジューラ +/// +/// スレッドのスケジューリングを管理 +pub struct Scheduler { + /// スケジューラが有効かどうか + enabled: bool, + /// タイムスライス(タイマー割り込み回数) + time_slice: u64, + /// 現在のタイムスライスカウンタ + current_slice: u64, +} + +impl Scheduler { + /// デフォルトのタイムスライス(10ms × 2 = 20ms) + pub const DEFAULT_TIME_SLICE: u64 = 2; + + /// 新しいスケジューラを作成 + pub const fn new() -> Self { + Self { + enabled: false, + time_slice: Self::DEFAULT_TIME_SLICE, + current_slice: 0, + } + } + + /// スケジューラを有効化 + pub fn enable(&mut self) { + self.enabled = true; + } + + /// スケジューラを無効化 + pub fn disable(&mut self) { + self.enabled = false; + } + + /// スケジューラが有効かどうか + pub fn is_enabled(&self) -> bool { + self.enabled + } + + /// タイムスライスを設定 + pub fn set_time_slice(&mut self, slice: u64) { + self.time_slice = slice; + } + + /// タイマー割り込み時に呼ばれる + /// + /// タイムスライスをカウントし、期限が来たらスケジューリングを実行 + pub fn tick(&mut self) -> bool { + if !self.enabled { + return false; + } + + self.current_slice += 1; + if self.current_slice >= self.time_slice { + self.current_slice = 0; + true // スケジューリングが必要 + } else { + false + } + } + + /// タイムスライスをリセット + pub fn reset_slice(&mut self) { + self.current_slice = 0; + } +} + +impl Default for Scheduler { + fn default() -> Self { + Self::new() + } +} + +/// グローバルスケジューラ +static SCHEDULER: SpinLock = SpinLock::new(Scheduler::new()); + +/// スケジューラを初期化 +pub fn init_scheduler() { + let mut scheduler = SCHEDULER.lock(); + scheduler.enable(); +} + +/// スケジューラを有効化 +pub fn enable_scheduler() { + SCHEDULER.lock().enable(); +} + +/// タイムスライスを設定 +pub fn set_time_slice(slice: u64) { + SCHEDULER.lock().set_time_slice(slice); +} + +/// スケジューラを無効化 +pub fn disable_scheduler() { + SCHEDULER.lock().disable(); +} + +/// スケジューラが有効かどうか +pub fn is_scheduler_enabled() -> bool { + SCHEDULER.lock().is_enabled() +} + +/// タイマー割り込み時に呼ばれる(タイマー割り込みハンドラから呼び出す) +/// +/// # Returns +/// スケジューリングが必要な場合はtrue +pub fn scheduler_tick() -> bool { + if let Some(tid) = current_thread_id() { + if with_thread(tid, |t| t.in_syscall()).unwrap_or(false) { + return false; + } + } + SCHEDULER.lock().tick() +} + +/// 次に実行すべきスレッドを選択 +/// +/// ラウンドロビンスケジューリング:Ready状態のスレッドを順に選択 +/// +/// # Returns +/// 次に実行すべきスレッドID。実行可能なスレッドがない場合はNone +pub fn schedule() -> Option { + let mut queue = THREAD_QUEUE.lock(); + + // 現在のスレッドを取得 + let current = current_thread_id(); + + // 現在のスレッドがあれば、状態をReadyに戻す(Running -> Ready) + if let Some(current_id) = current { + if let Some(thread) = queue.get_mut(current_id) { + if thread.state() == ThreadState::Running { + thread.set_state(ThreadState::Ready); + } + } + } + + // 現在のスレッドの次のReady状態のスレッドを探す + if let Some(next_thread) = queue.peek_next_after(current) { + let next_id = next_thread.id(); + next_thread.set_state(ThreadState::Running); + + // スケジューラのタイムスライスをリセット + drop(queue); + SCHEDULER.lock().reset_slice(); + + Some(next_id) + } else { + None + } +} + +/// 現在のスレッドを明示的にCPUを手放す(yield) +/// +/// スケジューラを呼び出して次のスレッドに切り替える +pub fn yield_now() { + if !is_scheduler_enabled() { + return; + } + + crate::debug!("yield_now() called"); + + // スケジューリングと切り替えは割り込み禁止区間で実行し、 + // 状態更新と実際の切替の間に割り込みが入る競合窓を防ぐ。 + x86_64::instructions::interrupts::without_interrupts(|| { + if let Some(next_id) = schedule() { + let current = current_thread_id(); + + crate::debug!("yield_now: current={:?}, next={:?}", current, next_id); + + // 次のスレッドが現在のスレッドと異なる場合のみ切り替え + if Some(next_id) != current { + crate::debug!("Calling switch_to_thread..."); + + // コンテキストスイッチを実行 + unsafe { + switch_to_thread(current, next_id); + } + + crate::debug!("Returned from switch_to_thread"); + } + } + }); +} + +/// スレッドをブロック状態にする +/// +/// 現在のスレッドをBlocked状態にして、次のスレッドにスケジューリング +pub fn block_current_thread() { + if let Some(current_id) = current_thread_id() { + with_thread_mut(current_id, |thread| { + thread.set_state(ThreadState::Blocked); + }); + + // 次のスレッドにスケジューリング + yield_now(); + } +} + +/// スレッドをスリープ状態にする +/// +/// 指定されたスレッドをSleeping状態にする +pub fn sleep_thread(id: ThreadId) { + with_thread_mut(id, |thread| { + thread.set_state(ThreadState::Sleeping); + }); +} + +/// スレッドを起床させる +/// +/// Sleeping/Blocked状態のスレッドをReady状態にする。 +/// Ready状態の場合は pending_wakeup フラグを立てて競合を防ぐ。 +pub fn wake_thread(id: ThreadId) { + with_thread_mut(id, |thread| { + let state = thread.state(); + if state == ThreadState::Sleeping || state == ThreadState::Blocked { + thread.set_state(ThreadState::Ready); + } else if state == ThreadState::Ready { + // まだ眠っていない場合、起床要求を記録しておく + thread.set_pending_wakeup(); + } + }); +} + +/// 現在のスレッドをスリープ状態にする。 +/// +/// pending_wakeup フラグが立っていれば眠らずに即座に返す(競合回避)。 +/// # Returns +/// `true` なら実際に Sleeping 状態に遷移した。`false` なら眠らなかった。 +pub fn sleep_thread_unless_woken(id: ThreadId) -> bool { + with_thread_mut(id, |thread| { + if thread.take_pending_wakeup() { + // 先に wake が呼ばれていたので眠らない + false + } else { + thread.set_state(ThreadState::Sleeping); + true + } + }) + .unwrap_or(false) +} + +/// 子プロセス終了時に親プロセスの先頭スレッドの IPC waiter を起床させる。 +/// IPC recv_blocking でスリープしている親スレッドを叩き起こし、child exit を検知させる。 +fn wake_parent_ipc_waiter(exited_pid: crate::task::ProcessId) { + use crate::task::with_process; + let parent_pid = match with_process(exited_pid, |p| p.parent_id()) { + Some(Some(pid)) => pid, + _ => return, + }; + + // 親プロセスの最初のスレッドを探し、IPC mailbox に積まれた waiter を起床させる + let mut parent_tid: Option = None; + crate::task::for_each_thread(|thread| { + if parent_tid.is_none() && thread.process_id() == parent_pid { + parent_tid = Some(thread.id()); + } + }); + + if let Some(tid) = parent_tid { + // ゼロ長メッセージを mailbox に積んで recv_blocking が確実に戻れるようにする。 + // wake_thread だけでは「スリープ中に Ready に変えて pending_wakeup なし」の場合、 + // recv_blocking が yield 後に再スリープしてしまうため、必ずメッセージを使う。 + crate::syscall::ipc::send_from_kernel(tid.as_u64(), &[]); + } +} + +/// スレッドを終了させる +/// +/// 指定されたスレッドをTerminated状態にして削除 +pub fn terminate_thread(id: ThreadId) { + with_thread_mut(id, |thread| { + thread.set_state(ThreadState::Terminated); + }); + + // 現在のスレッドの場合は次のスレッドにスケジューリング + if Some(id) == current_thread_id() { + set_current_thread(None); + yield_now(); + } + + crate::syscall::process::clear_futex_waiter(id); + // スレッドをキューから削除し、カーネルスタックを解放 + if let Some(thread) = remove_thread(id) { + crate::task::free_kernel_stack(thread.kernel_stack_base()); + } +} + +/// 現在のタスクを終了させる(exitシステムコール用) +/// +/// 現在のスレッドをTerminated状態にして削除し、次のスレッドにスケジューリング +pub fn exit_current_task(exit_code: u64) -> ! { + if let Some(current_id) = current_thread_id() { + crate::debug!("Exiting thread {:?} with code {}", current_id, exit_code); + let current_pid = with_thread(current_id, |thread| thread.process_id()); + + with_thread_mut(current_id, |thread| { + thread.set_state(ThreadState::Terminated); + }); + + if let Some(pid) = current_pid { + let mut has_other_live_threads = false; + crate::task::for_each_thread(|thread| { + if thread.process_id() == pid + && thread.id() != current_id + && thread.state() != ThreadState::Terminated + { + has_other_live_threads = true; + } + }); + if !has_other_live_threads { + crate::task::mark_process_exited(pid, exit_code); + // 親プロセスが IPC でブロックしている可能性があるので起床させる + wake_parent_ipc_waiter(pid); + // 親プロセスへ SIGCHLD を送達する + crate::syscall::signal::deliver_sigchld_to_parent(pid); + } + } + + // 現在のスレッドをクリア(先にクリアしないとschedule()が正しく動作しない) + set_current_thread(None); + + x86_64::instructions::interrupts::without_interrupts(|| { + // 次のスレッドにスケジューリング(戻ってこない) + if let Some(next_id) = schedule() { + crate::debug!("Switching from exited thread to {:?}", next_id); + + // スレッドをキューから削除(コンテキストスイッチ前に削除) + crate::syscall::process::clear_futex_waiter(current_id); + let kstack_base = with_thread(current_id, |t| t.kernel_stack_base()).unwrap_or(0); + remove_thread(current_id); + + // カーネルスタックをフリーリストへ返却(スイッチ直前、まだスタックは有効) + crate::task::free_kernel_stack(kstack_base); + + // コンテキストスイッチを実行(終了したスレッドのコンテキストは保存しない) + // old_context_ptr = None を渡すことで、現在のコンテキストを保存せずに次のスレッドにジャンプ + unsafe { + switch_to_thread(None, next_id); + } + + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "scheduler switch_to_thread returned unexpectedly after exit", + ); + x86_64::instructions::interrupts::disable(); + loop { + x86_64::instructions::hlt(); + } + } + }); + + // スレッドをキューから削除 + crate::syscall::process::clear_futex_waiter(current_id); + if let Some(thread) = remove_thread(current_id) { + crate::task::free_kernel_stack(thread.kernel_stack_base()); + } + } + + // スレッドがない場合は永久にhaltして待機 + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "scheduler observed no more user threads", + ); + x86_64::instructions::interrupts::disable(); + loop { + x86_64::instructions::hlt(); + } +} + +/// スケジューリングしてコンテキストスイッチを実行 +/// +/// タイマー割り込みハンドラから呼び出される +pub fn schedule_and_switch() { + if !is_scheduler_enabled() { + return; + } + + x86_64::instructions::interrupts::without_interrupts(|| { + let current = current_thread_id(); + + // 次のスレッドを選択 + if let Some(next_id) = schedule() { + // 次のスレッドが現在のスレッドと異なる場合のみ切り替え + if Some(next_id) != current { + // コンテキストスイッチを実行 + unsafe { + switch_to_thread(current, next_id); + } + } + } + }); +} + +/// 最初のスレッドを起動 +/// +/// スケジューラを開始して最初のスレッドにジャンプ +pub fn start_scheduling() -> ! { + // 最初のスレッドを選択 + if let Some(first_id) = super::thread::peek_next_thread() { + x86_64::instructions::interrupts::without_interrupts(|| { + with_thread_mut(first_id, |thread| { + crate::info!( + "Starting first thread: {} (id={:?})", + thread.name(), + thread.id() + ); + thread.set_state(ThreadState::Running); + }); + + // 最初のスレッドへ switch_to_thread でジャンプ(戻ってこない) + // user/kernel どちらも switch_context 経由で正しく動作する + unsafe { + switch_to_thread(None, first_id); + } + }); + + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "start_scheduling switch_to_thread returned unexpectedly", + ); + x86_64::instructions::interrupts::disable(); + loop { + x86_64::instructions::hlt(); + } + } else { + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "start_scheduling found no threads to schedule", + ); + x86_64::instructions::interrupts::disable(); + loop { + x86_64::instructions::hlt(); + } + } +} + +/// プロセス終了用のエイリアス(ページフォルトハンドラなどから呼び出される) +/// +/// 現在のプロセス/スレッドを終了させる +pub fn exit_current_process(exit_code: i32) -> ! { + exit_current_task(exit_code as u64) +} diff --git a/src/core/task/signal.rs b/src/core/task/signal.rs new file mode 100644 index 0000000..f5386ef --- /dev/null +++ b/src/core/task/signal.rs @@ -0,0 +1,140 @@ +//! プロセスごとのシグナル状態 + +/// シグナルハンドラのデフォルト動作(SIG_DFL) +pub const SIG_DFL: u64 = 0; +/// シグナルを無視する(SIG_IGN) +pub const SIG_IGN: u64 = 1; + +// ----- シグナル番号定数 (Linux x86-64 互換) ----- +pub const SIGHUP: usize = 1; +pub const SIGINT: usize = 2; +pub const SIGQUIT: usize = 3; +pub const SIGKILL: usize = 9; +pub const SIGTERM: usize = 15; +pub const SIGCHLD: usize = 17; +pub const SIGCONT: usize = 18; +pub const SIGSTOP: usize = 19; +pub const SIGTSTP: usize = 20; +pub const SIGTTIN: usize = 21; +pub const SIGTTOU: usize = 22; +pub const SIGWINCH: usize = 28; + +// ----- SA_* フラグ ----- +pub const SA_RESTORER: u64 = 0x04000000; +/// カーネルが各プロセスへ固定配置する sigreturn スタブのオフセット。 +pub const USER_SIGRETURN_STUB_OFFSET: u64 = 0x2000; +/// シグナルフレーム整合性検証用のマジック値。 +pub const SIGNAL_FRAME_MAGIC: u64 = 0x6d6f_6368_695f_7367; + +/// シグナルのデフォルト動作 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DefaultAction { + /// プロセスを終了する + Terminate, + /// シグナルを無視する + Ignore, +} + +/// シグナル番号に対応するデフォルト動作を返す +pub fn default_action(sig: usize) -> DefaultAction { + match sig { + // 無視するシグナル + SIGCHLD | SIGCONT | SIGWINCH => DefaultAction::Ignore, + SIGSTOP | SIGTSTP | SIGTTIN | SIGTTOU => DefaultAction::Ignore, + // それ以外はすべてプロセス終了 + _ => DefaultAction::Terminate, + } +} + +/// 1つのシグナルに対するアクション(Linux の struct sigaction と互換) +#[derive(Clone, Copy)] +pub struct SigAction { + /// ハンドラ: SIG_DFL=0, SIG_IGN=1, それ以外はユーザー空間の関数ポインタ + pub handler: u64, + /// SA_* フラグ + pub flags: u64, + /// Linux 互換レイアウト維持のため残すが、カーネルは信用しない。 + pub restorer: u64, + /// ハンドラ実行中にブロックするシグナルマスク(ビット i = シグナル i+1) + pub mask: u64, +} + +impl SigAction { + pub const fn default_action() -> Self { + Self { + handler: SIG_DFL, + flags: 0, + restorer: 0, + mask: 0, + } + } + + pub fn is_default(&self) -> bool { + self.handler == SIG_DFL + } + pub fn is_ignored(&self) -> bool { + self.handler == SIG_IGN + } + pub fn has_user_handler(&self) -> bool { + self.handler > SIG_IGN + } +} + +#[inline] +pub fn sigreturn_stub_addr(stack_top: u64) -> Option { + stack_top.checked_add(USER_SIGRETURN_STUB_OFFSET) +} + +/// プロセスのシグナル状態 +pub struct SignalState { + /// シグナルごとのアクション(インデックス 0 = SIGHUP, ... インデックス 63 = シグナル64) + pub actions: [SigAction; 64], + /// ブロック中のシグナルマスク(ビット i = シグナル i+1 がブロック中) + pub mask: u64, + /// 保留(pending)シグナルビットマップ + pub pending: u64, +} + +impl SignalState { + pub const fn new() -> Self { + Self { + actions: [SigAction::default_action(); 64], + mask: 0, + pending: 0, + } + } + + /// シグナルを pending にセットする + pub fn set_pending(&mut self, sig: usize) { + if sig >= 1 && sig <= 64 { + self.pending |= 1u64 << (sig - 1); + } + } + + /// ブロックされていない pending シグナルを1つ取り出す(ビットをクリアして番号を返す) + pub fn take_next_deliverable(&mut self) -> Option { + let deliverable = self.pending & !self.mask; + if deliverable == 0 { + return None; + } + let bit = deliverable.trailing_zeros() as usize; + self.pending &= !(1u64 << bit); + Some(bit + 1) + } + + /// 指定シグナルのアクションを取得 + pub fn action(&self, sig: usize) -> SigAction { + if sig >= 1 && sig <= 64 { + self.actions[sig - 1] + } else { + SigAction::default_action() + } + } + + /// 指定シグナルのアクションをセット + pub fn set_action(&mut self, sig: usize, action: SigAction) { + if sig >= 1 && sig <= 64 { + self.actions[sig - 1] = action; + } + } +} diff --git a/src/core/task/thread.rs b/src/core/task/thread.rs new file mode 100644 index 0000000..e63e50c --- /dev/null +++ b/src/core/task/thread.rs @@ -0,0 +1,995 @@ +use crate::interrupt::spinlock::SpinLock; +use x86_64::VirtAddr; + +use super::context::Context; +use super::ids::{ProcessId, ThreadId, ThreadState}; + +/// スレッド終了時に呼ばれるハンドラ +/// この関数から戻ることはない +extern "C" fn thread_exit_handler() -> ! { + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "thread returned into thread_exit_handler", + ); + crate::task::exit_current_task(0) +} + +/// スレッド構造体 +/// +/// プロセス内で実行される軽量な実行単位。 +/// 同じプロセス内のスレッドはメモリ空間を共有する。 +pub struct Thread { + /// スレッドID + id: ThreadId, + /// 所属するプロセスID + process_id: ProcessId, + /// スレッド名 (固定長バッファ) + name: [u8; 32], + /// 有効な名前の長さ + name_len: usize, + /// 現在の状態 + state: ThreadState, + /// CPUコンテキスト + context: Context, + /// カーネルスタックの開始アドレス + kernel_stack: u64, + /// カーネルスタックのサイズ + kernel_stack_size: usize, + /// ユーザーモードエントリポイント(0の場合はカーネルモードスレッド) + user_entry: u64, + /// ユーザースタックトップ(0の場合はカーネルモードスレッド) + user_stack: u64, + /// fork時に子プロセスへ渡すユーザー RFLAGS + fork_user_rflags: u64, + /// TLS用 FS ベースレジスタ (arch_prctl ARCH_SET_FS で設定) + fs_base: u64, + /// 現在システムコールコンテキスト中かどうか + in_syscall: bool, + /// KPTI 復帰用のユーザーCR3 + syscall_user_cr3: u64, + /// 直近の SYSCALL 入口で保存したユーザー RIP + syscall_user_rip: u64, + /// 直近の SYSCALL 入口で保存したユーザー RSP + syscall_user_rsp: u64, + /// 直近の SYSCALL 入口で保存したユーザー RFLAGS + syscall_user_rflags: u64, + /// futex wait timeout で起床したことを示すフラグ + futex_timed_out: bool, + /// IPC受信などで眠る前に起床要求が来たことを示すフラグ + pending_wakeup: bool, +} + +// Simple kernel stack pool for creating kernel stacks for threads +const KSTACK_POOL_SIZE: usize = 4096 * 512; // 2 MiB(128KB/スレッドで約15スレッド分、フリーリストで再利用) +const KSTACK_PAGE_BYTES: usize = 4096; +const KSTACK_GUARD_BYTES: usize = KSTACK_PAGE_BYTES; + +/// 解放済みカーネルスタックのフリーリスト +/// 各エントリは guard_addr(= スタックベース - KSTACK_GUARD_BYTES)を格納。0 = 空き +const KSTACK_FREE_LIST_CAP: usize = 32; +static KSTACK_FREE_LIST: SpinLock<[u64; KSTACK_FREE_LIST_CAP]> = + SpinLock::new([0u64; KSTACK_FREE_LIST_CAP]); + +#[repr(align(4096))] +struct KernelStackPool([u8; KSTACK_POOL_SIZE]); + +static KSTACK_POOL: SpinLock = + SpinLock::new(KernelStackPool([0; KSTACK_POOL_SIZE])); +static NEXT_KSTACK_OFFSET: core::sync::atomic::AtomicUsize = + core::sync::atomic::AtomicUsize::new(0); + +fn unmap_guard_page(guard_addr: u64) -> bool { + use x86_64::structures::paging::mapper::Translate; + use x86_64::structures::paging::mapper::TranslateError; + use x86_64::structures::paging::{Mapper, Page, Size4KiB}; + + let page = Page::::containing_address(VirtAddr::new(guard_addr)); + let mut page_table_lock = crate::mem::paging::PAGE_TABLE.lock(); + let page_table = match page_table_lock.as_mut() { + Some(pt) => pt, + None => { + crate::debug!( + "unmap_guard_page: PAGE_TABLE not initialized at {:#x}", + guard_addr + ); + return false; + } + }; + + match page_table.translate_page(page) { + Ok(_) => { + crate::debug!( + "unmap_guard_page: page mapped at {:#x}, attempting unmap", + guard_addr + ); + } + Err(TranslateError::PageNotMapped) => { + crate::debug!( + "unmap_guard_page: page not mapped at {:#x}, returning true", + guard_addr + ); + return true; + } + Err(TranslateError::ParentEntryHugePage) => { + // Huge page region - can't unmap individual 4KB pages within huge pages + // This is expected for bootloader-mapped regions. Skip guard page unmapping. + crate::debug!( + "unmap_guard_page: page in huge page region at {:#x}, skipping unmap", + guard_addr + ); + return true; + } + Err(e) => { + crate::debug!( + "unmap_guard_page: translate error at {:#x}: {:?}", + guard_addr, + e + ); + return false; + } + } + + unsafe { + match page_table.unmap(page) { + Ok((_frame, flush)) => { + flush.flush(); + crate::debug!("unmap_guard_page: successfully unmapped {:#x}", guard_addr); + true + } + Err(e) => { + crate::debug!( + "unmap_guard_page: unmap error at {:#x}: {:?}", + guard_addr, + e + ); + false + } + } + } +} + +/// カーネルスタックを内部プールから割り当てます。 +/// フリーリストに空きがあれば再利用し、なければバンプアロケータから新規割り当て。 +/// Returns base address (bottom) of stack. +pub fn allocate_kernel_stack(size: usize) -> Option { + if size == 0 || size > KSTACK_POOL_SIZE.saturating_sub(KSTACK_GUARD_BYTES) { + return None; + } + let size_pages = size + .checked_add(KSTACK_PAGE_BYTES - 1)? + .checked_div(KSTACK_PAGE_BYTES)? + .checked_mul(KSTACK_PAGE_BYTES)?; + + // フリーリストから再利用を試みる(guard ページは既に unmap 済み) + { + let mut list = KSTACK_FREE_LIST.lock(); + for slot in list.iter_mut() { + if *slot != 0 { + let guard_addr = *slot; + *slot = 0; + crate::debug!( + "allocate_kernel_stack: reused from free list at {:#x}", + guard_addr + ); + return guard_addr.checked_add(KSTACK_GUARD_BYTES as u64); + } + } + } + + // バンプアロケータから新規割り当て + let alloc_size = size_pages.checked_add(KSTACK_GUARD_BYTES)?; + let off = NEXT_KSTACK_OFFSET.fetch_add(alloc_size, core::sync::atomic::Ordering::SeqCst); + if off + alloc_size > KSTACK_POOL_SIZE { + crate::debug!( + "allocate_kernel_stack: pool exhausted, need {}, have {}", + off + alloc_size, + KSTACK_POOL_SIZE + ); + return None; + } + + let pool_base = { + let pool = KSTACK_POOL.lock(); + pool.0.as_ptr() as u64 + }; + let guard_addr = pool_base.checked_add(off as u64)?; + crate::debug!( + "allocate_kernel_stack: allocated from pool at {:#x}, calling unmap_guard_page", + guard_addr + ); + if !unmap_guard_page(guard_addr) { + crate::debug!( + "allocate_kernel_stack: unmap_guard_page failed at {:#x}", + guard_addr + ); + return None; + } + + crate::debug!( + "allocate_kernel_stack: returning stack top {:#x}", + guard_addr + KSTACK_GUARD_BYTES as u64 + ); + guard_addr.checked_add(KSTACK_GUARD_BYTES as u64) +} + +/// カーネルスタックをフリーリストへ返却する。 +/// `base` は `allocate_kernel_stack` が返したアドレス(ガードページの直上)。 +pub fn free_kernel_stack(base: u64) { + if base == 0 { + return; + } + let guard_addr = match base.checked_sub(KSTACK_GUARD_BYTES as u64) { + Some(a) => a, + None => return, + }; + // プール範囲内のアドレスのみ受け付ける + let pool_base = { + let pool = KSTACK_POOL.lock(); + pool.0.as_ptr() as u64 + }; + if guard_addr < pool_base || guard_addr >= pool_base + KSTACK_POOL_SIZE as u64 { + return; + } + let mut list = KSTACK_FREE_LIST.lock(); + for slot in list.iter_mut() { + if *slot == 0 { + *slot = guard_addr; + return; + } + } + // フリーリストが満杯の場合はリークさせる(通常は発生しない) +} + +impl Thread { + /// 新しいスレッドを作成 + /// + /// # Arguments + /// * `process_id` - 所属するプロセスID + /// * `name` - スレッド名 + /// * `entry_point` - スレッドのエントリーポイント関数 + /// * `kernel_stack` - カーネルスタックのアドレス + /// * `kernel_stack_size` - カーネルスタックのサイズ + pub fn new( + process_id: ProcessId, + name: &str, + entry_point: fn() -> !, + kernel_stack: u64, + kernel_stack_size: usize, + ) -> Self { + let mut name_buf = [0u8; 32]; + let bytes = name.as_bytes(); + let len = core::cmp::min(bytes.len(), 32); + name_buf[..len].copy_from_slice(&bytes[..len]); + + let mut context = Context::new(); + + // スタックポインタをスタックの最後に設定(スタックは下に伸びる) + // 16バイト境界に合わせる + let stack_top = (kernel_stack + kernel_stack_size as u64) & !0xF; + + // 呼び出し規約に合わせて、戻り先アドレス用のスロットを確保 + let stack_ptr = stack_top - 8; + + unsafe { + // 戻り先として thread_exit_handler を配置 + let ret_addr = stack_ptr as *mut u64; + *ret_addr = thread_exit_handler as *const () as u64; + } + + // rsp は「戻り先アドレスが置かれている位置」を指す + context.rsp = stack_ptr; + context.rbp = stack_top; + + // エントリーポイントをripに設定 + context.rip = entry_point as usize as u64; + + // RFLAGSの初期値(割り込み有効) + context.rflags = 0x202; // IF (Interrupt Flag) = 1 + + crate::debug!( + "Creating thread '{}': stack={:#x}, size={:#x}, rsp={:#x}, rip={:#x}", + name, + kernel_stack, + kernel_stack_size, + context.rsp, + context.rip + ); + + Self { + id: ThreadId::new(), + process_id, + name: name_buf, + name_len: len, + state: ThreadState::Ready, + context, + kernel_stack, + kernel_stack_size, + user_entry: 0, + user_stack: 0, + fork_user_rflags: 0, + fs_base: 0, + in_syscall: false, + syscall_user_cr3: 0, + syscall_user_rip: 0, + syscall_user_rsp: 0, + syscall_user_rflags: 0, + futex_timed_out: false, + pending_wakeup: false, + } + } + + /// 新しいユーザーモードスレッドを作成 + /// + /// # Arguments + /// * `process_id` - 所属するプロセスID + /// * `name` - スレッド名 + /// * `user_entry` - ユーザーモードのエントリーポイント + /// * `user_stack` - ユーザースタックのトップアドレス + /// * `kernel_stack` - カーネルスタックのアドレス + /// * `kernel_stack_size` - カーネルスタックのサイズ + pub fn new_usermode( + process_id: ProcessId, + name: &str, + user_entry: u64, + user_stack: u64, + kernel_stack: u64, + kernel_stack_size: usize, + ) -> Self { + let mut name_buf = [0u8; 32]; + let bytes = name.as_bytes(); + let len = core::cmp::min(bytes.len(), 32); + name_buf[..len].copy_from_slice(&bytes[..len]); + + // カーネルスタックを設定(ユーザーモードからシステムコール時に使用) + let mut context = Context::new(); + let stack_top = (kernel_stack + kernel_stack_size as u64) & !0xF; + + // ユーザーモードへジャンプするトランポリン関数を設定 + extern "C" fn usermode_entry_trampoline() -> ! { + // この関数は各スレッドが最初に実行される + // スレッド固有のuser_entryとuser_stackを取得してジャンプする + let tid = match current_thread_id() { + Some(t) => t, + None => { + crate::warn!("usermode_entry_trampoline: No current thread"); + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "usermode_entry_trampoline without current thread", + ); + crate::task::exit_current_task(crate::syscall::EINVAL); + } + }; + let (entry, stack) = + match with_thread(tid, |thread| (thread.user_entry(), thread.user_stack())) { + Some(v) => v, + None => { + crate::warn!("usermode_entry_trampoline: Thread not found"); + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "usermode_entry_trampoline missing thread metadata", + ); + crate::task::exit_current_task(crate::syscall::EINVAL); + } + }; + + crate::debug!( + "Jumping to usermode: entry={:#x}, stack={:#x}", + entry, + stack + ); + unsafe { + crate::task::jump_to_usermode(entry, stack); + } + } + + let stack_ptr = stack_top - 8; + unsafe { + let ret_addr = stack_ptr as *mut u64; + *ret_addr = thread_exit_handler as *const () as u64; + } + + context.rsp = stack_ptr; + context.rbp = stack_top; + context.rip = usermode_entry_trampoline as *const () as u64; + context.rflags = 0x202; + + crate::debug!( + "Creating usermode thread '{}': user_entry={:#x}, user_stack={:#x}", + name, + user_entry, + user_stack + ); + + Self { + id: ThreadId::new(), + process_id, + name: name_buf, + name_len: len, + state: ThreadState::Ready, + context, + kernel_stack, + kernel_stack_size, + user_entry, + user_stack, + fork_user_rflags: 0, + fs_base: 0, + in_syscall: false, + syscall_user_cr3: 0, + syscall_user_rip: 0, + syscall_user_rsp: 0, + syscall_user_rflags: 0, + futex_timed_out: false, + pending_wakeup: false, + } + } + + /// ユーザーモードエントリポイントを取得 + pub fn user_entry(&self) -> u64 { + self.user_entry + } + + /// ユーザースタックを取得 + pub fn user_stack(&self) -> u64 { + self.user_stack + } + + /// TLS FSベースを取得 + pub fn fs_base(&self) -> u64 { + self.fs_base + } + + /// fork_user_rflags を取得 + pub fn fork_user_rflags(&self) -> u64 { + self.fork_user_rflags + } + + /// fork の子プロセス用スレッドを作成 + /// + /// 子スレッドはユーザー空間で fork() の戻り値として 0 を返す + pub fn new_fork_child( + process_id: ProcessId, + user_rip: u64, + user_rsp: u64, + user_rflags: u64, + fs_base: u64, + kernel_stack: u64, + kernel_stack_size: usize, + ) -> Self { + let mut context = Context::new(); + let stack_top = (kernel_stack + kernel_stack_size as u64) & !0xF; + let stack_ptr = stack_top - 8; + unsafe { + let ret_addr = stack_ptr as *mut u64; + *ret_addr = thread_exit_handler as *const () as u64; + } + context.rsp = stack_ptr; + context.rbp = stack_top; + + extern "C" fn fork_child_trampoline() -> ! { + let tid = match current_thread_id() { + Some(t) => t, + None => { + crate::warn!("fork_child_trampoline: No current thread"); + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "fork_child_trampoline without current thread", + ); + crate::task::exit_current_task(crate::syscall::EINVAL); + } + }; + let (entry, stack, rflags, fs) = match with_thread(tid, |thread| { + ( + thread.user_entry(), + thread.user_stack(), + thread.fork_user_rflags(), + thread.fs_base(), + ) + }) { + Some(v) => v, + None => { + crate::warn!("fork_child_trampoline: Thread not found"); + crate::audit::log( + crate::audit::AuditEventKind::Fault, + "fork_child_trampoline missing thread metadata", + ); + crate::task::exit_current_task(crate::syscall::EINVAL); + } + }; + unsafe { + crate::task::usermode::jump_to_usermode_fork_child(entry, stack, rflags, fs); + } + } + + context.rip = fork_child_trampoline as *const () as u64; + context.rflags = 0x202; + + Self { + id: ThreadId::new(), + process_id, + name: [ + b'f', b'o', b'r', b'k', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + ], + name_len: 4, + state: ThreadState::Ready, + context, + kernel_stack, + kernel_stack_size, + user_entry: user_rip, + user_stack: user_rsp, + fork_user_rflags: user_rflags, + fs_base, + in_syscall: false, + syscall_user_cr3: 0, + syscall_user_rip: user_rip, + syscall_user_rsp: user_rsp, + syscall_user_rflags: user_rflags, + futex_timed_out: false, + pending_wakeup: false, + } + } + + /// TLS FSベースを設定 + pub fn set_fs_base(&mut self, base: u64) { + self.fs_base = base; + } + + /// システムコールコンテキスト中かどうか + pub fn in_syscall(&self) -> bool { + self.in_syscall + } + + pub fn set_in_syscall(&mut self, in_syscall: bool) { + self.in_syscall = in_syscall; + } + + pub fn syscall_user_cr3(&self) -> u64 { + self.syscall_user_cr3 + } + + pub fn set_syscall_user_cr3(&mut self, cr3: u64) { + self.syscall_user_cr3 = cr3; + } + + pub fn syscall_user_context(&self) -> (u64, u64, u64) { + ( + self.syscall_user_rip, + self.syscall_user_rsp, + self.syscall_user_rflags, + ) + } + + pub fn set_syscall_user_context(&mut self, rip: u64, rsp: u64, rflags: u64) { + self.syscall_user_rip = rip; + self.syscall_user_rsp = rsp; + self.syscall_user_rflags = rflags; + } + + pub fn set_futex_timed_out(&mut self, timed_out: bool) { + self.futex_timed_out = timed_out; + } + + pub fn take_futex_timed_out(&mut self) -> bool { + let timed_out = self.futex_timed_out; + self.futex_timed_out = false; + timed_out + } + + /// 起床要求フラグを立てる(眠る前に wake が呼ばれた場合の競合回避) + pub fn set_pending_wakeup(&mut self) { + self.pending_wakeup = true; + } + + /// 起床要求フラグを取り出して消去する。true なら眠る必要はない。 + pub fn take_pending_wakeup(&mut self) -> bool { + let v = self.pending_wakeup; + self.pending_wakeup = false; + v + } + + /// スレッドIDを取得 + pub fn id(&self) -> ThreadId { + self.id + } + + /// 所属するプロセスIDを取得 + pub fn process_id(&self) -> ProcessId { + self.process_id + } + + /// スレッド名を取得 + pub fn name(&self) -> &str { + core::str::from_utf8(&self.name[..self.name_len]).unwrap_or("???") + } + + /// スレッドの状態を取得 + pub fn state(&self) -> ThreadState { + self.state + } + + /// スレッドの状態を設定 + pub fn set_state(&mut self, state: ThreadState) { + self.state = state; + } + + /// コンテキストへの可変参照を取得 + pub fn context_mut(&mut self) -> &mut Context { + &mut self.context + } + + /// コンテキストへの参照を取得 + pub fn context(&self) -> &Context { + &self.context + } + + pub fn kernel_stack_top(&self) -> u64 { + (self.kernel_stack + self.kernel_stack_size as u64) & !0xF + } + + pub fn kernel_stack_bottom(&self) -> u64 { + self.kernel_stack + } + + pub fn is_kernel_stack_guard_intact(&self) -> bool { + use x86_64::structures::paging::mapper::TranslateError; + use x86_64::structures::paging::Mapper; + use x86_64::structures::paging::{Page, Size4KiB}; + + let (pool_start, pool_end) = { + let pool = KSTACK_POOL.lock(); + let start = pool.0.as_ptr() as u64; + (start, start + KSTACK_POOL_SIZE as u64) + }; + let stack_end = match self.kernel_stack.checked_add(self.kernel_stack_size as u64) { + Some(v) => v, + None => return false, + }; + let pooled_stack = self.kernel_stack >= pool_start + KSTACK_GUARD_BYTES as u64 + && stack_end <= pool_end + && self.kernel_stack >= KSTACK_GUARD_BYTES as u64; + if !pooled_stack { + return true; + } + let guard_start = self.kernel_stack - KSTACK_GUARD_BYTES as u64; + let guard_page = Page::::containing_address(VirtAddr::new(guard_start)); + let mut page_table_lock = crate::mem::paging::PAGE_TABLE.lock(); + let Some(page_table) = page_table_lock.as_mut() else { + return true; + }; + match page_table.translate_page(guard_page) { + Ok(_) => false, + Err(TranslateError::PageNotMapped) => true, + // 現状の early kernel 領域は bootloader の huge page に載ることがある。 + // その場合は 4KiB guard を作れていないので、破損として扱わない。 + Err(TranslateError::ParentEntryHugePage) => true, + Err(_) => false, + } + } + + /// カーネルスタックのベースアドレス(フリーリスト返却用) + pub fn kernel_stack_base(&self) -> u64 { + self.kernel_stack + } +} + +impl core::fmt::Debug for Thread { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Thread") + .field("id", &self.id) + .field("process_id", &self.process_id) + .field("name", &self.name) + .field("state", &self.state) + .field("kernel_stack", &format_args!("{:#x}", self.kernel_stack)) + .field("kernel_stack_size", &self.kernel_stack_size) + .finish() + } +} + +/// スレッドキュー +/// +/// 実行可能なスレッドを管理するキュー +pub struct ThreadQueue { + /// スレッドの配列(最大容量) + threads: [Option; Self::MAX_THREADS], + /// スロット世代番号(スロット再利用時に増加) + slot_generations: [u64; Self::MAX_THREADS], + /// 現在のスレッド数 + count: usize, +} + +impl ThreadQueue { + /// スレッドキューの最大容量 + pub const MAX_THREADS: usize = 64; + + /// 新しいスレッドキューを作成 + pub const fn new() -> Self { + const INIT: Option = None; + Self { + threads: [INIT; Self::MAX_THREADS], + slot_generations: [0; Self::MAX_THREADS], + count: 0, + } + } + + /// スレッドを追加 + /// + /// # Returns + /// 成功時はスレッドIDを返す。キューが満杯の場合はNone + pub fn push(&mut self, thread: Thread) -> Option { + if self.count >= Self::MAX_THREADS { + return None; + } + + let id = thread.id(); + + // 空きスロットを探す + for (idx, slot) in self.threads.iter_mut().enumerate() { + if slot.is_none() { + *slot = Some(thread); + self.slot_generations[idx] = self.slot_generations[idx].wrapping_add(1); + if self.slot_generations[idx] == 0 { + self.slot_generations[idx] = 1; + } + self.count += 1; + return Some(id); + } + } + + None + } + + /// スレッドIDでスレッドを取得 + pub fn get(&self, id: ThreadId) -> Option<&Thread> { + self.threads + .iter() + .find_map(|slot| slot.as_ref().filter(|t| t.id() == id)) + } + + /// スレッドIDでスレッドの可変参照を取得 + pub fn get_mut(&mut self, id: ThreadId) -> Option<&mut Thread> { + self.threads + .iter_mut() + .find_map(|slot| slot.as_mut().filter(|t| t.id() == id)) + } + + /// スレッドIDが存在するスロットインデックスを返す + pub fn slot_index(&self, id: ThreadId) -> Option { + self.threads + .iter() + .position(|slot| slot.as_ref().is_some_and(|t| t.id() == id)) + } + + /// スレッドIDが存在するスロットと世代番号を返す + pub fn slot_index_and_generation(&self, id: ThreadId) -> Option<(usize, u64)> { + self.threads.iter().enumerate().find_map(|(idx, slot)| { + slot.as_ref() + .filter(|t| t.id() == id) + .map(|_| (idx, self.slot_generations[idx])) + }) + } + + /// スレッドを削除 + /// + /// # Returns + /// 削除されたスレッドを返す。存在しない場合はNone + pub fn remove(&mut self, id: ThreadId) -> Option { + for slot in &mut self.threads { + if let Some(ref thread) = slot { + if thread.id() == id { + self.count -= 1; + return slot.take(); + } + } + } + None + } + + /// 次に実行すべきスレッドを取得(削除せずに参照を返す) + /// + /// Ready状態のスレッドを優先して返す + pub fn peek_next(&self) -> Option<&Thread> { + // Ready状態のスレッドを探す + self.threads + .iter() + .filter_map(|slot| slot.as_ref()) + .find(|t| t.state() == ThreadState::Ready) + } + + /// 次に実行すべきスレッドを取得(可変参照) + pub fn peek_next_mut(&mut self) -> Option<&mut Thread> { + // Ready状態のスレッドを探す + self.threads + .iter_mut() + .filter_map(|slot| slot.as_mut()) + .find(|t| t.state() == ThreadState::Ready) + } + + /// 指定されたスレッドの次のReady状態のスレッドを取得(ラウンドロビン用) + /// + /// current_idの次のスロットから検索を開始し、見つからなければ先頭から検索 + pub fn peek_next_after(&mut self, current_id: Option) -> Option<&mut Thread> { + if let Some(current) = current_id { + // 現在のスレッドのインデックスを探す + let mut current_index = None; + for (i, slot) in self.threads.iter().enumerate() { + if let Some(thread) = slot.as_ref() { + if thread.id() == current { + current_index = Some(i); + break; + } + } + } + + if let Some(start_idx) = current_index { + for i in (start_idx + 1..Self::MAX_THREADS).chain(0..=start_idx) { + if self.threads[i] + .as_ref() + .is_some_and(|t| t.state() == ThreadState::Ready) + { + return self.threads[i].as_mut(); + } + } + } + } + + // current_idがない場合は最初のReady状態のスレッドを返す + self.peek_next_mut() + } + + /// 指定された状態のスレッド数をカウント + pub fn count_by_state(&self, state: ThreadState) -> usize { + self.threads + .iter() + .filter_map(|slot| slot.as_ref()) + .filter(|t| t.state() == state) + .count() + } + + /// 指定されたプロセスに属するスレッドを反復処理 + pub fn iter_by_process(&self, process_id: ProcessId) -> impl Iterator { + self.threads + .iter() + .filter_map(|slot| slot.as_ref()) + .filter(move |t| t.process_id() == process_id) + } + + /// すべてのスレッドを反復処理 + pub fn iter(&self) -> impl Iterator { + self.threads.iter().filter_map(|slot| slot.as_ref()) + } + + /// すべてのスレッドを可変反復処理 + pub fn iter_mut(&mut self) -> impl Iterator { + self.threads.iter_mut().filter_map(|slot| slot.as_mut()) + } + + /// 現在のスレッド数を取得 + pub fn count(&self) -> usize { + self.count + } + + /// スレッドキューが満杯かどうか + pub fn is_full(&self) -> bool { + self.count >= Self::MAX_THREADS + } + + /// スレッドキューが空かどうか + pub fn is_empty(&self) -> bool { + self.count == 0 + } +} + +impl Default for ThreadQueue { + fn default() -> Self { + Self::new() + } +} + +/// グローバルスレッドキュー +pub(super) static THREAD_QUEUE: SpinLock = SpinLock::new(ThreadQueue::new()); + +/// スレッドキューにスレッドを追加 +pub fn add_thread(thread: Thread) -> Option { + THREAD_QUEUE.lock().push(thread) +} + +/// スレッドIDでスレッド情報を取得(読み取り専用操作) +pub fn with_thread(id: ThreadId, f: F) -> Option +where + F: FnOnce(&Thread) -> R, +{ + let queue = THREAD_QUEUE.lock(); + queue.get(id).map(f) +} + +/// スレッドIDでスレッド情報を可変操作 +pub fn with_thread_mut(id: ThreadId, f: F) -> Option +where + F: FnOnce(&mut Thread) -> R, +{ + let mut queue = THREAD_QUEUE.lock(); + queue.get_mut(id).map(f) +} + +/// スレッドを削除 +pub fn remove_thread(id: ThreadId) -> Option { + THREAD_QUEUE.lock().remove(id) +} + +/// 次に実行すべきスレッドIDを取得 +pub fn peek_next_thread() -> Option { + THREAD_QUEUE.lock().peek_next().map(|t| t.id()) +} + +/// 指定された状態のスレッド数を取得 +pub fn count_threads_by_state(state: ThreadState) -> usize { + THREAD_QUEUE.lock().count_by_state(state) +} + +/// すべてのスレッドに対して操作を実行 +pub fn for_each_thread(mut f: F) +where + F: FnMut(&Thread), +{ + let queue = THREAD_QUEUE.lock(); + for thread in queue.iter() { + f(thread); + } +} + +/// 現在のスレッド数を取得 +pub fn thread_count() -> usize { + THREAD_QUEUE.lock().count() +} + +/// 指定した u64 IDのスレッドが存在するか確認 (IPC送信先検証用) +pub fn thread_id_exists(id_val: u64) -> bool { + let queue = THREAD_QUEUE.lock(); + let exists = queue.iter().any(|t| t.id().as_u64() == id_val); + exists +} + +/// 指定したスレッドIDのスロットインデックスを返す +pub fn thread_slot_index(id: ThreadId) -> Option { + THREAD_QUEUE.lock().slot_index(id) +} + +/// 指定したu64スレッドIDのスロットインデックスを返す +pub fn thread_slot_index_by_u64(id_val: u64) -> Option { + thread_slot_index(ThreadId::from_u64(id_val)) +} + +/// 指定したスレッドIDのスロットインデックスと世代番号を返す +pub fn thread_slot_index_and_generation(id: ThreadId) -> Option<(usize, u64)> { + THREAD_QUEUE.lock().slot_index_and_generation(id) +} + +/// 指定したu64スレッドIDのスロットインデックスと世代番号を返す +pub fn thread_slot_index_and_generation_by_u64(id_val: u64) -> Option<(usize, u64)> { + thread_slot_index_and_generation(ThreadId::from_u64(id_val)) +} + +/// 現在実行中のスレッドIDを取得 +pub fn current_thread_id() -> Option { + x86_64::instructions::interrupts::without_interrupts(|| { + let raw = crate::percpu::current_thread_raw_id(); + if raw == 0 { + None + } else { + Some(ThreadId::from_u64(raw)) + } + }) +} + +/// 現在実行中のスレッドIDを設定 +pub fn set_current_thread(id: Option) { + let raw = id.map(|v| v.as_u64()).unwrap_or(0); + x86_64::instructions::interrupts::without_interrupts(|| { + crate::percpu::set_current_thread_raw_id(raw); + }); +} + +/// スレッドIDからプロセスIDを取得 +pub fn thread_to_process_id(thread_id: u64) -> Option { + with_thread(ThreadId::from_u64(thread_id), |t| t.process_id()) +} diff --git a/src/core/task/usermode.rs b/src/core/task/usermode.rs new file mode 100644 index 0000000..6f9484a --- /dev/null +++ b/src/core/task/usermode.rs @@ -0,0 +1,178 @@ +//! ユーザーモード実行サポート + +use crate::mem::gdt; +use core::arch::asm; + +/// ユーザーモードでコードを実行する +/// +/// # 引数 +/// - `entry`: ユーザーモードで実行する関数のアドレス +/// - `user_stack`: ユーザースタックのトップアドレス +/// +/// # 注意 +/// この関数は戻らない +/// +/// # Safety +/// `entry` と `user_stack` はユーザー空間の有効な実行/スタックアドレスである必要がある。 +pub unsafe fn jump_to_usermode(entry: u64, user_stack: u64) -> ! { + let user_cs = gdt::user_code_selector() as u64 | 3; // RPL=3 + let user_ss = gdt::user_data_selector() as u64 | 3; // RPL=3 + let (fs_base, user_cr3) = crate::task::current_thread_id() + .and_then(|tid| { + crate::task::with_thread(tid, |thread| { + let pid = thread.process_id(); + let fs = thread.fs_base(); + let cr3 = crate::task::with_process(pid, |proc| proc.page_table().unwrap_or(0)) + .unwrap_or(0); + (fs, cr3) + }) + }) + .unwrap_or((0, 0)); + + // GDTエントリの内容を読み取って確認 + let cs_selector = gdt::user_code_selector(); + let ss_selector = gdt::user_data_selector(); + + let gdtr = read_gdtr(); + let gdt_base = gdtr.0; + + // CSのGDTエントリを読み取る + let cs_index = (cs_selector >> 3) as usize; + let cs_entry_ptr = (gdt_base + (cs_index * 8) as u64) as *const u64; + let cs_entry = core::ptr::read_volatile(cs_entry_ptr); + let cs_dpl = (cs_entry >> 45) & 0b11; + + // SSのGDTエントリを読み取る + let ss_index = (ss_selector >> 3) as usize; + let ss_entry_ptr = (gdt_base + (ss_index * 8) as u64) as *const u64; + let ss_entry = core::ptr::read_volatile(ss_entry_ptr); + let ss_dpl = (ss_entry >> 45) & 0b11; + + crate::debug!("GDT Check:"); + crate::debug!( + " CS selector={:#x}, index={}, entry={:#018x}, DPL={}", + cs_selector, + cs_index, + cs_entry, + cs_dpl + ); + crate::debug!( + " SS selector={:#x}, index={}, entry={:#018x}, DPL={}", + ss_selector, + ss_index, + ss_entry, + ss_dpl + ); + crate::debug!( + " Final CS={:#x} (with RPL=3), SS={:#x} (with RPL=3)", + user_cs, + user_ss + ); + + crate::debug!( + "Jumping to usermode: entry={:#x}, stack={:#x}, fs_base={:#x}", + entry, + user_stack, + fs_base + ); + + crate::cpu::write_fs_base(fs_base); + if user_cr3 != 0 { + crate::mem::paging::switch_page_table(user_cr3); + } + + // iretqスタックフレームを構築: + // SS, RSP, RFLAGS, CS, RIP + asm!( + "cli", + + // データセグメントをユーザーセグメントに設定(iretq前) + "mov ax, {ss:x}", + "mov ds, ax", + "mov es, ax", + + // iretq用のスタックフレームをプッシュ + "push {ss}", // SS (ユーザーデータセグメント) + "push {rsp}", // RSP (ユーザースタック) + "pushfq", // 現在のRFLAGSを保存 + "pop r11", + "or r11, 0x200", // IF (Interrupt Flag) を設定 + "push r11", // RFLAGS + "push {cs}", // CS (ユーザーコードセグメント) + "push {rip}", // RIP (エントリーポイント) + + // iretqでユーザーモードへジャンプ + "iretq", + + ss = in(reg) user_ss, + rsp = in(reg) user_stack, + cs = in(reg) user_cs, + rip = in(reg) entry, + options(noreturn) + ) +} + +/// GDTRを読み取る +fn read_gdtr() -> (u64, u16) { + let mut gdtr: [u8; 10] = [0; 10]; + unsafe { + asm!("sgdt [{}]", in(reg) gdtr.as_mut_ptr(), options(nostack)); + } + let limit = u16::from_le_bytes([gdtr[0], gdtr[1]]); + let base = u64::from_le_bytes([ + gdtr[2], gdtr[3], gdtr[4], gdtr[5], gdtr[6], gdtr[7], gdtr[8], gdtr[9], + ]); + (base, limit) +} + +/// fork の子プロセスとしてユーザーモードへジャンプする +/// +/// iretq フレームを構築し、RAX=0 (fork の子側戻り値) でユーザーに復帰する +/// +/// # Safety +/// `entry`/`stack`/`user_rflags`/`fs_base` は子プロセスの有効な復帰コンテキストである必要がある。 +pub unsafe fn jump_to_usermode_fork_child( + entry: u64, + stack: u64, + user_rflags: u64, + fs_base: u64, +) -> ! { + let user_cs = gdt::user_code_selector() as u64 | 3; + let user_ss = gdt::user_data_selector() as u64 | 3; + let user_cr3 = crate::task::current_thread_id() + .and_then(|tid| crate::task::with_thread(tid, |thread| thread.process_id())) + .and_then(|pid| crate::task::with_process(pid, |proc| proc.page_table().unwrap_or(0))) + .unwrap_or(0); + let fs_lo = fs_base as u32; + let fs_hi = (fs_base >> 32) as u32; + if user_cr3 != 0 { + crate::mem::paging::switch_page_table(user_cr3); + } + asm!( + "cli", + // FS ベースを IA32_FS_BASE MSR 経由で設定 + "mov ecx, 0xC0000100", + "wrmsr", + // データセグメントをユーザーセグメントに設定 + "mov ax, {ss:x}", + "mov ds, ax", + "mov es, ax", + // iretq フレームを構築: SS, RSP, RFLAGS, CS, RIP + "push {ss}", + "push {rsp}", + "push {rflags}", + "push {cs}", + "push {rip}", + // fork 子プロセスは rax=0 を返す + "xor eax, eax", + "iretq", + ss = in(reg) user_ss, + rsp = in(reg) stack, + rflags = in(reg) (user_rflags | 0x200), + cs = in(reg) user_cs, + rip = in(reg) entry, + in("eax") fs_lo, + in("edx") fs_hi, + options(noreturn) + ) +} diff --git a/src/util/console.rs b/src/core/util/console.rs similarity index 96% rename from src/util/console.rs rename to src/core/util/console.rs index bca1749..0420ca2 100644 --- a/src/util/console.rs +++ b/src/core/util/console.rs @@ -7,7 +7,7 @@ use spin::Mutex; use x86_64::instructions::port::Port; /// シリアルポート (COM1) - 割込み対応版 -static SERIAL: Mutex = Mutex::new(SerialPort::new(0x3F8)); +pub static SERIAL: Mutex = Mutex::new(SerialPort::new(0x3F8)); /// 割込み無効化を伴うロック取得 fn lock_serial(f: F) -> R @@ -70,7 +70,7 @@ impl SerialPort { } /// 1バイト送信 - fn send_byte(&mut self, byte: u8) { + pub fn send_byte(&mut self, byte: u8) { unsafe { // 送信準備完了を待つ while self.line_status.read() & 0x20 == 0 {} diff --git a/src/util/fifo.rs b/src/core/util/fifo.rs similarity index 89% rename from src/util/fifo.rs rename to src/core/util/fifo.rs index 388c565..1776405 100644 --- a/src/util/fifo.rs +++ b/src/core/util/fifo.rs @@ -8,13 +8,19 @@ use spin::Mutex; /// FIFOバッファ pub struct Fifo { + /// バッファ buffer: Mutex>, } +/// FIFOインナー struct FifoInner { + /// データ data: [Option; N], + /// 書き込み先 write_pos: usize, + /// 読み込み先 read_pos: usize, + /// カウント count: usize, } @@ -74,3 +80,9 @@ impl Fifo { self.buffer.lock().count } } + +impl Default for Fifo { + fn default() -> Self { + Self::new() + } +} diff --git a/src/util/log.rs b/src/core/util/log.rs similarity index 66% rename from src/util/log.rs rename to src/core/util/log.rs index 8e58643..46c06c1 100644 --- a/src/util/log.rs +++ b/src/core/util/log.rs @@ -1,17 +1,50 @@ //! ロギングユーティリティ +use core::sync::atomic::{AtomicU8, Ordering}; + /// ログレベル #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum LogLevel { + /// システムトレース Trace, + /// デバッグ情報 Debug, + /// インフォメーション Info, + /// 警告 Warn, + /// エラー Error, } +/// 現在のログレベル(デフォルト: Info) +static LOG_LEVEL: AtomicU8 = AtomicU8::new(2); + +fn level_to_u8(level: LogLevel) -> u8 { + match level { + LogLevel::Trace => 0, + LogLevel::Debug => 1, + LogLevel::Info => 2, + LogLevel::Warn => 3, + LogLevel::Error => 4, + } +} + +fn should_log(level: LogLevel) -> bool { + level_to_u8(level) >= LOG_LEVEL.load(Ordering::Relaxed) +} + +/// ログレベルを設定 +pub fn set_level(level: LogLevel) { + LOG_LEVEL.store(level_to_u8(level), Ordering::Relaxed); +} + /// ログ出力(シリアルとVGAの両方) pub fn log(level: LogLevel, args: core::fmt::Arguments) { + if !should_log(level) { + return; + } + use crate::{sprint, sprintln, vprint, vprintln}; let prefix = match level { diff --git a/src/util/mod.rs b/src/core/util/mod.rs similarity index 62% rename from src/util/mod.rs rename to src/core/util/mod.rs index e62c814..680fa04 100644 --- a/src/util/mod.rs +++ b/src/core/util/mod.rs @@ -1,4 +1,6 @@ pub mod console; pub mod fifo; pub mod log; +pub mod ps2kbd; +pub mod ps2mouse; pub mod vga; diff --git a/src/core/util/ps2kbd.rs b/src/core/util/ps2kbd.rs new file mode 100644 index 0000000..153f4f6 --- /dev/null +++ b/src/core/util/ps2kbd.rs @@ -0,0 +1,50 @@ +//! PS/2 キーボードドライバ (カーネル側) +//! +//! IRQ1 割り込みハンドラからスキャンコードを受け取りFIFOに蓄積する。 +//! 変換ロジックはユーザー空間 (shell.service) が担当する。 + +use super::fifo::Fifo; +use core::sync::atomic::{AtomicU64, Ordering}; + +/// rawスキャンコードのバッファ (割り込みハンドラ ↔ syscall) +pub static SCANCODE_BUF: Fifo = Fifo::new(); +/// ドライバ監視用のミラーキュー(通常入力を消費しない) +pub static SCANCODE_TAP_BUF: Fifo = Fifo::new(); + +/// read(0, ...) でブロッキング待機しているスレッドのID(0 = 待ちなし) +static KEYBOARD_WAITER: AtomicU64 = AtomicU64::new(0); + +/// IRQ1 ハンドラから呼ぶ: rawスキャンコードをバッファへ積み、待機スレッドを起床させる +pub fn push_scancode(scancode: u8) { + let _ = SCANCODE_BUF.push(scancode); + let _ = SCANCODE_TAP_BUF.push(scancode); + + // ブロッキング read で眠っているスレッドがいれば起床させる + let waiter = KEYBOARD_WAITER.swap(0, Ordering::AcqRel); + if waiter != 0 { + crate::task::wake_thread(crate::task::ThreadId::from_u64(waiter)); + } +} + +/// `KeyboardRead` syscall から呼ぶ: rawスキャンコードを1バイト取り出す +pub fn pop_scancode() -> Option { + SCANCODE_BUF.pop() +} + +/// ドライバ監視用ミラーキューから rawスキャンコードを1バイト取り出す +pub fn pop_tap_scancode() -> Option { + SCANCODE_TAP_BUF.pop() +} + +/// ブロッキング read 用: 現在のスレッドをwaiterとして登録する +/// +/// スキャンコードが届いたとき `push_scancode` が起床させる。 +/// 呼び出し前に waiter 登録 → pop 再試行 → 眠る、の順にすること(競合回避)。 +pub fn register_waiter(tid: u64) { + KEYBOARD_WAITER.store(tid, Ordering::Release); +} + +/// waiter 登録をキャンセルする(眠らずに済んだ場合のクリーンアップ用) +pub fn unregister_waiter(tid: u64) { + let _ = KEYBOARD_WAITER.compare_exchange(tid, 0, Ordering::AcqRel, Ordering::Relaxed); +} diff --git a/src/core/util/ps2mouse.rs b/src/core/util/ps2mouse.rs new file mode 100644 index 0000000..1dbf59d --- /dev/null +++ b/src/core/util/ps2mouse.rs @@ -0,0 +1,216 @@ +//! PS/2 マウス入力 +//! +//! IRQ12 から届くバイト列を 3 バイトのパケットへ組み立てて FIFO に積む。 + +use super::fifo::Fifo; +use core::sync::atomic::{AtomicU64, Ordering}; +use spin::Mutex; + +/// 完成済みマウスパケットのキュー(b0 | b1<<8 | b2<<16) +pub static MOUSE_PACKET_BUF: Fifo = Fifo::new(); + +/// read で待機しているスレッド(0 = 待機なし) +static MOUSE_WAITER: AtomicU64 = AtomicU64::new(0); +static MOUSE_PACKET_DROPS: AtomicU64 = AtomicU64::new(0); + +#[derive(Clone, Copy)] +struct PacketAssembler { + bytes: [u8; 3], + len: u8, +} + +static ASSEMBLER: Mutex = Mutex::new(PacketAssembler { + bytes: [0; 3], + len: 0, +}); + +const STATUS_OUTPUT_FULL: u8 = 1 << 0; +const STATUS_INPUT_FULL: u8 = 1 << 1; + +#[inline] +fn wait_input_clear(mut budget: usize) -> bool { + use x86_64::instructions::port::Port; + while budget > 0 { + let status: u8 = unsafe { Port::::new(0x64).read() }; + if (status & STATUS_INPUT_FULL) == 0 { + return true; + } + budget -= 1; + core::hint::spin_loop(); + } + false +} + +#[inline] +fn wait_output_full(mut budget: usize) -> bool { + use x86_64::instructions::port::Port; + while budget > 0 { + let status: u8 = unsafe { Port::::new(0x64).read() }; + if (status & STATUS_OUTPUT_FULL) != 0 { + return true; + } + budget -= 1; + core::hint::spin_loop(); + } + false +} + +#[inline] +fn read_data_with_timeout(budget: usize) -> Option { + use x86_64::instructions::port::Port; + if !wait_output_full(budget) { + return None; + } + Some(unsafe { Port::::new(0x60).read() }) +} + +#[inline] +fn write_controller_command(cmd: u8) -> bool { + use x86_64::instructions::port::Port; + if !wait_input_clear(100_000) { + return false; + } + unsafe { + Port::::new(0x64).write(cmd); + } + true +} + +#[inline] +fn write_controller_data(data: u8) -> bool { + use x86_64::instructions::port::Port; + if !wait_input_clear(100_000) { + return false; + } + unsafe { + Port::::new(0x60).write(data); + } + true +} + +fn flush_output_buffer() { + use x86_64::instructions::port::Port; + for _ in 0..32 { + if !wait_output_full(1000) { + break; + } + let _: u8 = unsafe { Port::::new(0x60).read() }; + } +} + +fn send_mouse_command(cmd: u8) -> bool { + if !write_controller_command(0xD4) { + return false; + } + if !write_controller_data(cmd) { + return false; + } + matches!(read_data_with_timeout(100_000), Some(0xFA)) +} + +/// PS/2 マウスを初期化する。 +/// +/// 成功時 `true`、失敗時 `false`。 +pub fn init() -> bool { + flush_output_buffer(); + + // AUX (mouse) ポート有効化 + if !write_controller_command(0xA8) { + return false; + } + + // コントローラ設定バイトを読み出し、IRQ12を有効化 + if !write_controller_command(0x20) { + return false; + } + let Some(config) = read_data_with_timeout(100_000) else { + return false; + }; + // bit1: IRQ12 enable, bit5: mouse clock disable + let updated = (config | 0x02) & !0x20; + if !write_controller_command(0x60) { + return false; + } + if !write_controller_data(updated) { + return false; + } + + // 既定値に戻し、データ報告を有効化 + if !send_mouse_command(0xF6) { + return false; + } + if !send_mouse_command(0xF4) { + return false; + } + + true +} + +/// IRQ12 から届いた 1 バイトを取り込み、3バイト揃ったらパケット化する +pub fn push_byte(byte: u8) { + let mut assembler = ASSEMBLER.lock(); + + // 先頭バイトは常に bit3=1。同期が崩れた場合はここで再同期する。 + if assembler.len == 0 && (byte & 0x08) == 0 { + return; + } + + let idx = assembler.len as usize; + if idx >= assembler.bytes.len() { + assembler.len = 0; + return; + } + assembler.bytes[idx] = byte; + assembler.len += 1; + + if assembler.len < 3 { + return; + } + + let b0 = assembler.bytes[0]; + let b1 = assembler.bytes[1]; + let b2 = assembler.bytes[2]; + assembler.len = 0; + drop(assembler); + + // オーバーフローは破棄 + if (b0 & 0xC0) != 0 { + return; + } + + let packet = u32::from(b0) | (u32::from(b1) << 8) | (u32::from(b2) << 16); + push_packet(packet); +} + +/// 完成済みパケットを直接キューへ積む(ユーザー空間注入用) +pub fn push_packet(packet: u32) { + if MOUSE_PACKET_BUF.push(packet).is_err() { + MOUSE_PACKET_DROPS.fetch_add(1, Ordering::Relaxed); + } + + let waiter = MOUSE_WAITER.swap(0, Ordering::AcqRel); + if waiter != 0 { + crate::task::wake_thread(crate::task::ThreadId::from_u64(waiter)); + } +} + +pub fn packet_drop_count() -> u64 { + MOUSE_PACKET_DROPS.load(Ordering::Relaxed) +} + +/// 完成済みパケットを 1 つ取り出す +pub fn pop_packet() -> Option { + MOUSE_PACKET_BUF.pop() +} + +/// ブロッキング read 用に待機スレッドを登録する +pub fn register_waiter(tid: u64) -> bool { + MOUSE_WAITER + .compare_exchange(0, tid, Ordering::Release, Ordering::Acquire) + .is_ok() +} + +/// 待機登録をキャンセルする +pub fn unregister_waiter(tid: u64) { + let _ = MOUSE_WAITER.compare_exchange(tid, 0, Ordering::AcqRel, Ordering::Relaxed); +} diff --git a/src/core/util/vga.rs b/src/core/util/vga.rs new file mode 100644 index 0000000..0a99a85 --- /dev/null +++ b/src/core/util/vga.rs @@ -0,0 +1,318 @@ +//! フレームバッファ出力 + +use core::fmt; +use core::fmt::Write; +use spin::{Mutex, Once}; + +static FB_INFO: Once = Once::new(); + +/// フレームバッファ +#[derive(Clone, Copy)] +pub struct FramebufferInfo { + /// アドレス + pub addr: u64, + /// 横幅 + pub width: usize, + /// 高さ + pub height: usize, + /// 行当たりのピクセル数(ストライド) + pub stride: usize, +} + +/// フォントサイズ (8×8 ビットマップ) +const FONT_HEIGHT: usize = 8; +const FONT_WIDTH: usize = 8; + +/// 8×8 ビットマップフォント (ASCII 0x20–0x7F) +/// bit 0 = 左端ピクセル +#[rustfmt::skip] +static FONT_8X8: [[u8; 8]; 96] = [ + [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], // 0x20 ' ' + [0x18,0x3C,0x3C,0x18,0x18,0x00,0x18,0x00], // 0x21 '!' + [0x36,0x36,0x00,0x00,0x00,0x00,0x00,0x00], // 0x22 '"' + [0x36,0x36,0x7F,0x36,0x7F,0x36,0x36,0x00], // 0x23 '#' + [0x0C,0x3E,0x03,0x1E,0x30,0x1F,0x0C,0x00], // 0x24 '$' + [0x00,0x63,0x33,0x18,0x0C,0x66,0x63,0x00], // 0x25 '%' + [0x1C,0x36,0x1C,0x6E,0x3B,0x33,0x6E,0x00], // 0x26 '&' + [0x06,0x06,0x03,0x00,0x00,0x00,0x00,0x00], // 0x27 '\'' + [0x18,0x0C,0x06,0x06,0x06,0x0C,0x18,0x00], // 0x28 '(' + [0x06,0x0C,0x18,0x18,0x18,0x0C,0x06,0x00], // 0x29 ')' + [0x00,0x66,0x3C,0xFF,0x3C,0x66,0x00,0x00], // 0x2A '*' + [0x00,0x0C,0x0C,0x3F,0x0C,0x0C,0x00,0x00], // 0x2B '+' + [0x00,0x00,0x00,0x00,0x00,0x0C,0x0C,0x06], // 0x2C ',' + [0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00], // 0x2D '-' + [0x00,0x00,0x00,0x00,0x00,0x0C,0x0C,0x00], // 0x2E '.' + [0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x00], // 0x2F '/' + [0x3E,0x63,0x73,0x7B,0x6F,0x67,0x3E,0x00], // 0x30 '0' + [0x0C,0x0E,0x0C,0x0C,0x0C,0x0C,0x3F,0x00], // 0x31 '1' + [0x1E,0x33,0x30,0x1C,0x06,0x33,0x3F,0x00], // 0x32 '2' + [0x1E,0x33,0x30,0x1C,0x30,0x33,0x1E,0x00], // 0x33 '3' + [0x38,0x3C,0x36,0x33,0x7F,0x30,0x78,0x00], // 0x34 '4' + [0x3F,0x03,0x1F,0x30,0x30,0x33,0x1E,0x00], // 0x35 '5' + [0x1C,0x06,0x03,0x1F,0x33,0x33,0x1E,0x00], // 0x36 '6' + [0x3F,0x33,0x30,0x18,0x0C,0x0C,0x0C,0x00], // 0x37 '7' + [0x1E,0x33,0x33,0x1E,0x33,0x33,0x1E,0x00], // 0x38 '8' + [0x1E,0x33,0x33,0x3E,0x30,0x18,0x0E,0x00], // 0x39 '9' + [0x00,0x0C,0x0C,0x00,0x00,0x0C,0x0C,0x00], // 0x3A ':' + [0x00,0x0C,0x0C,0x00,0x00,0x0C,0x0C,0x06], // 0x3B ';' + [0x18,0x0C,0x06,0x03,0x06,0x0C,0x18,0x00], // 0x3C '<' + [0x00,0x00,0x3F,0x00,0x00,0x3F,0x00,0x00], // 0x3D '=' + [0x06,0x0C,0x18,0x30,0x18,0x0C,0x06,0x00], // 0x3E '>' + [0x1E,0x33,0x30,0x18,0x0C,0x00,0x0C,0x00], // 0x3F '?' + [0x3E,0x63,0x7B,0x7B,0x7B,0x03,0x1E,0x00], // 0x40 '@' + [0x0C,0x1E,0x33,0x33,0x3F,0x33,0x33,0x00], // 0x41 'A' + [0x3F,0x66,0x66,0x3E,0x66,0x66,0x3F,0x00], // 0x42 'B' + [0x3C,0x66,0x03,0x03,0x03,0x66,0x3C,0x00], // 0x43 'C' + [0x1F,0x36,0x66,0x66,0x66,0x36,0x1F,0x00], // 0x44 'D' + [0x7F,0x46,0x16,0x1E,0x16,0x46,0x7F,0x00], // 0x45 'E' + [0x7F,0x46,0x16,0x1E,0x16,0x06,0x0F,0x00], // 0x46 'F' + [0x3C,0x66,0x03,0x03,0x73,0x66,0x7C,0x00], // 0x47 'G' + [0x33,0x33,0x33,0x3F,0x33,0x33,0x33,0x00], // 0x48 'H' + [0x1E,0x0C,0x0C,0x0C,0x0C,0x0C,0x1E,0x00], // 0x49 'I' + [0x78,0x30,0x30,0x30,0x33,0x33,0x1E,0x00], // 0x4A 'J' + [0x67,0x66,0x36,0x1E,0x36,0x66,0x67,0x00], // 0x4B 'K' + [0x0F,0x06,0x06,0x06,0x46,0x66,0x7F,0x00], // 0x4C 'L' + [0x63,0x77,0x7F,0x7F,0x6B,0x63,0x63,0x00], // 0x4D 'M' + [0x63,0x67,0x6F,0x7B,0x73,0x63,0x63,0x00], // 0x4E 'N' + [0x1C,0x36,0x63,0x63,0x63,0x36,0x1C,0x00], // 0x4F 'O' + [0x3F,0x66,0x66,0x3E,0x06,0x06,0x0F,0x00], // 0x50 'P' + [0x1E,0x33,0x33,0x33,0x3B,0x1E,0x38,0x00], // 0x51 'Q' + [0x3F,0x66,0x66,0x3E,0x36,0x66,0x67,0x00], // 0x52 'R' + [0x1E,0x33,0x07,0x0E,0x38,0x33,0x1E,0x00], // 0x53 'S' + [0x3F,0x2D,0x0C,0x0C,0x0C,0x0C,0x1E,0x00], // 0x54 'T' + [0x33,0x33,0x33,0x33,0x33,0x33,0x3F,0x00], // 0x55 'U' + [0x33,0x33,0x33,0x33,0x33,0x1E,0x0C,0x00], // 0x56 'V' + [0x63,0x63,0x63,0x6B,0x7F,0x77,0x63,0x00], // 0x57 'W' + [0x63,0x63,0x36,0x1C,0x1C,0x36,0x63,0x00], // 0x58 'X' + [0x33,0x33,0x33,0x1E,0x0C,0x0C,0x1E,0x00], // 0x59 'Y' + [0x7F,0x63,0x31,0x18,0x4C,0x66,0x7F,0x00], // 0x5A 'Z' + [0x1E,0x06,0x06,0x06,0x06,0x06,0x1E,0x00], // 0x5B '[' + [0x03,0x06,0x0C,0x18,0x30,0x60,0x40,0x00], // 0x5C '\' + [0x1E,0x18,0x18,0x18,0x18,0x18,0x1E,0x00], // 0x5D ']' + [0x08,0x1C,0x36,0x63,0x00,0x00,0x00,0x00], // 0x5E '^' + [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF], // 0x5F '_' + [0x0C,0x0C,0x18,0x00,0x00,0x00,0x00,0x00], // 0x60 '`' + [0x00,0x00,0x1E,0x30,0x3E,0x33,0x6E,0x00], // 0x61 'a' + [0x07,0x06,0x06,0x3E,0x66,0x66,0x3B,0x00], // 0x62 'b' + [0x00,0x00,0x1E,0x33,0x03,0x33,0x1E,0x00], // 0x63 'c' + [0x38,0x30,0x30,0x3E,0x33,0x33,0x6E,0x00], // 0x64 'd' + [0x00,0x00,0x1E,0x33,0x3F,0x03,0x1E,0x00], // 0x65 'e' + [0x1C,0x36,0x06,0x0F,0x06,0x06,0x0F,0x00], // 0x66 'f' + [0x00,0x00,0x6E,0x33,0x33,0x3E,0x30,0x1F], // 0x67 'g' + [0x07,0x06,0x36,0x6E,0x66,0x66,0x67,0x00], // 0x68 'h' + [0x0C,0x00,0x0E,0x0C,0x0C,0x0C,0x1E,0x00], // 0x69 'i' + [0x30,0x00,0x30,0x30,0x30,0x33,0x33,0x1E], // 0x6A 'j' + [0x07,0x06,0x66,0x36,0x1E,0x36,0x67,0x00], // 0x6B 'k' + [0x0E,0x0C,0x0C,0x0C,0x0C,0x0C,0x1E,0x00], // 0x6C 'l' + [0x00,0x00,0x33,0x7F,0x7F,0x6B,0x63,0x00], // 0x6D 'm' + [0x00,0x00,0x1F,0x33,0x33,0x33,0x33,0x00], // 0x6E 'n' + [0x00,0x00,0x1E,0x33,0x33,0x33,0x1E,0x00], // 0x6F 'o' + [0x00,0x00,0x3B,0x66,0x66,0x3E,0x06,0x0F], // 0x70 'p' + [0x00,0x00,0x6E,0x33,0x33,0x3E,0x30,0x78], // 0x71 'q' + [0x00,0x00,0x3B,0x6E,0x66,0x06,0x0F,0x00], // 0x72 'r' + [0x00,0x00,0x3E,0x03,0x1E,0x30,0x1F,0x00], // 0x73 's' + [0x08,0x0C,0x3E,0x0C,0x0C,0x2C,0x18,0x00], // 0x74 't' + [0x00,0x00,0x33,0x33,0x33,0x33,0x6E,0x00], // 0x75 'u' + [0x00,0x00,0x33,0x33,0x33,0x1E,0x0C,0x00], // 0x76 'v' + [0x00,0x00,0x63,0x6B,0x7F,0x7F,0x36,0x00], // 0x77 'w' + [0x00,0x00,0x63,0x36,0x1C,0x36,0x63,0x00], // 0x78 'x' + [0x00,0x00,0x33,0x33,0x33,0x3E,0x30,0x1F], // 0x79 'y' + [0x00,0x00,0x3F,0x19,0x0C,0x26,0x3F,0x00], // 0x7A 'z' + [0x38,0x0C,0x0C,0x07,0x0C,0x0C,0x38,0x00], // 0x7B '{' + [0x18,0x18,0x18,0x00,0x18,0x18,0x18,0x00], // 0x7C '|' + [0x07,0x0C,0x0C,0x38,0x0C,0x0C,0x07,0x00], // 0x7D '}' + [0x6E,0x3B,0x00,0x00,0x00,0x00,0x00,0x00], // 0x7E '~' + [0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF], // 0x7F DEL +]; + +/// フレームバッファライター +pub struct Writer { + col: usize, + row: usize, + max_cols: usize, + max_rows: usize, +} + +impl Writer { + fn new(info: &FramebufferInfo) -> Self { + Self { + col: 0, + row: 0, + max_cols: info.width / FONT_WIDTH, + max_rows: info.height / FONT_HEIGHT, + } + } + + fn put_pixel(info: &FramebufferInfo, x: usize, y: usize, color: u32) { + if x < info.width && y < info.height { + let fb_ptr = info.addr as *mut u32; + unsafe { + fb_ptr.add(y * info.stride + x).write_volatile(color); + } + } + } + + /// ビットマップフォントで1文字を描画 + fn draw_char(&self, ch: u8, x: usize, y: usize, fg: u32, bg: u32) { + let code = ch as usize; + let glyph = if code >= 0x20 && code <= 0x7F { + &FONT_8X8[code - 0x20] + } else { + &FONT_8X8[0] // 不明文字はスペース + }; + if let Some(info) = FB_INFO.get() { + for (row_idx, &bits) in glyph.iter().enumerate() { + for bit_idx in 0..FONT_WIDTH { + let color = if (bits >> bit_idx) & 1 != 0 { fg } else { bg }; + Self::put_pixel(info, x + bit_idx, y + row_idx, color); + } + } + } + } + + /// 1バイト書き込み + pub fn write_byte(&mut self, byte: u8) { + match byte { + b'\n' => self.new_line(), + byte => { + if self.col >= self.max_cols { + self.new_line(); + } + let x = self.col * FONT_WIDTH; + let y = self.row * FONT_HEIGHT; + self.draw_char(byte, x, y, 0xCCCCCC, 0x000000); + self.col += 1; + } + } + } + + /// 文字列を書き込み + pub fn write_string(&mut self, s: &str) { + for byte in s.bytes() { + match byte { + 0x20..=0x7E | b'\n' => self.write_byte(byte), + _ => self.write_byte(b'?'), + } + } + } + + /// 改行処理(スクロール付き) + fn new_line(&mut self) { + self.col = 0; + self.row += 1; + if self.row >= self.max_rows { + self.scroll_up(); + } + } + + /// 1行スクロールアップ + fn scroll_up(&mut self) { + if let Some(info) = FB_INFO.get() { + let fb_ptr = info.addr as *mut u32; + let row_pixels = FONT_HEIGHT * info.stride; + let total_rows = self.max_rows; + unsafe { + let src = fb_ptr.add(row_pixels); + let len = row_pixels * (total_rows - 1); + core::ptr::copy(src, fb_ptr, len); + // 最終行をクリア + let last = fb_ptr.add(row_pixels * (total_rows - 1)); + for i in 0..(row_pixels) { + last.add(i).write_volatile(0x000000); + } + } + self.row = total_rows - 1; + } + } + + /// 画面全体をクリア + pub fn clear_screen(&mut self) { + if let Some(info) = FB_INFO.get() { + let fb_ptr = info.addr as *mut u32; + let total = info.height * info.stride; + unsafe { + for i in 0..total { + fb_ptr.add(i).write_volatile(0x000000); + } + } + } + self.row = 0; + self.col = 0; + } +} + +impl Write for Writer { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.write_string(s); + Ok(()) + } +} + +/// グローバルライター +static WRITER: Once> = Once::new(); + +/// フレームバッファ情報を返す(syscall 用) +pub fn get_info() -> Option { + FB_INFO.get().copied() +} + +/// フレームバッファを初期化 +pub fn init(addr: u64, width: usize, height: usize, stride: usize) { + FB_INFO.call_once(|| FramebufferInfo { + addr, + width, + height, + stride, + }); + if let Some(info) = FB_INFO.get() { + WRITER.call_once(|| Mutex::new(Writer::new(info))); + if let Some(writer) = WRITER.get() { + writer.lock().clear_screen(); + } + } +} + +/// フレームバッファに文字列を出力 +pub fn print(args: fmt::Arguments) { + if let Some(writer) = WRITER.get() { + x86_64::instructions::interrupts::without_interrupts(|| { + let _ = writer.lock().write_fmt(args); + }); + } +} + +/// カーソルのピクセルY座標を設定する(シェルとの同期用) +pub fn set_cursor_pixel_y(pixel_y: usize) { + if let Some(writer) = WRITER.get() { + let mut w = writer.lock(); + w.row = pixel_y / FONT_HEIGHT; + w.col = 0; + } +} + +/// カーソルの現在ピクセルY座標を取得する(シェルとの同期用) +pub fn get_cursor_pixel_y() -> usize { + if let Some(writer) = WRITER.get() { + let w = writer.lock(); + w.row * FONT_HEIGHT + } else { + 0 + } +} + +/// フレームバッファ出力マクロ +#[macro_export] +macro_rules! vprint { + ($($arg:tt)*) => { + $crate::util::vga::print(format_args!($($arg)*)) + }; +} + +/// 改行付きフレームバッファ出力マクロ +#[macro_export] +macro_rules! vprintln { + () => ($crate::vprint!("\n")); + ($($arg:tt)*) => { + $crate::vprint!("{}\n", format_args!($($arg)*)) + }; +} diff --git a/src/drivers/net/.cargo/config.toml b/src/drivers/net/.cargo/config.toml new file mode 100644 index 0000000..a177f26 --- /dev/null +++ b/src/drivers/net/.cargo/config.toml @@ -0,0 +1,11 @@ +[build] +target = "../../x86_64-mochios.json" + +[unstable] +build-std = ["std", "panic_abort"] +json-target-spec = true + +[target.x86_64-mochios] +rustflags = [ + "-C", "link-arg=-nostdlib", +] diff --git a/src/drivers/net/Cargo.toml b/src/drivers/net/Cargo.toml new file mode 100644 index 0000000..b3e3503 --- /dev/null +++ b/src/drivers/net/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "netdrv" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "netdrv" +path = "src/main_refactored.rs" +test = false +bench = false + +[dependencies] +swiftlib = { path = "../../user", features = ["std-support"] } + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +opt-level = "z" +lto = true diff --git a/src/drivers/net/build.rs b/src/drivers/net/build.rs new file mode 100644 index 0000000..093088e --- /dev/null +++ b/src/drivers/net/build.rs @@ -0,0 +1,90 @@ +use std::env; +use std::path::{Path, PathBuf}; + +fn cargo_toml_has_workspace(path: &Path) -> bool { + std::fs::read_to_string(path) + .map(|s| { + s.lines() + .map(|line| line.trim()) + .any(|line| line == "[workspace]") + }) + .unwrap_or(false) +} + +fn find_project_root(manifest_dir: &Path) -> PathBuf { + if let Ok(workspace_dir) = env::var("CARGO_WORKSPACE_DIR") { + return PathBuf::from(workspace_dir); + } + + for ancestor in manifest_dir.ancestors().skip(1) { + if ancestor.join("ramfs").join("lib").exists() { + return ancestor.to_path_buf(); + } + } + + for ancestor in manifest_dir.ancestors().skip(1) { + let cargo_toml = ancestor.join("Cargo.toml"); + if cargo_toml.exists() && cargo_toml_has_workspace(&cargo_toml) { + return ancestor.to_path_buf(); + } + } + + manifest_dir.to_path_buf() +} + +fn main() { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"); + let manifest_path = Path::new(&manifest_dir); + let project_root = find_project_root(manifest_path); + + let libs_dir = project_root.join("ramfs").join("lib"); + + println!("cargo:rustc-link-search=native={}", libs_dir.display()); + println!("cargo:rustc-link-arg={}/crt0.o", libs_dir.display()); + println!("cargo:rustc-link-arg=-static"); + println!("cargo:rustc-link-arg=-no-pie"); + println!("cargo:rustc-link-arg=-T{}/linker.ld", manifest_dir); + println!("cargo:rerun-if-changed={}", manifest_path.join("linker.ld").display()); + println!("cargo:rustc-link-arg=--allow-multiple-definition"); + + println!("cargo:rustc-link-lib=static=c"); + println!("cargo:rustc-link-lib=static=g"); + println!("cargo:rustc-link-lib=static=m"); + + let libgcc_s = libs_dir.join("libgcc_s.a"); + let libg = libs_dir.join("libg.a"); + if !libgcc_s.exists() && libg.exists() { + let tmp_name = format!( + "libgcc_s.a.tmp.{}.{}", + std::process::id(), + std::thread::current().name().unwrap_or("net-build") + ); + let libgcc_tmp = libs_dir.join(tmp_name); + if let Err(err) = std::fs::copy(&libg, &libgcc_tmp) { + panic!( + "failed to copy {} to {} for static gcc_s linking: {}", + libg.display(), + libgcc_tmp.display(), + err + ); + } + if let Err(err) = std::fs::rename(&libgcc_tmp, &libgcc_s) { + if libgcc_s.exists() { + let _ = std::fs::remove_file(&libgcc_tmp); + } else { + let _ = std::fs::remove_file(&libgcc_tmp); + panic!( + "failed to rename {} to {} for static gcc_s linking: {}", + libgcc_tmp.display(), + libgcc_s.display(), + err + ); + } + } + } + println!("cargo:rustc-link-lib=static=gcc_s"); + + println!("cargo:rerun-if-changed={}", libs_dir.join("libc.a").display()); + println!("cargo:rerun-if-changed={}", libs_dir.join("libg.a").display()); + println!("cargo:rerun-if-changed={}", libs_dir.join("libm.a").display()); +} diff --git a/src/drivers/net/linker.ld b/src/drivers/net/linker.ld new file mode 100644 index 0000000..f502ec1 --- /dev/null +++ b/src/drivers/net/linker.ld @@ -0,0 +1,33 @@ +OUTPUT_FORMAT("elf64-x86-64") +OUTPUT_ARCH(i386:x86-64) +ENTRY(_start) + +SECTIONS +{ + . = 0x800000; + + .text : { + *(.text._start) + *(.text .text.*) + } + + .rodata : ALIGN(4K) { + *(.rodata .rodata.*) + } + + .data : ALIGN(4K) { + *(.data .data.*) + } + + .bss : ALIGN(4K) { + __bss_start = .; + *(.bss .bss.*) + *(COMMON) + __bss_end = .; + } + + /DISCARD/ : { + *(.eh_frame) + *(.comment) + } +} diff --git a/src/drivers/net/src/main.rs b/src/drivers/net/src/main.rs new file mode 100644 index 0000000..ec156b2 --- /dev/null +++ b/src/drivers/net/src/main.rs @@ -0,0 +1,968 @@ +use core::ptr::{read_volatile, write_volatile}; +use core::sync::atomic::{compiler_fence, Ordering as AtomicOrdering}; + +use swiftlib::{mmio, port, privileged, task, time}; + +const PCI_CFG_ADDR_PORT: u16 = 0xCF8; +const PCI_CFG_DATA_PORT: u16 = 0xCFC; + +const PCI_COMMAND_IO: u16 = 1 << 0; +const PCI_COMMAND_MEM: u16 = 1 << 1; +const PCI_COMMAND_BUS_MASTER: u16 = 1 << 2; + +const CLASS_NETWORK: u8 = 0x02; +const VIRTIO_NET_F_MAC: u32 = 5; +const VIRTIO_NET_F_STATUS: u32 = 16; + +const VIRTIO_PIO_DEVICE_FEATURES: u16 = 0x00; +const VIRTIO_PIO_GUEST_FEATURES: u16 = 0x04; +const VIRTIO_PIO_QUEUE_ADDR_PFN: u16 = 0x08; +const VIRTIO_PIO_QUEUE_SIZE: u16 = 0x0C; +const VIRTIO_PIO_QUEUE_SELECT: u16 = 0x0E; +const VIRTIO_PIO_QUEUE_NOTIFY: u16 = 0x10; +const VIRTIO_PIO_DEVICE_STATUS: u16 = 0x12; +const VIRTIO_PIO_ISR_STATUS: u16 = 0x13; +const VIRTIO_PIO_DEVICE_CONFIG: u16 = 0x14; + +const VIRTIO_STATUS_ACKNOWLEDGE: u8 = 1 << 0; +const VIRTIO_STATUS_DRIVER: u8 = 1 << 1; +const VIRTIO_STATUS_DRIVER_OK: u8 = 1 << 2; + +const VIRTIO_QUEUE_RX: u16 = 0; +const VIRTIO_QUEUE_TX: u16 = 1; +const PAGE_SIZE: usize = 4096; +const RX_BUFFER_LEN: u32 = 2048; +const RX_BUFFER_COUNT: usize = 32; +const ETH_TYPE_ARP: u16 = 0x0806; +const ETH_TYPE_IPV4: u16 = 0x0800; +const ARP_OP_REQUEST: u16 = 1; +const ARP_OP_REPLY: u16 = 2; +const IP_PROTO_ICMP: u8 = 1; +const ICMP_ECHO_REQUEST: u8 = 8; +const ICMP_ECHO_REPLY: u8 = 0; +const ICMP_ECHO_ID: u16 = 0x1337; +const ICMP_ECHO_SEQ: u16 = 1; +const GATEWAY_IP: [u8; 4] = [10, 0, 2, 2]; +const LOCAL_IP: [u8; 4] = [10, 0, 2, 15]; + +const VRING_DESC_F_WRITE: u16 = 2; +const VIRTIO_NET_HDR_LEN: usize = 10; +const RX_POLL_BUDGET: usize = 64; +const TX_POLL_BUDGET: usize = 64; + +#[derive(Clone, Copy, Debug)] +struct PciBdf { + bus: u8, + device: u8, + function: u8, +} + +#[derive(Clone, Copy, Debug)] +enum NetKind { + VirtioNet, + E1000, + Unknown, +} + +#[derive(Clone, Copy, Debug)] +struct NetDevice { + bdf: PciBdf, + vendor_id: u16, + device_id: u16, + kind: NetKind, + bar0: u32, +} + +#[repr(C)] +#[derive(Clone, Copy)] +struct VringDesc { + addr: u64, + len: u32, + flags: u16, + next: u16, +} + +#[repr(C)] +#[derive(Clone, Copy)] +struct VringUsedElem { + id: u32, + len: u32, +} + +#[derive(Clone, Copy)] +struct SharedBuf { + virt: *mut u8, + phys: u64, + len: u32, +} + +struct VirtQueue { + index: u16, + size: u16, + base: *mut u8, + phys: u64, + avail_ring_off: usize, + used_ring_off: usize, + next_avail_idx: u16, + last_used_idx: u16, +} + +impl VirtQueue { + fn desc_ptr(&self, idx: u16) -> *mut VringDesc { + unsafe { self.base.add(idx as usize * core::mem::size_of::()) as *mut VringDesc } + } + + fn avail_idx_ptr(&self) -> *mut u16 { + unsafe { self.base.add(self.avail_ring_off + 2) as *mut u16 } + } + + fn avail_ring_entry_ptr(&self, slot: usize) -> *mut u16 { + unsafe { self.base.add(self.avail_ring_off + 4 + slot * 2) as *mut u16 } + } + + fn used_idx(&self) -> u16 { + unsafe { read_volatile(self.base.add(self.used_ring_off + 2) as *const u16) } + } + + fn used_elem(&self, slot: usize) -> VringUsedElem { + unsafe { + read_volatile( + self.base.add(self.used_ring_off + 4 + slot * core::mem::size_of::()) + as *const VringUsedElem, + ) + } + } +} + +struct VirtioNetRuntime { + base: u16, + mac: [u8; 6], + rxq: VirtQueue, + txq: VirtQueue, + rx_bufs: Vec, + tx_buf: SharedBuf, + tx_inflight: bool, + arp_sent: bool, + gateway_mac: Option<[u8; 6]>, + ping_sent: bool, + ping_reply_seen: bool, + ping_pending: bool, + ticks: u64, +} + +fn pci_config_address(bdf: PciBdf, offset: u8) -> u32 { + 0x8000_0000 + | ((bdf.bus as u32) << 16) + | ((bdf.device as u32) << 11) + | ((bdf.function as u32) << 8) + | (u32::from(offset) & 0xFC) +} + +fn pci_read_u32(bdf: PciBdf, offset: u8) -> u32 { + let addr = pci_config_address(bdf, offset); + port::outl(PCI_CFG_ADDR_PORT, addr); + port::inl(PCI_CFG_DATA_PORT) +} + +fn pci_read_u16(bdf: PciBdf, offset: u8) -> u16 { + let aligned = offset & 0xFC; + let shift = u32::from(offset & 0x02) * 8; + ((pci_read_u32(bdf, aligned) >> shift) & 0xFFFF) as u16 +} + +fn pci_write_u16(bdf: PciBdf, offset: u8, value: u16) { + let aligned = offset & 0xFC; + let mut reg = pci_read_u32(bdf, aligned); + let shift = u32::from(offset & 0x02) * 8; + reg &= !(0xFFFFu32 << shift); + reg |= u32::from(value) << shift; + let addr = pci_config_address(bdf, aligned); + port::outl(PCI_CFG_ADDR_PORT, addr); + port::outl(PCI_CFG_DATA_PORT, reg); +} + +fn pci_function_exists(bdf: PciBdf) -> bool { + pci_read_u16(bdf, 0x00) != 0xFFFF +} + +fn classify_net_device(vendor_id: u16, device_id: u16) -> NetKind { + match (vendor_id, device_id) { + // virtio-net (legacy/transitional) + (0x1AF4, 0x1000) => NetKind::VirtioNet, + // virtio-net (modern) + (0x1AF4, 0x1041) => NetKind::VirtioNet, + // Intel e1000 family (QEMUでよく使う) + (0x8086, 0x100E) | (0x8086, 0x100F) | (0x8086, 0x10D3) => NetKind::E1000, + _ => NetKind::Unknown, + } +} + +fn enable_device_command_bits(bdf: PciBdf) { + let mut command = pci_read_u16(bdf, 0x04); + command |= PCI_COMMAND_IO | PCI_COMMAND_MEM | PCI_COMMAND_BUS_MASTER; + pci_write_u16(bdf, 0x04, command); +} + +fn find_network_devices() -> Vec { + let mut devices = Vec::new(); + + for bus in 0u16..=255 { + for device in 0u16..32 { + let bdf0 = PciBdf { + bus: bus as u8, + device: device as u8, + function: 0, + }; + if !pci_function_exists(bdf0) { + continue; + } + + let header = pci_read_u32(bdf0, 0x0C); + let header_type = ((header >> 16) & 0xFF) as u8; + let function_count = if (header_type & 0x80) != 0 { 8 } else { 1 }; + + for function in 0..function_count { + let bdf = PciBdf { + bus: bus as u8, + device: device as u8, + function: function as u8, + }; + if !pci_function_exists(bdf) { + continue; + } + + let class_reg = pci_read_u32(bdf, 0x08); + let class_code = ((class_reg >> 24) & 0xFF) as u8; + if class_code != CLASS_NETWORK { + continue; + } + + let vendor_device = pci_read_u32(bdf, 0x00); + let vendor_id = (vendor_device & 0xFFFF) as u16; + let device_id = ((vendor_device >> 16) & 0xFFFF) as u16; + let bar0 = pci_read_u32(bdf, 0x10); + let kind = classify_net_device(vendor_id, device_id); + + devices.push(NetDevice { + bdf, + vendor_id, + device_id, + kind, + bar0, + }); + } + } + } + + devices +} + +fn try_map_mmio_bar0(dev: NetDevice) { + if (dev.bar0 & 0x1) != 0 { + if let NetKind::VirtioNet = dev.kind { + println!("[NETDRV] BAR0 is I/O space (legacy virtio-net PIO)"); + } else { + println!("[NETDRV] BAR0 is I/O space (PIO), MMIO map skipped"); + } + return; + } + + let mmio_base = u64::from(dev.bar0 & 0xFFFF_FFF0); + if mmio_base == 0 { + println!("[NETDRV] BAR0 MMIO base is zero"); + return; + } + + match mmio::map_physical(mmio_base, 0x1000) { + Ok(mapped) => { + println!( + "[NETDRV] MMIO mapped phys={:#x} -> virt={:#x}", + mmio_base, mapped as u64 + ); + } + Err(errno) => { + println!( + "[NETDRV] MMIO map failed phys={:#x}, errno={}", + mmio_base, errno + ); + } + } +} + +fn virtio_pio_base(bar0: u32) -> Option { + if (bar0 & 0x1) == 0 { + return None; + } + let base = bar0 & 0xFFFF_FFFC; + if base == 0 || base > 0xFFFF { + return None; + } + Some(base as u16) +} + +fn virtio_legacy_init_pio(dev: NetDevice) -> Option { + let Some(base) = virtio_pio_base(dev.bar0) else { + println!("[NETDRV] virtio-net BAR0 is not legacy PIO"); + return None; + }; + + let device_features = port::inl(base + VIRTIO_PIO_DEVICE_FEATURES); + let guest_features = device_features & ((1u32 << VIRTIO_NET_F_MAC) | (1u32 << VIRTIO_NET_F_STATUS)); + println!("[NETDRV] virtio legacy PIO base={:#x}", base); + println!("[NETDRV] virtio device_features={:#010x}", device_features); + println!("[NETDRV] virtio guest_features ={:#010x}", guest_features); + + port::outb(base + VIRTIO_PIO_DEVICE_STATUS, 0); + port::outb( + base + VIRTIO_PIO_DEVICE_STATUS, + VIRTIO_STATUS_ACKNOWLEDGE | VIRTIO_STATUS_DRIVER, + ); + port::outl(base + VIRTIO_PIO_GUEST_FEATURES, guest_features); + port::outb( + base + VIRTIO_PIO_DEVICE_STATUS, + VIRTIO_STATUS_ACKNOWLEDGE | VIRTIO_STATUS_DRIVER | VIRTIO_STATUS_DRIVER_OK, + ); + + let status = port::inb(base + VIRTIO_PIO_DEVICE_STATUS); + println!("[NETDRV] virtio status={:#04x}", status); + + let mut mac = [0u8; 6]; + if (guest_features & (1u32 << VIRTIO_NET_F_MAC)) != 0 { + for (i, byte) in mac.iter_mut().enumerate() { + *byte = port::inb(base + VIRTIO_PIO_DEVICE_CONFIG + i as u16); + } + println!( + "[NETDRV] virtio MAC {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] + ); + } else { + println!("[NETDRV] virtio MAC feature not advertised"); + } + + let isr = port::inb(base + VIRTIO_PIO_ISR_STATUS); + println!("[NETDRV] virtio isr={:#04x}", isr); + + let rxq = setup_virtio_legacy_queue(base, VIRTIO_QUEUE_RX)?; + let txq = setup_virtio_legacy_queue(base, VIRTIO_QUEUE_TX)?; + let tx_buf = alloc_shared_buf(PAGE_SIZE as u32)?; + + let mut rt = VirtioNetRuntime { + base, + mac, + rxq, + txq, + rx_bufs: Vec::new(), + tx_buf, + tx_inflight: false, + arp_sent: false, + gateway_mac: None, + ping_sent: false, + ping_reply_seen: false, + ping_pending: false, + ticks: 0, + }; + + if !populate_rx_ring(&mut rt) { + return None; + } + + Some(rt) +} + +fn align_up(value: usize, align: usize) -> usize { + if align == 0 { + return value; + } + (value + (align - 1)) & !(align - 1) +} + +fn compute_virtqueue_bytes(queue_size: usize) -> usize { + // descriptor table + avail ring + padding(used ring alignment) + used ring + let desc_bytes = 16usize.saturating_mul(queue_size); + let avail_bytes = 6usize.saturating_add(2usize.saturating_mul(queue_size)); + let used_bytes = 6usize.saturating_add(8usize.saturating_mul(queue_size)); + let used_off = align_up(desc_bytes.saturating_add(avail_bytes), PAGE_SIZE); + used_off.saturating_add(used_bytes) +} + +fn is_syscall_error(value: u64) -> bool { + (-4095..=-1).contains(&(value as i64)) +} + +fn alloc_shared_buf(len: u32) -> Option { + let page_count = align_up(len as usize, PAGE_SIZE) / PAGE_SIZE; + let mut phys_addrs = vec![0u64; page_count]; + let virt = unsafe { privileged::alloc_shared_pages(page_count as u64, Some(&mut phys_addrs), 0) }; + if is_syscall_error(virt) { + println!("[NETDRV] alloc_shared_pages(buf) failed: errno={}", virt as i64); + return None; + } + + if phys_addrs.is_empty() { + return None; + } + + Some(SharedBuf { + virt: virt as *mut u8, + phys: phys_addrs[0], + len, + }) +} + +fn alloc_phys_contiguous(bytes: usize) -> Option<(u64, *mut u8)> { + #[derive(Clone, Copy)] + struct PageAlloc { + virt: u64, + phys: u64, + } + + let page_count = align_up(bytes, PAGE_SIZE) / PAGE_SIZE; + let required_run = page_count; + let max_probe_pages = 64usize; + let mut pool: Vec = Vec::new(); + + for _ in 0..max_probe_pages { + let mut phys_buf = [0u64; 1]; + let virt = unsafe { privileged::alloc_shared_pages(1, Some(&mut phys_buf), 0) }; + if is_syscall_error(virt) { + println!("[NETDRV] alloc_shared_pages failed: errno={}", virt as i64); + break; + } + pool.push(PageAlloc { + virt, + phys: phys_buf[0], + }); + + if pool.len() < required_run { + continue; + } + + let mut phys_sorted: Vec = pool.iter().map(|p| p.phys).collect(); + phys_sorted.sort_unstable(); + phys_sorted.dedup(); + + for start_idx in 0..=phys_sorted.len().saturating_sub(required_run) { + let start_phys = phys_sorted[start_idx]; + let mut contiguous = true; + for step in 1..required_run { + let expected = start_phys + (step as u64 * PAGE_SIZE as u64); + if phys_sorted[start_idx + step] != expected { + contiguous = false; + break; + } + } + if !contiguous { + continue; + } + + let selected_phys: Vec = (0..required_run) + .map(|step| start_phys + (step as u64 * PAGE_SIZE as u64)) + .collect(); + + let queue_virt = unsafe { + privileged::map_physical_pages(task::gettid(), &selected_phys, 0) + }; + if is_syscall_error(queue_virt) { + println!( + "[NETDRV] map_physical_pages failed: errno={}", + queue_virt as i64 + ); + continue; + } + + for page in &pool { + let is_selected = selected_phys.iter().any(|&p| p == page.phys); + let rc = privileged::unmap_pages(page.virt, 1, !is_selected); + if rc != 0 { + println!( + "[NETDRV] unmap_pages failed virt={:#x} rc={}", + page.virt, rc as i64 + ); + } + } + + return Some((start_phys, queue_virt as *mut u8)); + } + } + + for page in &pool { + let rc = privileged::unmap_pages(page.virt, 1, true); + if rc != 0 { + println!( + "[NETDRV] unmap_pages cleanup failed virt={:#x} rc={}", + page.virt, rc as i64 + ); + } + } + + None +} + +fn setup_virtio_legacy_queue(base: u16, queue_index: u16) -> Option { + port::outw(base + VIRTIO_PIO_QUEUE_SELECT, queue_index); + let queue_size = port::inw(base + VIRTIO_PIO_QUEUE_SIZE); + if queue_size == 0 { + println!("[NETDRV] queue {} not available", queue_index); + return None; + } + + let bytes = compute_virtqueue_bytes(queue_size as usize); + let Some((phys, virt)) = alloc_phys_contiguous(bytes) else { + println!( + "[NETDRV] queue {} allocation failed (size={} bytes)", + queue_index, bytes + ); + return None; + }; + + let aligned = align_up(bytes, PAGE_SIZE); + unsafe { + core::ptr::write_bytes(virt, 0, aligned); + } + + let pfn = (phys >> 12) as u32; + port::outl(base + VIRTIO_PIO_QUEUE_ADDR_PFN, pfn); + let programmed = port::inl(base + VIRTIO_PIO_QUEUE_ADDR_PFN); + if programmed != pfn { + println!( + "[NETDRV] queue {} PFN mismatch: wrote={:#x} read={:#x}", + queue_index, pfn, programmed + ); + return None; + } + + let avail_ring_off = queue_size as usize * core::mem::size_of::(); + let used_ring_off = align_up( + avail_ring_off + 6 + (queue_size as usize * 2), + PAGE_SIZE, + ); + + println!( + "[NETDRV] queue {} ready size={} bytes={} pfn={:#x}", + queue_index, queue_size, bytes, pfn + ); + + Some(VirtQueue { + index: queue_index, + size: queue_size, + base: virt, + phys, + avail_ring_off, + used_ring_off, + next_avail_idx: 0, + last_used_idx: 0, + }) +} + +fn enqueue_desc_to_avail(queue: &mut VirtQueue, desc_id: u16) { + let slot = (queue.next_avail_idx % queue.size) as usize; + unsafe { + write_volatile(queue.avail_ring_entry_ptr(slot), desc_id); + } + compiler_fence(AtomicOrdering::SeqCst); + queue.next_avail_idx = queue.next_avail_idx.wrapping_add(1); + unsafe { + write_volatile(queue.avail_idx_ptr(), queue.next_avail_idx); + } +} + +fn populate_rx_ring(rt: &mut VirtioNetRuntime) -> bool { + let target_count = core::cmp::min(RX_BUFFER_COUNT, rt.rxq.size as usize); + for desc_id in 0..target_count { + let Some(buf) = alloc_shared_buf(RX_BUFFER_LEN) else { + println!("[NETDRV] rx buffer allocation failed at {}", desc_id); + return false; + }; + rt.rx_bufs.push(buf); + let desc = VringDesc { + addr: buf.phys, + len: buf.len, + flags: VRING_DESC_F_WRITE, + next: 0, + }; + unsafe { + write_volatile(rt.rxq.desc_ptr(desc_id as u16), desc); + } + enqueue_desc_to_avail(&mut rt.rxq, desc_id as u16); + } + port::outw(rt.base + VIRTIO_PIO_QUEUE_NOTIFY, VIRTIO_QUEUE_RX); + println!("[NETDRV] RX ring primed with {} buffers", target_count); + true +} + +fn poll_tx(rt: &mut VirtioNetRuntime) { + let used = rt.txq.used_idx(); + let mut processed = 0usize; + while rt.txq.last_used_idx != used && processed < TX_POLL_BUDGET { + let slot = (rt.txq.last_used_idx % rt.txq.size) as usize; + let elem = rt.txq.used_elem(slot); + println!("[NETDRV] TX complete: desc={} len={}", elem.id, elem.len); + rt.txq.last_used_idx = rt.txq.last_used_idx.wrapping_add(1); + rt.tx_inflight = false; + processed += 1; + } +} + +fn poll_rx(rt: &mut VirtioNetRuntime) { + let used = rt.rxq.used_idx(); + let mut recycled = 0usize; + let mut processed = 0usize; + while rt.rxq.last_used_idx != used && processed < RX_POLL_BUDGET { + let slot = (rt.rxq.last_used_idx % rt.rxq.size) as usize; + let elem = rt.rxq.used_elem(slot); + let desc_id = elem.id as usize; + if desc_id < rt.rx_bufs.len() { + let frame_total = elem.len as usize; + if frame_total > VIRTIO_NET_HDR_LEN { + let frame_len = frame_total - VIRTIO_NET_HDR_LEN; + let frame_ptr = unsafe { rt.rx_bufs[desc_id].virt.add(VIRTIO_NET_HDR_LEN) }; + let frame = unsafe { core::slice::from_raw_parts(frame_ptr as *const u8, frame_len) }; + handle_rx_frame(rt, frame); + } + enqueue_desc_to_avail(&mut rt.rxq, desc_id as u16); + recycled = recycled.saturating_add(1); + } else { + println!( + "[NETDRV] RX used elem out of range: desc={} len={}", + elem.id, elem.len + ); + } + rt.rxq.last_used_idx = rt.rxq.last_used_idx.wrapping_add(1); + processed += 1; + } + + if recycled > 0 { + port::outw(rt.base + VIRTIO_PIO_QUEUE_NOTIFY, VIRTIO_QUEUE_RX); + } + if processed == RX_POLL_BUDGET { + println!("[NETDRV] RX poll budget reached"); + } +} + +fn write_be16(dst: &mut [u8], value: u16) { + dst[0] = (value >> 8) as u8; + dst[1] = value as u8; +} + +fn checksum16(data: &[u8]) -> u16 { + let mut sum: u32 = 0; + let mut i = 0usize; + while i + 1 < data.len() { + sum = sum.wrapping_add(u16::from_be_bytes([data[i], data[i + 1]]) as u32); + i += 2; + } + if i < data.len() { + sum = sum.wrapping_add((data[i] as u32) << 8); + } + while (sum >> 16) != 0 { + sum = (sum & 0xFFFF) + (sum >> 16); + } + !(sum as u16) +} + +fn queue_tx_frame(rt: &mut VirtioNetRuntime, frame: &[u8]) -> bool { + if rt.tx_inflight { + println!("[NETDRV] TX busy, frame deferred"); + return false; + } + let wire_frame_len = core::cmp::max(frame.len(), 60); + let needed = VIRTIO_NET_HDR_LEN + wire_frame_len; + if needed > rt.tx_buf.len as usize { + println!("[NETDRV] TX frame too large: {}", frame.len()); + return false; + } + + let tx_region = unsafe { core::slice::from_raw_parts_mut(rt.tx_buf.virt, needed) }; + tx_region[..VIRTIO_NET_HDR_LEN].fill(0); + tx_region[VIRTIO_NET_HDR_LEN..].fill(0); + tx_region[VIRTIO_NET_HDR_LEN..(VIRTIO_NET_HDR_LEN + frame.len())].copy_from_slice(frame); + + let desc = VringDesc { + addr: rt.tx_buf.phys, + len: needed as u32, + flags: 0, + next: 0, + }; + unsafe { + write_volatile(rt.txq.desc_ptr(0), desc); + } + enqueue_desc_to_avail(&mut rt.txq, 0); + port::outw(rt.base + VIRTIO_PIO_QUEUE_NOTIFY, VIRTIO_QUEUE_TX); + rt.tx_inflight = true; + true +} + +fn send_arp_request(rt: &mut VirtioNetRuntime) { + let mut frame = [0u8; 42]; + frame[0..6].fill(0xFF); + frame[6..12].copy_from_slice(&rt.mac); + write_be16(&mut frame[12..14], ETH_TYPE_ARP); + write_be16(&mut frame[14..16], 1); + write_be16(&mut frame[16..18], ETH_TYPE_IPV4); + frame[18] = 6; + frame[19] = 4; + write_be16(&mut frame[20..22], ARP_OP_REQUEST); + frame[22..28].copy_from_slice(&rt.mac); + frame[28..32].copy_from_slice(&LOCAL_IP); + frame[32..38].fill(0); + frame[38..42].copy_from_slice(&GATEWAY_IP); + + if queue_tx_frame(rt, &frame) { + rt.arp_sent = true; + println!("[NETDRV] ARP who-has 10.0.2.2 sent"); + } +} + +fn send_icmp_echo(rt: &mut VirtioNetRuntime, dst_mac: [u8; 6]) { + println!( + "[NETDRV] send_icmp_echo: tx_inflight={} ping_sent={} pending={}", + rt.tx_inflight, rt.ping_sent, rt.ping_pending + ); + let payload = b"mochios-net"; + let ip_total_len = (20 + 8 + payload.len()) as u16; + let mut frame = [0u8; 128]; + + frame[0..6].copy_from_slice(&dst_mac); + frame[6..12].copy_from_slice(&rt.mac); + write_be16(&mut frame[12..14], ETH_TYPE_IPV4); + + let ip = &mut frame[14..34]; + ip.fill(0); + ip[0] = 0x45; + write_be16(&mut ip[2..4], ip_total_len); + write_be16(&mut ip[4..6], 0x1234); + ip[8] = 64; + ip[9] = IP_PROTO_ICMP; + ip[12..16].copy_from_slice(&LOCAL_IP); + ip[16..20].copy_from_slice(&GATEWAY_IP); + let ip_csum = checksum16(ip); + write_be16(&mut ip[10..12], ip_csum); + + let icmp_len = 8 + payload.len(); + let icmp = &mut frame[34..(34 + icmp_len)]; + icmp.fill(0); + icmp[0] = ICMP_ECHO_REQUEST; + write_be16(&mut icmp[4..6], ICMP_ECHO_ID); + write_be16(&mut icmp[6..8], ICMP_ECHO_SEQ); + icmp[8..].copy_from_slice(payload); + let icmp_csum = checksum16(icmp); + write_be16(&mut icmp[2..4], icmp_csum); + + let frame_len = 14 + ip_total_len as usize; + if queue_tx_frame(rt, &frame[..frame_len]) { + rt.ping_sent = true; + println!("[NETDRV] ICMP echo request sent to 10.0.2.2"); + } else { + println!("[NETDRV] ICMP enqueue failed"); + } +} + +fn try_send_pending_icmp(rt: &mut VirtioNetRuntime, dst_mac: [u8; 6]) { + println!( + "[NETDRV] try_send_pending_icmp: tx_inflight={} ping_sent={} pending={}", + rt.tx_inflight, rt.ping_sent, rt.ping_pending + ); + if rt.tx_inflight { + rt.ping_pending = true; + return; + } + send_icmp_echo(rt, dst_mac); + if !rt.tx_inflight { + rt.ping_pending = true; + } else { + rt.ping_pending = false; + } +} + +fn handle_arp(rt: &mut VirtioNetRuntime, frame: &[u8]) { + if frame.len() < 42 { + return; + } + let arp = &frame[14..42]; + let op = u16::from_be_bytes([arp[6], arp[7]]); + let sender_mac = [arp[8], arp[9], arp[10], arp[11], arp[12], arp[13]]; + let sender_ip = [arp[14], arp[15], arp[16], arp[17]]; + let target_ip = [arp[24], arp[25], arp[26], arp[27]]; + if op == ARP_OP_REPLY && sender_ip == GATEWAY_IP && target_ip == LOCAL_IP { + rt.gateway_mac = Some(sender_mac); + rt.ping_pending = true; + println!( + "[NETDRV] ARP reply: gateway MAC {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + sender_mac[0], sender_mac[1], sender_mac[2], sender_mac[3], sender_mac[4], sender_mac[5] + ); + if rt.tx_inflight { + println!("[NETDRV] ARP learned but TX busy, defer ICMP"); + rt.ping_pending = true; + } else { + println!("[NETDRV] ARP learned, send ICMP now"); + send_icmp_echo(rt, sender_mac); + if rt.tx_inflight { + rt.ping_pending = false; + } + } + } else if op == ARP_OP_REPLY { + println!( + "[NETDRV] ARP reply ignored: sender={}.{}.{}.{} target={}.{}.{}.{}", + sender_ip[0], sender_ip[1], sender_ip[2], sender_ip[3], + target_ip[0], target_ip[1], target_ip[2], target_ip[3] + ); + } +} + +fn handle_ipv4(rt: &mut VirtioNetRuntime, frame: &[u8]) { + if frame.len() < 14 + 20 { + return; + } + let ip = &frame[14..]; + if (ip[0] >> 4) != 4 { + return; + } + let ihl = ((ip[0] & 0x0F) as usize) * 4; + if ihl < 20 || ip.len() < ihl + 8 { + return; + } + if ip[9] != IP_PROTO_ICMP { + return; + } + if ip[12..16] != GATEWAY_IP || ip[16..20] != LOCAL_IP { + return; + } + let icmp = &ip[ihl..]; + if icmp[0] != ICMP_ECHO_REPLY { + println!("[NETDRV] ICMP type={} code={}", icmp[0], icmp.get(1).copied().unwrap_or(0)); + return; + } + if icmp.len() < 8 { + return; + } + let id = u16::from_be_bytes([icmp[4], icmp[5]]); + let seq = u16::from_be_bytes([icmp[6], icmp[7]]); + if id == ICMP_ECHO_ID && seq == ICMP_ECHO_SEQ { + rt.ping_reply_seen = true; + println!("[NETDRV] ICMP echo reply received from 10.0.2.2"); + } +} + +fn handle_rx_frame(rt: &mut VirtioNetRuntime, frame: &[u8]) { + if frame.len() < 14 { + return; + } + let eth_type = u16::from_be_bytes([frame[12], frame[13]]); + match eth_type { + ETH_TYPE_ARP => handle_arp(rt, frame), + ETH_TYPE_IPV4 => handle_ipv4(rt, frame), + _ => {} + } +} + +fn drive_network(rt: &mut VirtioNetRuntime) { + if rt.ping_reply_seen { + return; + } + if rt.ticks % 100 == 0 { + println!( + "[NETDRV] state: arp_sent={} gw={} tx_inflight={} ping_sent={} pending={}", + rt.arp_sent, + rt.gateway_mac.is_some(), + rt.tx_inflight, + rt.ping_sent, + rt.ping_pending + ); + } + if !rt.arp_sent { + send_arp_request(rt); + return; + } + if let Some(gw_mac) = rt.gateway_mac { + if rt.tx_inflight { + if rt.ping_pending { + println!("[NETDRV] waiting to send ICMP: tx_inflight=true"); + } + return; + } + if rt.ping_pending { + try_send_pending_icmp(rt, gw_mac); + return; + } + if !rt.ping_sent || rt.ticks % 100 == 0 { + if rt.ping_sent { + println!("[NETDRV] ICMP retry"); + } + try_send_pending_icmp(rt, gw_mac); + } + } else if rt.ticks % 100 == 0 { + println!("[NETDRV] ARP retry"); + send_arp_request(rt); + } +} + +fn run_virtio_loop(mut rt: VirtioNetRuntime) { + println!( + "[NETDRV] runtime ready: rxq={} txq={} rx_pfn={:#x} tx_pfn={:#x}", + rt.rxq.size, + rt.txq.size, + rt.rxq.phys >> 12, + rt.txq.phys >> 12 + ); + loop { + rt.ticks = rt.ticks.wrapping_add(1); + poll_tx(&mut rt); + poll_rx(&mut rt); + drive_network(&mut rt); + time::sleep_ms(10); + } +} + +fn init_device(dev: NetDevice) { + println!( + "[NETDRV] net device {:02x}:{:02x}.{} vendor={:04x} device={:04x} kind={:?}", + dev.bdf.bus, + dev.bdf.device, + dev.bdf.function, + dev.vendor_id, + dev.device_id, + dev.kind + ); + + enable_device_command_bits(dev.bdf); + + match dev.kind { + NetKind::VirtioNet => { + println!("[NETDRV] virtio-net detected"); + if let Some(rt) = virtio_legacy_init_pio(dev) { + try_map_mmio_bar0(dev); + run_virtio_loop(rt); + } else { + println!("[NETDRV] virtio-net init failed"); + try_map_mmio_bar0(dev); + } + } + NetKind::E1000 => { + println!("[NETDRV] e1000 detected (phase1: probe only)"); + try_map_mmio_bar0(dev); + } + NetKind::Unknown => { + println!("[NETDRV] unknown NIC class device (phase1: probe only)"); + try_map_mmio_bar0(dev); + } + } +} + +fn main() { + println!("[NETDRV] network driver started"); + + let devices = find_network_devices(); + if devices.is_empty() { + println!("[NETDRV] no PCI network controller found"); + } else { + println!("[NETDRV] found {} network controller(s)", devices.len()); + for dev in devices { + init_device(dev); + } + } + + println!("[NETDRV] driver idle"); + loop { + time::sleep_ms(1000); + } +} diff --git a/src/drivers/net/src/main_refactored.rs b/src/drivers/net/src/main_refactored.rs new file mode 100644 index 0000000..ef0cd40 --- /dev/null +++ b/src/drivers/net/src/main_refactored.rs @@ -0,0 +1,63 @@ +mod net_common; +mod pci; +mod util; +mod virtio; + +use crate::net_common::{NetDevice, NetKind}; +use crate::pci::{find_network_devices, enable_device_command_bits, try_map_mmio_bar0}; +use crate::virtio::{virtio_legacy_init_pio, run_virtio_loop}; +use swiftlib::time; + +fn init_device(dev: NetDevice) { + println!( + "[NETDRV] net device {:02x}:{:02x}.{} vendor={:04x} device={:04x} kind={:?}", + dev.bdf.bus, + dev.bdf.device, + dev.bdf.function, + dev.vendor_id, + dev.device_id, + dev.kind + ); + + enable_device_command_bits(dev.bdf); + + match dev.kind { + NetKind::VirtioNet => { + println!("[NETDRV] virtio-net detected"); + if let Some(rt) = virtio_legacy_init_pio(dev) { + try_map_mmio_bar0(dev); + run_virtio_loop(rt); + } else { + println!("[NETDRV] virtio-net init failed"); + try_map_mmio_bar0(dev); + } + } + NetKind::E1000 => { + println!("[NETDRV] e1000 detected (phase1: probe only)"); + try_map_mmio_bar0(dev); + } + NetKind::Unknown => { + println!("[NETDRV] unknown NIC class device (phase1: probe only)"); + try_map_mmio_bar0(dev); + } + } +} + +fn main() { + println!("[NETDRV] network driver started"); + + let devices = find_network_devices(); + if devices.is_empty() { + println!("[NETDRV] no PCI network controller found"); + } else { + println!("[NETDRV] found {} network controller(s)", devices.len()); + for dev in devices { + init_device(dev); + } + } + + println!("[NETDRV] driver idle"); + loop { + time::sleep_ms(1000); + } +} diff --git a/src/drivers/net/src/net_common.rs b/src/drivers/net/src/net_common.rs new file mode 100644 index 0000000..9910f92 --- /dev/null +++ b/src/drivers/net/src/net_common.rs @@ -0,0 +1,74 @@ +use core::fmt::Debug; + +pub const PCI_CFG_ADDR_PORT: u16 = 0xCF8; +pub const PCI_CFG_DATA_PORT: u16 = 0xCFC; + +pub const PCI_COMMAND_IO: u16 = 1 << 0; +pub const PCI_COMMAND_MEM: u16 = 1 << 1; +pub const PCI_COMMAND_BUS_MASTER: u16 = 1 << 2; + +pub const CLASS_NETWORK: u8 = 0x02; + +pub const VIRTIO_NET_F_MAC: u32 = 5; +pub const VIRTIO_NET_F_STATUS: u32 = 16; + +pub const VIRTIO_PIO_DEVICE_FEATURES: u16 = 0x00; +pub const VIRTIO_PIO_GUEST_FEATURES: u16 = 0x04; +pub const VIRTIO_PIO_QUEUE_ADDR_PFN: u16 = 0x08; +pub const VIRTIO_PIO_QUEUE_SIZE: u16 = 0x0C; +pub const VIRTIO_PIO_QUEUE_SELECT: u16 = 0x0E; +pub const VIRTIO_PIO_QUEUE_NOTIFY: u16 = 0x10; +pub const VIRTIO_PIO_DEVICE_STATUS: u16 = 0x12; +pub const VIRTIO_PIO_ISR_STATUS: u16 = 0x13; +pub const VIRTIO_PIO_DEVICE_CONFIG: u16 = 0x14; + +pub const VIRTIO_STATUS_ACKNOWLEDGE: u8 = 1 << 0; +pub const VIRTIO_STATUS_DRIVER: u8 = 1 << 1; +pub const VIRTIO_STATUS_DRIVER_OK: u8 = 1 << 2; + +pub const VIRTIO_QUEUE_RX: u16 = 0; +pub const VIRTIO_QUEUE_TX: u16 = 1; +pub const PAGE_SIZE: usize = 4096; +pub const RX_BUFFER_LEN: u32 = 2048; +pub const RX_BUFFER_COUNT: usize = 32; + +pub const ETH_TYPE_ARP: u16 = 0x0806; +pub const ETH_TYPE_IPV4: u16 = 0x0800; +pub const ARP_OP_REQUEST: u16 = 1; +pub const ARP_OP_REPLY: u16 = 2; +pub const IP_PROTO_ICMP: u8 = 1; +pub const ICMP_ECHO_REQUEST: u8 = 8; +pub const ICMP_ECHO_REPLY: u8 = 0; +pub const ICMP_ECHO_ID: u16 = 0x1337; +pub const ICMP_ECHO_SEQ: u16 = 1; + +pub const GATEWAY_IP: [u8; 4] = [10, 0, 2, 2]; +pub const LOCAL_IP: [u8; 4] = [10, 0, 2, 15]; + +pub const VRING_DESC_F_WRITE: u16 = 2; +pub const VIRTIO_NET_HDR_LEN: usize = 10; +pub const RX_POLL_BUDGET: usize = 64; +pub const TX_POLL_BUDGET: usize = 64; + +#[derive(Clone, Copy, Debug)] +pub struct PciBdf { + pub bus: u8, + pub device: u8, + pub function: u8, +} + +#[derive(Clone, Copy, Debug)] +pub enum NetKind { + VirtioNet, + E1000, + Unknown, +} + +#[derive(Clone, Copy, Debug)] +pub struct NetDevice { + pub bdf: PciBdf, + pub vendor_id: u16, + pub device_id: u16, + pub kind: NetKind, + pub bar0: u32, +} diff --git a/src/drivers/net/src/pci.rs b/src/drivers/net/src/pci.rs new file mode 100644 index 0000000..7b38cd5 --- /dev/null +++ b/src/drivers/net/src/pci.rs @@ -0,0 +1,149 @@ +use crate::net_common::*; +use swiftlib::{mmio, port}; + +fn pci_config_address(bdf: PciBdf, offset: u8) -> u32 { + 0x8000_0000 + | ((bdf.bus as u32) << 16) + | ((bdf.device as u32) << 11) + | ((bdf.function as u32) << 8) + | (u32::from(offset) & 0xFC) +} + +fn pci_read_u32(bdf: PciBdf, offset: u8) -> u32 { + let addr = pci_config_address(bdf, offset); + port::outl(PCI_CFG_ADDR_PORT, addr); + port::inl(PCI_CFG_DATA_PORT) +} + +fn pci_read_u16(bdf: PciBdf, offset: u8) -> u16 { + let aligned = offset & 0xFC; + let shift = u32::from(offset & 0x02) * 8; + ((pci_read_u32(bdf, aligned) >> shift) & 0xFFFF) as u16 +} + +fn pci_write_u16(bdf: PciBdf, offset: u8, value: u16) { + let aligned = offset & 0xFC; + let mut reg = pci_read_u32(bdf, aligned); + let shift = u32::from(offset & 0x02) * 8; + reg &= !(0xFFFFu32 << shift); + reg |= u32::from(value) << shift; + let addr = pci_config_address(bdf, aligned); + port::outl(PCI_CFG_ADDR_PORT, addr); + port::outl(PCI_CFG_DATA_PORT, reg); +} + +fn pci_function_exists(bdf: PciBdf) -> bool { + pci_read_u16(bdf, 0x00) != 0xFFFF +} + +fn classify_net_device(vendor_id: u16, device_id: u16) -> NetKind { + match (vendor_id, device_id) { + (0x1AF4, 0x1000) => NetKind::VirtioNet, + (0x1AF4, 0x1041) => NetKind::VirtioNet, + (0x8086, 0x100E) | (0x8086, 0x100F) | (0x8086, 0x10D3) => NetKind::E1000, + _ => NetKind::Unknown, + } +} + +pub fn enable_device_command_bits(bdf: PciBdf) { + let mut command = pci_read_u16(bdf, 0x04); + command |= PCI_COMMAND_IO | PCI_COMMAND_MEM | PCI_COMMAND_BUS_MASTER; + pci_write_u16(bdf, 0x04, command); +} + +pub fn find_network_devices() -> Vec { + let mut devices = Vec::new(); + + for bus in 0u16..=255 { + for device in 0u16..32 { + let bdf0 = PciBdf { + bus: bus as u8, + device: device as u8, + function: 0, + }; + if !pci_function_exists(bdf0) { + continue; + } + + let header = pci_read_u32(bdf0, 0x0C); + let header_type = ((header >> 16) & 0xFF) as u8; + let function_count = if (header_type & 0x80) != 0 { 8 } else { 1 }; + + for function in 0..function_count { + let bdf = PciBdf { + bus: bus as u8, + device: device as u8, + function: function as u8, + }; + if !pci_function_exists(bdf) { + continue; + } + + let class_reg = pci_read_u32(bdf, 0x08); + let class_code = ((class_reg >> 24) & 0xFF) as u8; + if class_code != CLASS_NETWORK { + continue; + } + + let vendor_device = pci_read_u32(bdf, 0x00); + let vendor_id = (vendor_device & 0xFFFF) as u16; + let device_id = ((vendor_device >> 16) & 0xFFFF) as u16; + let bar0 = pci_read_u32(bdf, 0x10); + let kind = classify_net_device(vendor_id, device_id); + + devices.push(NetDevice { + bdf, + vendor_id, + device_id, + kind, + bar0, + }); + } + } + } + + devices +} + +pub fn try_map_mmio_bar0(dev: NetDevice) { + if (dev.bar0 & 0x1) != 0 { + if let NetKind::VirtioNet = dev.kind { + println!("[NETDRV] BAR0 is I/O space (legacy virtio-net PIO)"); + } else { + println!("[NETDRV] BAR0 is I/O space (PIO), MMIO map skipped"); + } + return; + } + + let mmio_base = u64::from(dev.bar0 & 0xFFFF_FFF0); + if mmio_base == 0 { + println!("[NETDRV] BAR0 MMIO base is zero"); + return; + } + + match mmio::map_physical(mmio_base, 0x1000) { + Ok(mapped) => { + println!( + "[NETDRV] MMIO mapped phys={:#x} -> virt={:#x}", + mmio_base, mapped as u64 + ); + } + Err(errno) => { + println!( + "[NETDRV] MMIO map failed phys={:#x}, errno={}", + mmio_base, errno + ); + } + } +} + +pub fn virtio_pio_base(bar0: u32) -> Option { + if (bar0 & 0x1) == 0 { + return None; + } + let base = bar0 & 0xFFFF_FFFC; + if base == 0 || base > 0xFFFF { + return None; + } + Some(base as u16) +} diff --git a/src/drivers/net/src/util.rs b/src/drivers/net/src/util.rs new file mode 100644 index 0000000..3e46379 --- /dev/null +++ b/src/drivers/net/src/util.rs @@ -0,0 +1,160 @@ +use crate::net_common::*; +use core::ptr::{read_volatile, write_volatile}; +use core::sync::atomic::{compiler_fence, Ordering as AtomicOrdering}; +use swiftlib::{privileged, task}; + +pub fn align_up(value: usize, align: usize) -> usize { + if align == 0 { + return value; + } + (value + (align - 1)) & !(align - 1) +} + +pub fn compute_virtqueue_bytes(queue_size: usize) -> usize { + let desc_bytes = 16usize.saturating_mul(queue_size); + let avail_bytes = 6usize.saturating_add(2usize.saturating_mul(queue_size)); + let used_bytes = 6usize.saturating_add(8usize.saturating_mul(queue_size)); + let used_off = align_up(desc_bytes.saturating_add(avail_bytes), PAGE_SIZE); + used_off.saturating_add(used_bytes) +} + +pub fn is_syscall_error(value: u64) -> bool { + (-4095..=-1).contains(&(value as i64)) +} + +#[derive(Clone, Copy)] +pub struct SharedBuf { + pub virt: *mut u8, + pub phys: u64, + pub len: u32, +} + +pub fn alloc_shared_buf(len: u32) -> Option { + let page_count = align_up(len as usize, PAGE_SIZE) / PAGE_SIZE; + let mut phys_addrs = vec![0u64; page_count]; + let virt = unsafe { privileged::alloc_shared_pages(page_count as u64, Some(&mut phys_addrs), 0) }; + if is_syscall_error(virt) { + println!("[NETDRV] alloc_shared_pages(buf) failed: errno={}", virt as i64); + return None; + } + + if phys_addrs.is_empty() { + return None; + } + + Some(SharedBuf { + virt: virt as *mut u8, + phys: phys_addrs[0], + len, + }) +} + +pub fn alloc_phys_contiguous(bytes: usize) -> Option<(u64, *mut u8)> { + #[derive(Clone, Copy)] + struct PageAlloc { + virt: u64, + phys: u64, + } + + let page_count = align_up(bytes, PAGE_SIZE) / PAGE_SIZE; + let required_run = page_count; + let max_probe_pages = 64usize; + let mut pool: Vec = Vec::new(); + + for _ in 0..max_probe_pages { + let mut phys_buf = [0u64; 1]; + let virt = unsafe { privileged::alloc_shared_pages(1, Some(&mut phys_buf), 0) }; + if is_syscall_error(virt) { + println!("[NETDRV] alloc_shared_pages failed: errno={}", virt as i64); + break; + } + pool.push(PageAlloc { + virt, + phys: phys_buf[0], + }); + + if pool.len() < required_run { + continue; + } + + let mut phys_sorted: Vec = pool.iter().map(|p| p.phys).collect(); + phys_sorted.sort_unstable(); + phys_sorted.dedup(); + + for start_idx in 0..=phys_sorted.len().saturating_sub(required_run) { + let start_phys = phys_sorted[start_idx]; + let mut contiguous = true; + for step in 1..required_run { + let expected = start_phys + (step as u64 * PAGE_SIZE as u64); + if phys_sorted[start_idx + step] != expected { + contiguous = false; + break; + } + } + if !contiguous { + continue; + } + + let selected_phys: Vec = (0..required_run) + .map(|step| start_phys + (step as u64 * PAGE_SIZE as u64)) + .collect(); + + let queue_virt = unsafe { + privileged::map_physical_pages(task::gettid(), &selected_phys, 0) + }; + if is_syscall_error(queue_virt) { + println!( + "[NETDRV] map_physical_pages failed: errno={}", + queue_virt as i64 + ); + continue; + } + + for page in &pool { + let is_selected = selected_phys.iter().any(|&p| p == page.phys); + let rc = privileged::unmap_pages(page.virt, 1, !is_selected); + if rc != 0 { + println!( + "[NETDRV] unmap_pages failed virt={:#x} rc={}", + page.virt, rc as i64 + ); + } + } + + return Some((start_phys, queue_virt as *mut u8)); + } + } + + for page in &pool { + let rc = privileged::unmap_pages(page.virt, 1, true); + if rc != 0 { + println!( + "[NETDRV] unmap_pages cleanup failed virt={:#x} rc={}", + page.virt, rc as i64 + ); + } + } + + None +} + +pub fn write_be16(dst: &mut [u8], value: u16) { + dst[0] = (value >> 8) as u8; + dst[1] = value as u8; +} + +pub fn checksum16(data: &[u8]) -> u16 { + let mut sum: u32 = 0; + let mut i = 0usize; + while i + 1 < data.len() { + sum = sum.wrapping_add(u16::from_be_bytes([data[i], data[i + 1]]) as u32); + i += 2; + } + if i < data.len() { + sum = sum.wrapping_add((data[i] as u32) << 8); + } + while (sum >> 16) != 0 { + sum = (sum & 0xFFFF) + (sum >> 16); + } + !(sum as u16) +} diff --git a/src/drivers/net/src/virtio.rs b/src/drivers/net/src/virtio.rs new file mode 100644 index 0000000..4489b17 --- /dev/null +++ b/src/drivers/net/src/virtio.rs @@ -0,0 +1,558 @@ +use crate::net_common::*; +use crate::util::*; +use core::ptr::{read_volatile, write_volatile}; +use core::sync::atomic::{compiler_fence, Ordering as AtomicOrdering}; +use swiftlib::{ipc, mmio, port, privileged, task, time}; + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct VringDesc { + pub addr: u64, + pub len: u32, + pub flags: u16, + pub next: u16, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct VringUsedElem { + pub id: u32, + pub len: u32, +} + +pub struct VirtQueue { + pub index: u16, + pub size: u16, + pub base: *mut u8, + pub phys: u64, + pub avail_ring_off: usize, + pub used_ring_off: usize, + pub next_avail_idx: u16, + pub last_used_idx: u16, +} + +impl VirtQueue { + fn desc_ptr(&self, idx: u16) -> *mut VringDesc { + unsafe { + self.base + .add(idx as usize * core::mem::size_of::()) as *mut VringDesc + } + } + + fn avail_idx_ptr(&self) -> *mut u16 { + unsafe { self.base.add(self.avail_ring_off + 2) as *mut u16 } + } + + fn avail_ring_entry_ptr(&self, slot: usize) -> *mut u16 { + unsafe { self.base.add(self.avail_ring_off + 4 + slot * 2) as *mut u16 } + } + + fn used_idx(&self) -> u16 { + unsafe { read_volatile(self.base.add(self.used_ring_off + 2) as *const u16) } + } + + fn used_elem(&self, slot: usize) -> VringUsedElem { + unsafe { + read_volatile( + self.base + .add(self.used_ring_off + 4 + slot * core::mem::size_of::()) + as *const VringUsedElem, + ) + } + } +} + +pub struct VirtioNetRuntime { + pub base: u16, + pub mac: [u8; 6], + pub rxq: VirtQueue, + pub txq: VirtQueue, + pub rx_bufs: Vec, + pub tx_buf: SharedBuf, + pub tx_inflight: bool, + pub arp_sent: bool, + pub gateway_mac: Option<[u8; 6]>, + pub ping_sent: bool, + pub ping_reply_seen: bool, + pub ping_pending: bool, + pub ticks: u64, + pub listeners: Vec, // thread IDs registered to receive incoming frames +} + +pub fn setup_virtio_legacy_queue(base: u16, queue_index: u16) -> Option { + port::outw(base + VIRTIO_PIO_QUEUE_SELECT, queue_index); + let queue_size = port::inw(base + VIRTIO_PIO_QUEUE_SIZE); + if queue_size == 0 { + println!("[NETDRV] queue {} not available", queue_index); + return None; + } + + let bytes = compute_virtqueue_bytes(queue_size as usize); + let Some((phys, virt)) = alloc_phys_contiguous(bytes) else { + println!( + "[NETDRV] queue {} allocation failed (size={} bytes)", + queue_index, bytes + ); + return None; + }; + + let aligned = align_up(bytes, PAGE_SIZE); + unsafe { + core::ptr::write_bytes(virt, 0, aligned); + } + + let pfn = (phys >> 12) as u32; + port::outl(base + VIRTIO_PIO_QUEUE_ADDR_PFN, pfn); + let programmed = port::inl(base + VIRTIO_PIO_QUEUE_ADDR_PFN); + if programmed != pfn { + println!( + "[NETDRV] queue {} PFN mismatch: wrote={:#x} read={:#x}", + queue_index, pfn, programmed + ); + return None; + } + + let avail_ring_off = queue_size as usize * core::mem::size_of::(); + let used_ring_off = align_up(avail_ring_off + 6 + (queue_size as usize * 2), PAGE_SIZE); + + println!( + "[NETDRV] queue {} ready size={} bytes={} pfn={:#x}", + queue_index, queue_size, bytes, pfn + ); + + Some(VirtQueue { + index: queue_index, + size: queue_size, + base: virt, + phys, + avail_ring_off, + used_ring_off, + next_avail_idx: 0, + last_used_idx: 0, + }) +} + +pub fn enqueue_desc_to_avail(queue: &mut VirtQueue, desc_id: u16) { + let slot = (queue.next_avail_idx % queue.size) as usize; + unsafe { + write_volatile(queue.avail_ring_entry_ptr(slot), desc_id); + } + compiler_fence(AtomicOrdering::SeqCst); + queue.next_avail_idx = queue.next_avail_idx.wrapping_add(1); + unsafe { + write_volatile(queue.avail_idx_ptr(), queue.next_avail_idx); + } +} + +pub fn populate_rx_ring(rt: &mut VirtioNetRuntime) -> bool { + let target_count = core::cmp::min(RX_BUFFER_COUNT, rt.rxq.size as usize); + for desc_id in 0..target_count { + let Some(buf) = alloc_shared_buf(RX_BUFFER_LEN) else { + println!("[NETDRV] rx buffer allocation failed at {}", desc_id); + return false; + }; + rt.rx_bufs.push(buf); + let desc = VringDesc { + addr: buf.phys, + len: buf.len, + flags: VRING_DESC_F_WRITE, + next: 0, + }; + unsafe { + write_volatile(rt.rxq.desc_ptr(desc_id as u16), desc); + } + enqueue_desc_to_avail(&mut rt.rxq, desc_id as u16); + } + port::outw(rt.base + VIRTIO_PIO_QUEUE_NOTIFY, VIRTIO_QUEUE_RX); + println!("[NETDRV] RX ring primed with {} buffers", target_count); + true +} + +pub fn poll_tx(rt: &mut VirtioNetRuntime) { + let used = rt.txq.used_idx(); + let mut processed = 0usize; + while rt.txq.last_used_idx != used && processed < TX_POLL_BUDGET { + let slot = (rt.txq.last_used_idx % rt.txq.size) as usize; + let elem = rt.txq.used_elem(slot); + println!("[NETDRV] TX complete: desc={} len={}", elem.id, elem.len); + rt.txq.last_used_idx = rt.txq.last_used_idx.wrapping_add(1); + rt.tx_inflight = false; + processed += 1; + } +} + +pub fn poll_rx(rt: &mut VirtioNetRuntime) { + let used = rt.rxq.used_idx(); + let mut recycled = 0usize; + let mut processed = 0usize; + while rt.rxq.last_used_idx != used && processed < RX_POLL_BUDGET { + let slot = (rt.rxq.last_used_idx % rt.rxq.size) as usize; + let elem = rt.rxq.used_elem(slot); + let desc_id = elem.id as usize; + if desc_id < rt.rx_bufs.len() { + let frame_total = elem.len as usize; + if frame_total > VIRTIO_NET_HDR_LEN { + let frame_len = frame_total - VIRTIO_NET_HDR_LEN; + let frame_ptr = unsafe { rt.rx_bufs[desc_id].virt.add(VIRTIO_NET_HDR_LEN) }; + let frame = + unsafe { core::slice::from_raw_parts(frame_ptr as *const u8, frame_len) }; + handle_rx_frame(rt, frame); + } + enqueue_desc_to_avail(&mut rt.rxq, desc_id as u16); + recycled = recycled.saturating_add(1); + } else { + println!( + "[NETDRV] RX used elem out of range: desc={} len={}", + elem.id, elem.len + ); + } + rt.rxq.last_used_idx = rt.rxq.last_used_idx.wrapping_add(1); + processed += 1; + } + + if recycled > 0 { + port::outw(rt.base + VIRTIO_PIO_QUEUE_NOTIFY, VIRTIO_QUEUE_RX); + } + if processed == RX_POLL_BUDGET { + println!("[NETDRV] RX poll budget reached"); + } +} + +pub fn queue_tx_frame(rt: &mut VirtioNetRuntime, frame: &[u8]) -> bool { + if rt.tx_inflight { + println!("[NETDRV] TX busy, frame deferred"); + return false; + } + let wire_frame_len = core::cmp::max(frame.len(), 60); + let needed = VIRTIO_NET_HDR_LEN + wire_frame_len; + if needed > rt.tx_buf.len as usize { + println!("[NETDRV] TX frame too large: {}", frame.len()); + return false; + } + + let tx_region = unsafe { core::slice::from_raw_parts_mut(rt.tx_buf.virt, needed) }; + tx_region[..VIRTIO_NET_HDR_LEN].fill(0); + tx_region[VIRTIO_NET_HDR_LEN..].fill(0); + tx_region[VIRTIO_NET_HDR_LEN..(VIRTIO_NET_HDR_LEN + frame.len())].copy_from_slice(frame); + + let desc = VringDesc { + addr: rt.tx_buf.phys, + len: needed as u32, + flags: 0, + next: 0, + }; + unsafe { + write_volatile(rt.txq.desc_ptr(0), desc); + } + enqueue_desc_to_avail(&mut rt.txq, 0); + port::outw(rt.base + VIRTIO_PIO_QUEUE_NOTIFY, VIRTIO_QUEUE_TX); + rt.tx_inflight = true; + true +} + +pub fn send_arp_request(rt: &mut VirtioNetRuntime) { + let mut frame = [0u8; 42]; + frame[0..6].fill(0xFF); + frame[6..12].copy_from_slice(&rt.mac); + write_be16(&mut frame[12..14], ETH_TYPE_ARP); + write_be16(&mut frame[14..16], 1); + write_be16(&mut frame[16..18], ETH_TYPE_IPV4); + frame[18] = 6; + frame[19] = 4; + write_be16(&mut frame[20..22], ARP_OP_REQUEST); + frame[22..28].copy_from_slice(&rt.mac); + frame[28..32].copy_from_slice(&LOCAL_IP); + frame[32..38].fill(0); + frame[38..42].copy_from_slice(&GATEWAY_IP); + + if queue_tx_frame(rt, &frame) { + rt.arp_sent = true; + println!("[NETDRV] ARP who-has 10.0.2.2 sent"); + } +} + +pub fn send_icmp_echo(rt: &mut VirtioNetRuntime, dst_mac: [u8; 6]) { + println!( + "[NETDRV] send_icmp_echo: tx_inflight={} ping_sent={} pending={}", + rt.tx_inflight, rt.ping_sent, rt.ping_pending + ); + let payload = b"mochios-net"; + let ip_total_len = (20 + 8 + payload.len()) as u16; + let mut frame = [0u8; 128]; + + frame[0..6].copy_from_slice(&dst_mac); + frame[6..12].copy_from_slice(&rt.mac); + write_be16(&mut frame[12..14], ETH_TYPE_IPV4); + + let ip = &mut frame[14..34]; + ip.fill(0); + ip[0] = 0x45; + write_be16(&mut ip[2..4], ip_total_len); + write_be16(&mut ip[4..6], 0x1234); + ip[8] = 64; + ip[9] = IP_PROTO_ICMP; + ip[12..16].copy_from_slice(&LOCAL_IP); + ip[16..20].copy_from_slice(&GATEWAY_IP); + let ip_csum = checksum16(ip); + write_be16(&mut ip[10..12], ip_csum); + + let icmp_len = 8 + payload.len(); + let icmp = &mut frame[34..(34 + icmp_len)]; + icmp.fill(0); + icmp[0] = ICMP_ECHO_REQUEST; + write_be16(&mut icmp[4..6], ICMP_ECHO_ID); + write_be16(&mut icmp[6..8], ICMP_ECHO_SEQ); + icmp[8..].copy_from_slice(payload); + let icmp_csum = checksum16(icmp); + write_be16(&mut icmp[2..4], icmp_csum); + + let frame_len = 14 + ip_total_len as usize; + if queue_tx_frame(rt, &frame[..frame_len]) { + rt.ping_sent = true; + println!("[NETDRV] ICMP echo request sent to 10.0.2.2"); + } else { + println!("[NETDRV] ICMP enqueue failed"); + } +} + +pub fn try_send_pending_icmp(rt: &mut VirtioNetRuntime, dst_mac: [u8; 6]) { + println!( + "[NETDRV] try_send_pending_icmp: tx_inflight={} ping_sent={} pending={}", + rt.tx_inflight, rt.ping_sent, rt.ping_pending + ); + if rt.tx_inflight { + rt.ping_pending = true; + return; + } + send_icmp_echo(rt, dst_mac); + if !rt.tx_inflight { + rt.ping_pending = true; + } else { + rt.ping_pending = false; + } +} + +pub fn handle_arp(rt: &mut VirtioNetRuntime, frame: &[u8]) { + if frame.len() < 42 { + return; + } + let arp = &frame[14..42]; + let op = u16::from_be_bytes([arp[6], arp[7]]); + let sender_mac = [arp[8], arp[9], arp[10], arp[11], arp[12], arp[13]]; + let sender_ip = [arp[14], arp[15], arp[16], arp[17]]; + let target_ip = [arp[24], arp[25], arp[26], arp[27]]; + if op == ARP_OP_REPLY && sender_ip == GATEWAY_IP && target_ip == LOCAL_IP { + rt.gateway_mac = Some(sender_mac); + rt.ping_pending = true; + println!( + "[NETDRV] ARP reply: gateway MAC {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + sender_mac[0], + sender_mac[1], + sender_mac[2], + sender_mac[3], + sender_mac[4], + sender_mac[5] + ); + if rt.tx_inflight { + println!("[NETDRV] ARP learned but TX busy, defer ICMP"); + rt.ping_pending = true; + } else { + println!("[NETDRV] ARP learned, send ICMP now"); + send_icmp_echo(rt, sender_mac); + if rt.tx_inflight { + rt.ping_pending = false; + } + } + } else if op == ARP_OP_REPLY { + println!( + "[NETDRV] ARP reply ignored: sender={}.{}.{}.{} target={}.{}.{}.{}", + sender_ip[0], + sender_ip[1], + sender_ip[2], + sender_ip[3], + target_ip[0], + target_ip[1], + target_ip[2], + target_ip[3] + ); + } +} + +pub fn handle_ipv4(rt: &mut VirtioNetRuntime, frame: &[u8]) { + if frame.len() < 14 + 20 { + return; + } + let ip = &frame[14..]; + if (ip[0] >> 4) != 4 { + return; + } + let ihl = ((ip[0] & 0x0F) as usize) * 4; + if ihl < 20 || ip.len() < ihl + 8 { + return; + } + if ip[9] != IP_PROTO_ICMP { + return; + } + if ip[12..16] != GATEWAY_IP || ip[16..20] != LOCAL_IP { + return; + } + let icmp = &ip[ihl..]; + if icmp[0] != ICMP_ECHO_REPLY { + println!( + "[NETDRV] ICMP type={} code={}", + icmp[0], + icmp.get(1).copied().unwrap_or(0) + ); + return; + } + if icmp.len() < 8 { + return; + } + let id = u16::from_be_bytes([icmp[4], icmp[5]]); + let seq = u16::from_be_bytes([icmp[6], icmp[7]]); + if id == ICMP_ECHO_ID && seq == ICMP_ECHO_SEQ { + rt.ping_reply_seen = true; + println!("[NETDRV] ICMP echo reply received from 10.0.2.2"); + } +} + +pub fn handle_rx_frame(rt: &mut VirtioNetRuntime, frame: &[u8]) { + if frame.len() < 14 { + return; + } + let eth_type = u16::from_be_bytes([frame[12], frame[13]]); + match eth_type { + ETH_TYPE_ARP => handle_arp(rt, frame), + ETH_TYPE_IPV4 => handle_ipv4(rt, frame), + _ => {} + } +} + +pub fn drive_network(rt: &mut VirtioNetRuntime) { + if rt.ping_reply_seen { + return; + } + if rt.ticks % 100 == 0 { + println!( + "[NETDRV] state: arp_sent={} gw={} tx_inflight={} ping_sent={} pending={}", + rt.arp_sent, + rt.gateway_mac.is_some(), + rt.tx_inflight, + rt.ping_sent, + rt.ping_pending + ); + } + if !rt.arp_sent { + send_arp_request(rt); + return; + } + if let Some(gw_mac) = rt.gateway_mac { + if rt.tx_inflight { + if rt.ping_pending { + println!("[NETDRV] waiting to send ICMP: tx_inflight=true"); + } + return; + } + if rt.ping_pending { + try_send_pending_icmp(rt, gw_mac); + return; + } + if !rt.ping_sent || rt.ticks % 100 == 0 { + if rt.ping_sent { + println!("[NETDRV] ICMP retry"); + } + try_send_pending_icmp(rt, gw_mac); + } + } else if rt.ticks % 100 == 0 { + println!("[NETDRV] ARP retry"); + send_arp_request(rt); + } +} + +pub fn run_virtio_loop(mut rt: VirtioNetRuntime) { + println!( + "[NETDRV] runtime ready: rxq={} txq={} rx_pfn={:#x} tx_pfn={:#x}", + rt.rxq.size, + rt.txq.size, + rt.rxq.phys >> 12, + rt.txq.phys >> 12 + ); + loop { + rt.ticks = rt.ticks.wrapping_add(1); + poll_tx(&mut rt); + poll_rx(&mut rt); + drive_network(&mut rt); + time::sleep_ms(10); + } +} + +pub fn virtio_legacy_init_pio(dev: crate::net_common::NetDevice) -> Option { + let Some(base) = crate::pci::virtio_pio_base(dev.bar0) else { + println!("[NETDRV] virtio-net BAR0 is not legacy PIO"); + return None; + }; + + let device_features = port::inl(base + VIRTIO_PIO_DEVICE_FEATURES); + let guest_features = + device_features & ((1u32 << VIRTIO_NET_F_MAC) | (1u32 << VIRTIO_NET_F_STATUS)); + println!("[NETDRV] virtio legacy PIO base={:#x}", base); + println!("[NETDRV] virtio device_features={:#010x}", device_features); + println!("[NETDRV] virtio guest_features ={:#010x}", guest_features); + + port::outb(base + VIRTIO_PIO_DEVICE_STATUS, 0); + port::outb( + base + VIRTIO_PIO_DEVICE_STATUS, + VIRTIO_STATUS_ACKNOWLEDGE | VIRTIO_STATUS_DRIVER, + ); + port::outl(base + VIRTIO_PIO_GUEST_FEATURES, guest_features); + port::outb( + base + VIRTIO_PIO_DEVICE_STATUS, + VIRTIO_STATUS_ACKNOWLEDGE | VIRTIO_STATUS_DRIVER | VIRTIO_STATUS_DRIVER_OK, + ); + + let status = port::inb(base + VIRTIO_PIO_DEVICE_STATUS); + println!("[NETDRV] virtio status={:#04x}", status); + + let mut mac = [0u8; 6]; + if (guest_features & (1u32 << VIRTIO_NET_F_MAC)) != 0 { + for (i, byte) in mac.iter_mut().enumerate() { + *byte = port::inb(base + VIRTIO_PIO_DEVICE_CONFIG + i as u16); + } + println!( + "[NETDRV] virtio MAC {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] + ); + } else { + println!("[NETDRV] virtio MAC feature not advertised"); + } + + let isr = port::inb(base + VIRTIO_PIO_ISR_STATUS); + println!("[NETDRV] virtio isr={:#04x}", isr); + + let rxq = setup_virtio_legacy_queue(base, VIRTIO_QUEUE_RX)?; + let txq = setup_virtio_legacy_queue(base, VIRTIO_QUEUE_TX)?; + let tx_buf = alloc_shared_buf(PAGE_SIZE as u32)?; + + let mut rt = VirtioNetRuntime { + base, + mac, + rxq, + txq, + rx_bufs: Vec::new(), + tx_buf, + tx_inflight: false, + arp_sent: false, + gateway_mac: None, + ping_sent: false, + ping_reply_seen: false, + ping_pending: false, + ticks: 0, + listeners: Vec::new(), + }; + + if !populate_rx_ring(&mut rt) { + return None; + } + + Some(rt) +} diff --git a/src/drivers/usb/.cargo/config.toml b/src/drivers/usb/.cargo/config.toml new file mode 100644 index 0000000..a177f26 --- /dev/null +++ b/src/drivers/usb/.cargo/config.toml @@ -0,0 +1,11 @@ +[build] +target = "../../x86_64-mochios.json" + +[unstable] +build-std = ["std", "panic_abort"] +json-target-spec = true + +[target.x86_64-mochios] +rustflags = [ + "-C", "link-arg=-nostdlib", +] diff --git a/src/drivers/usb/Cargo.toml b/src/drivers/usb/Cargo.toml new file mode 100644 index 0000000..f9cc731 --- /dev/null +++ b/src/drivers/usb/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "xhci" +version = "0.1.0" +edition = "2021" + +[[bin]] +# driver.service から起動されるユーザー空間ドライバー実行ファイル +name = "xhci" +path = "src/main.rs" +test = false +bench = false + +[dependencies] +# ユーザー空間ドライバーのため swiftlib(std-support) を利用する +swiftlib = { path = "../../user", features = ["std-support"] } + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +opt-level = "z" +lto = true diff --git a/src/drivers/usb/build.rs b/src/drivers/usb/build.rs new file mode 100644 index 0000000..d99a552 --- /dev/null +++ b/src/drivers/usb/build.rs @@ -0,0 +1,108 @@ +use std::env; +use std::path::{Path, PathBuf}; + +fn cargo_toml_has_workspace(path: &Path) -> bool { + std::fs::read_to_string(path) + .map(|s| { + s.lines() + .map(|line| line.trim()) + .any(|line| line == "[workspace]") + }) + .unwrap_or(false) +} + +fn find_project_root(manifest_dir: &Path) -> PathBuf { + if let Ok(workspace_dir) = env::var("CARGO_WORKSPACE_DIR") { + return PathBuf::from(workspace_dir); + } + + for ancestor in manifest_dir.ancestors().skip(1) { + if ancestor.join("ramfs").join("lib").exists() { + return ancestor.to_path_buf(); + } + } + + for ancestor in manifest_dir.ancestors().skip(1) { + let cargo_toml = ancestor.join("Cargo.toml"); + if cargo_toml.exists() && cargo_toml_has_workspace(&cargo_toml) { + return ancestor.to_path_buf(); + } + } + + manifest_dir.to_path_buf() +} + +fn main() { + // Skip when building host PoC + if std::env::var("MOCHI_HOST_POC").is_ok() { + println!("cargo:warning=MOCHI_HOST_POC set; skipping mochiOS linker flags in drivers/usb/build.rs"); + return; + } + + let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"); + let manifest_path = Path::new(&manifest_dir); + let project_root = find_project_root(manifest_path); + + let libs_dir = project_root.join("ramfs").join("lib"); + + // ライブラリ検索パスを追加 + println!("cargo:rustc-link-search=native={}", libs_dir.display()); + + // crt0.o をリンク + println!("cargo:rustc-link-arg={}/crt0.o", libs_dir.display()); + + // 静的リンクを指定し、PIEを無効化する + println!("cargo:rustc-link-arg=-static"); + println!("cargo:rustc-link-arg=-no-pie"); + + // カスタムリンカースクリプトを使用してロードアドレスを0x800000に設定 + println!("cargo:rustc-link-arg=-T{}/linker.ld", manifest_dir); + println!("cargo:rerun-if-changed={}", manifest_path.join("linker.ld").display()); + + // 重複シンボルを許可(最初に見つかったものを使用) + println!("cargo:rustc-link-arg=--allow-multiple-definition"); + + // ライブラリをリンク + println!("cargo:rustc-link-lib=static=c"); // libc.a + println!("cargo:rustc-link-lib=static=g"); // libg.a + println!("cargo:rustc-link-lib=static=m"); // libm.a + + // std の unwind クレートが libgcc_s を要求するため libg.a を libgcc_s.a として提供 + let libgcc_s = libs_dir.join("libgcc_s.a"); + let libg = libs_dir.join("libg.a"); + if !libgcc_s.exists() && libg.exists() { + let tmp_name = format!( + "libgcc_s.a.tmp.{}.{}", + std::process::id(), + std::thread::current().name().unwrap_or("usb-build") + ); + let libgcc_tmp = libs_dir.join(tmp_name); + if let Err(err) = std::fs::copy(&libg, &libgcc_tmp) { + panic!( + "failed to copy {} to {} for static gcc_s linking: {}", + libg.display(), + libgcc_tmp.display(), + err + ); + } + if let Err(err) = std::fs::rename(&libgcc_tmp, &libgcc_s) { + if libgcc_s.exists() { + let _ = std::fs::remove_file(&libgcc_tmp); + } else { + let _ = std::fs::remove_file(&libgcc_tmp); + panic!( + "failed to rename {} to {} for static gcc_s linking: {}", + libgcc_tmp.display(), + libgcc_s.display(), + err + ); + } + } + } + println!("cargo:rustc-link-lib=static=gcc_s"); + + println!("cargo:rerun-if-changed={}", libs_dir.join("libc.a").display()); + println!("cargo:rerun-if-changed={}", libs_dir.join("libg.a").display()); + println!("cargo:rerun-if-changed={}", libs_dir.join("libm.a").display()); +} + diff --git a/src/drivers/usb/linker.ld b/src/drivers/usb/linker.ld new file mode 100644 index 0000000..f502ec1 --- /dev/null +++ b/src/drivers/usb/linker.ld @@ -0,0 +1,33 @@ +OUTPUT_FORMAT("elf64-x86-64") +OUTPUT_ARCH(i386:x86-64) +ENTRY(_start) + +SECTIONS +{ + . = 0x800000; + + .text : { + *(.text._start) + *(.text .text.*) + } + + .rodata : ALIGN(4K) { + *(.rodata .rodata.*) + } + + .data : ALIGN(4K) { + *(.data .data.*) + } + + .bss : ALIGN(4K) { + __bss_start = .; + *(.bss .bss.*) + *(COMMON) + __bss_end = .; + } + + /DISCARD/ : { + *(.eh_frame) + *(.comment) + } +} diff --git a/src/drivers/usb/src/define.rs b/src/drivers/usb/src/define.rs new file mode 100644 index 0000000..692b71b --- /dev/null +++ b/src/drivers/usb/src/define.rs @@ -0,0 +1,108 @@ +pub const PCI_CFG_ADDR_PORT: u16 = 0xCF8; +pub const PCI_CFG_DATA_PORT: u16 = 0xCFC; + +pub const XHCI_CLASS_CODE: u8 = 0x0C; +pub const XHCI_SUBCLASS: u8 = 0x03; +pub const XHCI_PROG_IF: u8 = 0x30; + +pub const XHCI_MMIO_MAP_SIZE: usize = 0x10000; +pub const PAGE_SIZE: usize = 4096; +pub const TRB_SIZE: usize = 16; + +pub const ENOMEM: u64 = (-12i64) as u64; +pub const EINVAL: u64 = (-22i64) as u64; + +pub const OP_USBCMD: usize = 0x00; +pub const OP_USBSTS: usize = 0x04; +pub const OP_CRCR: usize = 0x18; +pub const OP_DCBAAP: usize = 0x30; +pub const OP_CONFIG: usize = 0x38; + +pub const USBCMD_RUN_STOP: u32 = 1 << 0; +pub const USBCMD_HCRST: u32 = 1 << 1; +pub const USBCMD_INTE: u32 = 1 << 2; +pub const USBSTS_HCHALTED: u32 = 1 << 0; +pub const USBSTS_EINT: u32 = 1 << 3; +#[allow(unused)] +pub const USBSTS_CNR: u32 = 1 << 11; + +pub const RT_IR0_BASE: usize = 0x20; +pub const IR_IMAN: usize = 0x00; +pub const IR_IMOD: usize = 0x04; +pub const IR_ERSTSZ: usize = 0x08; +pub const IR_ERSTBA: usize = 0x10; +pub const IR_ERDP: usize = 0x18; + +pub const IMAN_IP: u32 = 1 << 0; +pub const IMAN_IE: u32 = 1 << 1; +pub const ERDP_EHB: u64 = 1 << 3; + +pub const TRB_TYPE_LINK: u32 = 6; +pub const TRB_TYPE_NORMAL: u32 = 1; +pub const TRB_TYPE_SETUP_STAGE: u32 = 2; +pub const TRB_TYPE_DATA_STAGE: u32 = 3; +pub const TRB_TYPE_STATUS_STAGE: u32 = 4; +pub const TRB_TYPE_ENABLE_SLOT_CMD: u32 = 9; +pub const TRB_TYPE_ADDRESS_DEVICE_CMD: u32 = 11; +pub const TRB_TYPE_CONFIGURE_ENDPOINT_CMD: u32 = 12; +pub const TRB_TYPE_NOOP_CMD: u32 = 23; +pub const TRB_TYPE_TRANSFER_EVENT: u32 = 32; +pub const TRB_TYPE_COMMAND_COMPLETION: u32 = 33; +pub const TRB_TYPE_PORT_STATUS_CHANGE: u32 = 34; + +pub const USB_DESC_DEVICE: u16 = 0x01; +pub const USB_DESC_CONFIGURATION: u16 = 0x02; + +#[derive(Clone, Copy)] +pub struct PciBdf { + pub(crate) bus: u8, + pub(crate) device: u8, + pub(crate) function: u8, +} + +#[derive(Clone, Copy)] +pub struct XhciController { + pub(crate) bdf: PciBdf, + pub(crate) vendor_id: u16, + pub(crate) device_id: u16, + pub(crate) bar0: u32, + pub(crate) bar1: u32, + pub(crate) mmio_base: u64, + pub(crate) bar_is_64bit: bool, +} + +#[allow(unused)] +#[derive(Clone)] +pub struct XhciRegs { + pub(crate) base: *mut u8, + pub(crate) cap_len: usize, + pub(crate) op_base: usize, + pub(crate) db_off: usize, + pub(crate) rt_off: usize, + pub(crate) max_ports: u8, + pub(crate) max_slots: u8, + pub(crate) hci_version: u16, + pub(crate) hccparams1: u32, + pub(crate) context_size: usize, +} + +pub const SC_RELEASE: u8 = 0x80; + +pub struct TransferRing { + pub(crate) page: DmaPage, + pub(crate) trb_count: usize, + pub(crate) enqueue_idx: usize, + pub(crate) cycle: bool, +} + +/// DMA 用に確保した1連続領域。 +/// +/// 所有権は DmaPage が保持し、Drop 時に `virt` を `size` バイト分解放する。 +/// - `virt` は PAGE_SIZE アラインで `size` バイト有効な領域を指す必要がある。 +/// - `phys` は `virt` 先頭に対応する物理アドレスである必要がある。 +/// - `virt` 参照を使うメソッドは、呼び出し元が有効ライフタイムと排他制御を保証すること。 +pub struct DmaPage { + pub(crate) virt: *mut u8, + pub(crate) phys: u64, + pub(crate) size: usize, +} diff --git a/src/drivers/usb/src/hid.rs b/src/drivers/usb/src/hid.rs new file mode 100644 index 0000000..06e4c65 --- /dev/null +++ b/src/drivers/usb/src/hid.rs @@ -0,0 +1,303 @@ +use swiftlib::input; + +use crate::define::SC_RELEASE; + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum HidReportKind { + Keyboard, + Mouse, + Unknown, +} + +impl Default for HidReportKind { + fn default() -> Self { + Self::Unknown + } +} + +#[derive(Default)] +pub struct HidParserState { + prev_keys: [u8; 6], + prev_modifiers: u8, + warned_kbd_inject: bool, + warned_mouse_inject: bool, + mouse_entries: [MouseDecodeEntry; 8], +} + +#[derive(Clone, Copy)] +struct MouseDecodeEntry { + used: bool, + slot: u8, + ep: u8, + prev_buttons: u8, +} + +impl MouseDecodeEntry { + const fn new() -> Self { + Self { + used: false, + slot: 0, + ep: 0, + prev_buttons: 0, + } + } +} + +impl Default for MouseDecodeEntry { + fn default() -> Self { + Self::new() + } +} + +fn mouse_entry_mut(state: &mut HidParserState, slot: u8, ep: u8) -> &mut MouseDecodeEntry { + if let Some(idx) = state + .mouse_entries + .iter() + .position(|e| e.used && e.slot == slot && e.ep == ep) + { + return &mut state.mouse_entries[idx]; + } + if let Some(idx) = state.mouse_entries.iter().position(|e| !e.used) { + state.mouse_entries[idx] = MouseDecodeEntry { + used: true, + slot, + ep, + prev_buttons: 0, + }; + return &mut state.mouse_entries[idx]; + } + // エントリ枯渇時は先頭を上書き + state.mouse_entries[0] = MouseDecodeEntry { + used: true, + slot, + ep, + prev_buttons: 0, + }; + &mut state.mouse_entries[0] +} + +#[inline] +fn map_hid_usage_to_set1_scancode(usage: u8) -> Option { + match usage { + 0x04 => Some(0x1E), // a + 0x05 => Some(0x30), // b + 0x06 => Some(0x2E), // c + 0x07 => Some(0x20), // d + 0x08 => Some(0x12), // e + 0x09 => Some(0x21), // f + 0x0A => Some(0x22), // g + 0x0B => Some(0x23), // h + 0x0C => Some(0x17), // i + 0x0D => Some(0x24), // j + 0x0E => Some(0x25), // k + 0x0F => Some(0x26), // l + 0x10 => Some(0x32), // m + 0x11 => Some(0x31), // n + 0x12 => Some(0x18), // o + 0x13 => Some(0x19), // p + 0x14 => Some(0x10), // q + 0x15 => Some(0x13), // r + 0x16 => Some(0x1F), // s + 0x17 => Some(0x14), // t + 0x18 => Some(0x16), // u + 0x19 => Some(0x2F), // v + 0x1A => Some(0x11), // w + 0x1B => Some(0x2D), // x + 0x1C => Some(0x15), // y + 0x1D => Some(0x2C), // z + 0x1E => Some(0x02), // 1 + 0x1F => Some(0x03), // 2 + 0x20 => Some(0x04), // 3 + 0x21 => Some(0x05), // 4 + 0x22 => Some(0x06), // 5 + 0x23 => Some(0x07), // 6 + 0x24 => Some(0x08), // 7 + 0x25 => Some(0x09), // 8 + 0x26 => Some(0x0A), // 9 + 0x27 => Some(0x0B), // 0 + 0x28 => Some(0x1C), // Enter + 0x29 => Some(0x01), // Esc + 0x2A => Some(0x0E), // Backspace + 0x2B => Some(0x0F), // Tab + 0x2C => Some(0x39), // Space + 0x2D => Some(0x0C), // - + 0x2E => Some(0x0D), // = + 0x2F => Some(0x1A), // [ + 0x30 => Some(0x1B), // ] + 0x31 => Some(0x2B), // \ + 0x33 => Some(0x27), // ; + 0x34 => Some(0x28), // ' + 0x35 => Some(0x29), // ` + 0x36 => Some(0x33), // , + 0x37 => Some(0x34), // . + 0x38 => Some(0x35), // / + _ => None, + } +} + +#[inline] +fn inject_scancode(scancode: u8, state: &mut HidParserState) { + if let Err(_err) = input::inject_scancode(scancode) { + if !state.warned_kbd_inject { + state.warned_kbd_inject = true; + } + } +} + +fn inject_modifier_transitions(new_mod: u8, state: &mut HidParserState) { + const LCTRL: u8 = 0x1D; + const LSHIFT: u8 = 0x2A; + const LALT: u8 = 0x38; + const LGUI: u8 = 0x5B; + const RCTRL: u8 = 0x1D; + const RSHIFT: u8 = 0x36; + const RALT: u8 = 0x38; + const RGUI: u8 = 0x5C; + let old_mod = state.prev_modifiers; + let pairs = [ + (0x01u8, false, LCTRL), + (0x02u8, false, LSHIFT), + (0x04u8, false, LALT), + (0x08u8, true, LGUI), + (0x10u8, true, RCTRL), + (0x20u8, false, RSHIFT), + (0x40u8, true, RALT), + (0x80u8, true, RGUI), + ]; + for (mask, e0, sc) in pairs { + let was = (old_mod & mask) != 0; + let now = (new_mod & mask) != 0; + if !was && now { + if e0 { + inject_scancode(0xE0, state); + } + inject_scancode(sc, state); + } else if was && !now { + if e0 { + inject_scancode(0xE0, state); + } + inject_scancode(sc | SC_RELEASE, state); + } + } + state.prev_modifiers = new_mod; +} + +fn parse_hid_keyboard_report( + _slot: u8, + _ep: u8, + report: &[u8], + state: &mut HidParserState, + strict_usage_check: bool, +) -> bool { + let mut chosen_offset = None; + for offset in [0usize, 1usize] { + if report.len() < offset + 8 { + continue; + } + // Boot keyboard report は [modifiers, reserved, key0..key5]。 + // reserved が 0 でない場合はキーボードとして扱わない。 + if report[offset + 1] != 0 { + continue; + } + chosen_offset = Some(offset); + break; + } + let Some(offset) = chosen_offset else { + return false; + }; + + let modifiers = report[offset]; + let keys = &report[offset + 2..offset + 8]; + if strict_usage_check + && keys + .iter() + .copied() + .any(|usage| usage != 0 && map_hid_usage_to_set1_scancode(usage).is_none()) + { + return false; + } + inject_modifier_transitions(modifiers, state); + + let prev_keys = state.prev_keys; + for &usage in &prev_keys { + if usage == 0 || keys.contains(&usage) { + continue; + } + if let Some(scancode) = map_hid_usage_to_set1_scancode(usage) { + inject_scancode(scancode | SC_RELEASE, state); + } + } + + for &usage in keys { + if usage == 0 || prev_keys.contains(&usage) { + continue; + } + if let Some(scancode) = map_hid_usage_to_set1_scancode(usage) { + inject_scancode(scancode, state); + } + } + + state.prev_keys.copy_from_slice(keys); + true +} + +fn parse_hid_mouse_report( + slot: u8, + ep: u8, + report: &[u8], + state: &mut HidParserState, +) -> bool { + if report.len() < 3 { + return false; + } + + let mut inject_errno: Option = None; + let raw_buttons = report[0]; + if (raw_buttons & 0xE0) != 0 { + return false; + } + let buttons = raw_buttons & 0x07; + let dx = report[1] as i8; + let dy = report[2] as i8; + let wheel = if report.len() > 3 { report[3] as i8 } else { 0 }; + + let prev_buttons = mouse_entry_mut(state, slot, ep).prev_buttons; + let has_change = dx != 0 || dy != 0 || wheel != 0 || buttons != prev_buttons; + if has_change { + if let Err(err) = input::inject_mouse_packet(buttons, dx, dy, wheel) { + inject_errno = Some(err as i64); + } + } + mouse_entry_mut(state, slot, ep).prev_buttons = buttons; + if let Some(errno) = inject_errno { + if !state.warned_mouse_inject { + println!("[xHCI] mouse inject failed: errno={}", errno); + state.warned_mouse_inject = true; + } + } + true +} + +pub fn parse_hid_report( + slot: u8, + ep: u8, + report: &[u8], + kind: HidReportKind, + state: &mut HidParserState, +) { + match kind { + HidReportKind::Keyboard => { + let _ = parse_hid_keyboard_report(slot, ep, report, state, false); + } + HidReportKind::Mouse => { + let _ = parse_hid_mouse_report(slot, ep, report, state); + } + HidReportKind::Unknown => { + if parse_hid_keyboard_report(slot, ep, report, state, true) { + return; + } + // Unknown はここではマウス扱いしない。 + // 種別確定は列挙時(descriptor 解析)で行う。 + } + } +} diff --git a/src/drivers/usb/src/main.rs b/src/drivers/usb/src/main.rs new file mode 100644 index 0000000..ad3f14a --- /dev/null +++ b/src/drivers/usb/src/main.rs @@ -0,0 +1,1916 @@ +use core::ptr::{read_volatile, write_volatile}; +use core::sync::atomic::{compiler_fence, Ordering as AtomicOrdering}; +use std::alloc::{alloc_zeroed, dealloc, Layout}; + +use swiftlib::{mmio, port, time}; + +mod define; +mod hid; +use define::*; +use hid::{parse_hid_report, HidParserState, HidReportKind}; + +const CC_SUCCESS: u8 = 1; +const CC_STALL_ERROR: u8 = 6; +const CC_SHORT_PACKET: u8 = 13; +const EVENT_DISPATCH_BUDGET: usize = 256; +const PORTSC_CHANGE_MASK: u32 = + (1 << 17) | (1 << 18) | (1 << 19) | (1 << 20) | (1 << 21) | (1 << 22) | (1 << 23); + +#[derive(Debug, Clone, Copy)] +enum RegisterAccessError { + OutOfBounds { offset: usize, size: usize }, +} + +fn pci_config_address(bdf: PciBdf, offset: u8) -> u32 { + 0x8000_0000 + | ((bdf.bus as u32) << 16) + | ((bdf.device as u32) << 11) + | ((bdf.function as u32) << 8) + | (u32::from(offset) & 0xFC) +} + +fn pci_read_u32(bdf: PciBdf, offset: u8) -> u32 { + let addr = pci_config_address(bdf, offset); + port::outl(PCI_CFG_ADDR_PORT, addr); + port::inl(PCI_CFG_DATA_PORT) +} + +fn pci_read_u16(bdf: PciBdf, offset: u8) -> u16 { + let aligned = offset & 0xFC; + let shift = u32::from(offset & 0x02) * 8; + ((pci_read_u32(bdf, aligned) >> shift) & 0xFFFF) as u16 +} + +fn pci_function_exists(bdf: PciBdf) -> bool { + pci_read_u16(bdf, 0x00) != 0xFFFF +} + +fn probe_xhci_controller(bdf: PciBdf) -> Option { + let class_reg = pci_read_u32(bdf, 0x08); + let class_code = ((class_reg >> 24) & 0xFF) as u8; + let subclass = ((class_reg >> 16) & 0xFF) as u8; + let prog_if = ((class_reg >> 8) & 0xFF) as u8; + + if class_code != XHCI_CLASS_CODE || subclass != XHCI_SUBCLASS || prog_if != XHCI_PROG_IF { + return None; + } + + let vendor_device = pci_read_u32(bdf, 0x00); + let vendor_id = (vendor_device & 0xFFFF) as u16; + let device_id = ((vendor_device >> 16) & 0xFFFF) as u16; + + let bar0 = pci_read_u32(bdf, 0x10); + let bar1 = pci_read_u32(bdf, 0x14); + + if (bar0 & 0x1) != 0 { + println!( + "[xHCI] controller {:02x}:{:02x}.{} uses I/O BAR (unsupported)", + bdf.bus, bdf.device, bdf.function + ); + return None; + } + + let bar_is_64bit = (bar0 & 0x6) == 0x4; + let mut mmio_base = u64::from(bar0 & 0xFFFF_FFF0); + if bar_is_64bit { + mmio_base |= u64::from(bar1) << 32; + } + + if mmio_base == 0 { + return None; + } + + Some(XhciController { + bdf, + vendor_id, + device_id, + bar0, + bar1, + mmio_base, + bar_is_64bit, + }) +} + +fn find_xhci_controller() -> Option { + for bus in 0u16..=255 { + for device in 0u16..32 { + let bdf0 = PciBdf { + bus: bus as u8, + device: device as u8, + function: 0, + }; + if !pci_function_exists(bdf0) { + continue; + } + + let header = pci_read_u32(bdf0, 0x0C); + let header_type = ((header >> 16) & 0xFF) as u8; + let function_count = if (header_type & 0x80) != 0 { 8 } else { 1 }; + + for function in 0..function_count { + let bdf = PciBdf { + bus: bus as u8, + device: device as u8, + function: function as u8, + }; + if !pci_function_exists(bdf) { + continue; + } + if let Some(controller) = probe_xhci_controller(bdf) { + return Some(controller); + } + } + } + } + None +} + +#[inline] +fn mmio_read_u8(base: *mut u8, offset: usize) -> u8 { + unsafe { read_volatile(base.add(offset) as *const u8) } +} + +#[inline] +fn mmio_read_u16(base: *mut u8, offset: usize) -> u16 { + unsafe { read_volatile(base.add(offset) as *const u16) } +} + +#[inline] +fn mmio_read_u32(base: *mut u8, offset: usize) -> u32 { + unsafe { read_volatile(base.add(offset) as *const u32) } +} + +#[inline] +fn mmio_write_u32(base: *mut u8, offset: usize, value: u32) { + unsafe { + write_volatile(base.add(offset) as *mut u32, value); + } +} + +#[inline] +#[allow(unused)] +fn mmio_read_u64(base: *mut u8, offset: usize) -> u64 { + let lo = u64::from(mmio_read_u32(base, offset)); + let hi = u64::from(mmio_read_u32(base, offset + 4)); + lo | (hi << 32) +} + +#[inline] +fn mmio_write_u64(base: *mut u8, offset: usize, value: u64) { + mmio_write_u32(base, offset, value as u32); + mmio_write_u32(base, offset + 4, (value >> 32) as u32); +} + +fn wait_until(timeout_ms: u64, mut condition: impl FnMut() -> bool) -> bool { + for _ in 0..timeout_ms { + if condition() { + return true; + } + time::sleep_ms(1); + } + false +} + +fn map_xhci_mmio(controller: &XhciController) -> Result<*mut u8, u64> { + let page_base = controller.mmio_base & !0xFFF; + let page_offset = (controller.mmio_base & 0xFFF) as usize; + let map_size = XHCI_MMIO_MAP_SIZE.saturating_add(page_offset); + let mapped = mmio::map_physical(page_base, map_size)?; + Ok(unsafe { mapped.add(page_offset) }) +} + +impl DmaPage { + fn alloc(size: usize) -> Result { + if size == 0 { + return Err(EINVAL); + } + let layout = Layout::from_size_align(size, PAGE_SIZE).map_err(|_| EINVAL)?; + let virt = unsafe { alloc_zeroed(layout) }; + if virt.is_null() { + return Err(ENOMEM); + } + let phys = mmio::virt_to_phys(virt as *const u8)?; + if (phys & 0xFFF) != 0 { + return Err(EINVAL); + } + Ok(Self { virt, phys, size }) + } + + fn zero(&self) { + unsafe { + core::ptr::write_bytes(self.virt, 0, self.size); + } + } + + fn write_u32(&self, offset: usize, value: u32) -> Result<(), RegisterAccessError> { + if offset + 4 > self.size { + return Err(RegisterAccessError::OutOfBounds { + offset, + size: self.size, + }); + } + unsafe { + write_volatile(self.virt.add(offset) as *mut u32, value); + } + Ok(()) + } + + fn read_u32(&self, offset: usize) -> Result { + if offset + 4 > self.size { + return Err(RegisterAccessError::OutOfBounds { + offset, + size: self.size, + }); + } + Ok(unsafe { read_volatile(self.virt.add(offset) as *const u32) }) + } + + #[allow(unused)] + fn write_bytes(&self, offset: usize, bytes: &[u8]) { + if offset + bytes.len() > self.size { + return; + } + unsafe { + core::ptr::copy_nonoverlapping(bytes.as_ptr(), self.virt.add(offset), bytes.len()); + } + } + + fn read_bytes(&self, offset: usize, len: usize) -> Vec { + if len == 0 || offset + len > self.size { + return Vec::new(); + } + let mut out = vec![0u8; len]; + for (i, b) in out.iter_mut().enumerate() { + *b = unsafe { read_volatile(self.virt.add(offset + i)) }; + } + out + } +} + + +impl Drop for DmaPage { + fn drop(&mut self) { + if let Ok(layout) = Layout::from_size_align(self.size, PAGE_SIZE) { + unsafe { + dealloc(self.virt, layout); + } + } + } +} +impl TransferRing { + fn new() -> Result { + let page = DmaPage::alloc(PAGE_SIZE)?; + let trb_count = page.size / TRB_SIZE; + if trb_count < 2 { + return Err(EINVAL); + } + + let link_index = trb_count - 1; + let link = [ + (page.phys & 0xFFFF_FFF0) as u32, + (page.phys >> 32) as u32, + 0, + (TRB_TYPE_LINK << 10) | (1 << 1) | 1, + ]; + trb_write(&page, link_index, link)?; + + Ok(Self { + page, + trb_count, + enqueue_idx: 0, + cycle: true, + }) + } + + #[inline] + fn ring_phys(&self) -> u64 { + self.page.phys + } + + #[inline] + fn link_index(&self) -> usize { + self.trb_count - 1 + } + + fn push_trb(&mut self, mut trb: [u32; 4]) -> u64 { + let idx = self.enqueue_idx; + let trb_phys = match idx + .checked_mul(TRB_SIZE) + .and_then(|off| u64::try_from(off).ok()) + .and_then(|off| self.page.phys.checked_add(off)) + { + Some(p) => p, + None => return 0, + }; + if self.cycle { + trb[3] |= 1; + } else { + trb[3] &= !1; + } + if trb_write(&self.page, idx, trb).is_err() { + return 0; + } + compiler_fence(AtomicOrdering::SeqCst); + + self.enqueue_idx += 1; + if self.enqueue_idx >= self.link_index() { + self.enqueue_idx = 0; + self.cycle = !self.cycle; + let mut link = match trb_read(&self.page, self.link_index()) { + Ok(v) => v, + Err(_) => return 0, + }; + if self.cycle { + link[3] |= 1; + } else { + link[3] &= !1; + } + if trb_write(&self.page, self.link_index(), link).is_err() { + return 0; + } + } + trb_phys + } +} + +fn trb_read(page: &DmaPage, index: usize) -> Result<[u32; 4], u64> { + let offset = index.checked_mul(TRB_SIZE).ok_or(EINVAL)?; + let end = offset.checked_add(TRB_SIZE).ok_or(EINVAL)?; + if end > page.size { + return Err(EINVAL); + } + let p = unsafe { page.virt.add(offset) as *const u32 }; + unsafe { + Ok([ + read_volatile(p.add(0)), + read_volatile(p.add(1)), + read_volatile(p.add(2)), + read_volatile(p.add(3)), + ]) + } +} + +fn trb_write(page: &DmaPage, index: usize, trb: [u32; 4]) -> Result<(), u64> { + let offset = index.checked_mul(TRB_SIZE).ok_or(EINVAL)?; + let end = offset.checked_add(TRB_SIZE).ok_or(EINVAL)?; + if end > page.size { + return Err(EINVAL); + } + let p = unsafe { page.virt.add(offset) as *mut u32 }; + unsafe { + write_volatile(p.add(0), trb[0]); + write_volatile(p.add(1), trb[1]); + write_volatile(p.add(2), trb[2]); + write_volatile(p.add(3), trb[3]); + } + Ok(()) +} + +struct CommandRing { + page: DmaPage, + trb_count: usize, + enqueue_idx: usize, + cycle: bool, +} + +impl CommandRing { + fn new() -> Result { + let page = DmaPage::alloc(PAGE_SIZE)?; + let trb_count = page.size / TRB_SIZE; + if trb_count < 2 { + return Err(EINVAL); + } + + let link_index = trb_count - 1; + let link = [ + (page.phys & 0xFFFF_FFF0) as u32, + (page.phys >> 32) as u32, + 0, + (TRB_TYPE_LINK << 10) | (1 << 1) | 1, + ]; + trb_write(&page, link_index, link)?; + + Ok(Self { + page, + trb_count, + enqueue_idx: 0, + cycle: true, + }) + } + + #[inline] + fn ring_phys(&self) -> u64 { + self.page.phys + } + + #[inline] + fn link_index(&self) -> usize { + self.trb_count - 1 + } + + #[allow(unused)] + fn push_noop_command(&mut self) -> u64 { + self.push_command([0, 0, 0, TRB_TYPE_NOOP_CMD << 10]) + } + + fn push_command(&mut self, mut trb: [u32; 4]) -> u64 { + let idx = self.enqueue_idx; + let trb_phys = match idx + .checked_mul(TRB_SIZE) + .and_then(|off| u64::try_from(off).ok()) + .and_then(|off| self.page.phys.checked_add(off)) + { + Some(p) => p, + None => return 0, + }; + if self.cycle { + trb[3] |= 1; + } else { + trb[3] &= !1; + } + if trb_write(&self.page, idx, trb).is_err() { + return 0; + } + compiler_fence(AtomicOrdering::SeqCst); + + self.enqueue_idx += 1; + if self.enqueue_idx >= self.link_index() { + self.enqueue_idx = 0; + self.cycle = !self.cycle; + let mut link = match trb_read(&self.page, self.link_index()) { + Ok(v) => v, + Err(_) => return 0, + }; + if self.cycle { + link[3] |= 1; + } else { + link[3] &= !1; + } + if trb_write(&self.page, self.link_index(), link).is_err() { + return 0; + } + } + + trb_phys + } +} + +struct EventRing { + segment: DmaPage, + erst: DmaPage, + trb_count: usize, + dequeue_idx: usize, + ccs: bool, +} + +impl EventRing { + fn new() -> Result { + let segment = DmaPage::alloc(PAGE_SIZE)?; + let erst = DmaPage::alloc(PAGE_SIZE)?; + let trb_count = segment.size / TRB_SIZE; + if trb_count == 0 { + return Err(EINVAL); + } + + let erst_entry = [ + segment.phys as u32, + (segment.phys >> 32) as u32, + trb_count as u32, + 0, + ]; + let p = erst.virt as *mut u32; + unsafe { + write_volatile(p.add(0), erst_entry[0]); + write_volatile(p.add(1), erst_entry[1]); + write_volatile(p.add(2), erst_entry[2]); + write_volatile(p.add(3), erst_entry[3]); + } + + Ok(Self { + segment, + erst, + trb_count, + dequeue_idx: 0, + ccs: true, + }) + } + + fn dequeue_phys(&self) -> u64 { + self.segment.phys + (self.dequeue_idx * TRB_SIZE) as u64 + } + + fn pop_event(&mut self) -> Option<[u32; 4]> { + let trb = trb_read(&self.segment, self.dequeue_idx).ok()?; + let cycle = (trb[3] & 1) != 0; + if cycle != self.ccs { + return None; + } + + self.dequeue_idx += 1; + if self.dequeue_idx >= self.trb_count { + self.dequeue_idx = 0; + self.ccs = !self.ccs; + } + Some(trb) + } +} + +#[derive(Clone, Copy)] +struct HidEndpointConfig { + interface_number: u8, + interface_subclass: u8, + interface_protocol: u8, + report_desc_len: u16, + report_id: u8, + ep_addr: u8, + dci: u8, + ep_type: u8, + max_packet: u16, + interval: u8, + kind: HidReportKind, +} + +struct HidEndpointState { + config: HidEndpointConfig, + ring: TransferRing, + report_buf: DmaPage, + report_len: usize, +} + +struct UsbDeviceState { + port_id: u8, + port_speed: u8, + slot_id: u8, + ep0_max_packet: u16, + input_ctx: DmaPage, + dev_ctx: DmaPage, + ep0_ring: TransferRing, + descriptor_buf: DmaPage, + hid_ep: Option, +} + +enum PendingCommandKind { + Noop, + EnableSlot { port_id: u8 }, + AddressDevice { slot_id: u8 }, + ConfigureEndpoint { slot_id: u8, dci: u8 }, +} + +struct PendingCommand { + trb_phys: u64, + kind: PendingCommandKind, +} + +enum PendingTransferKind { + DeviceDescriptor8 { slot_id: u8 }, + ConfigHeader { slot_id: u8 }, + ConfigFull { slot_id: u8 }, + HidReportDescriptor { + slot_id: u8, + config_value: u8, + hid_cfg: HidEndpointConfig, + }, + SetConfiguration { slot_id: u8 }, + InterruptIn { slot_id: u8, dci: u8, retries: u8 }, +} + +#[allow(unused)] +struct PendingTransfer { + trb_phys: u64, + data_phys: u64, + data_len: usize, + kind: PendingTransferKind, +} + +struct XhciRuntime { + regs: XhciRegs, + dcbaa: DmaPage, + command_ring: CommandRing, + event_ring: EventRing, + devices: Vec, + pending_commands: Vec, + pending_transfers: Vec, + enumerating_port: Option, + hid: HidParserState, +} + +fn read_xhci_regs(base: *mut u8) -> Option { + let cap_len = mmio_read_u8(base, 0x00) as usize; + if cap_len < 0x20 { + return None; + } + + let hci_version = mmio_read_u16(base, 0x02); + let hcs_params1 = mmio_read_u32(base, 0x04); + let hccparams1 = mmio_read_u32(base, 0x10); + let max_slots = (hcs_params1 & 0xFF) as u8; + let max_ports = ((hcs_params1 >> 24) & 0xFF) as u8; + let db_off = (mmio_read_u32(base, 0x14) & !0x3) as usize; + let rt_off = (mmio_read_u32(base, 0x18) & !0x1F) as usize; + let context_size = if (hccparams1 & (1 << 2)) != 0 { 64 } else { 32 }; + + Some(XhciRegs { + base, + cap_len, + op_base: cap_len, + db_off, + rt_off, + max_ports, + max_slots, + hci_version, + hccparams1, + context_size, + }) +} + +fn halt_xhci(regs: &XhciRegs) -> bool { + let usbcmd_off = regs.op_base; + let usbsts_off = regs.op_base + 0x04; + + let cmd = mmio_read_u32(regs.base, usbcmd_off); + if (cmd & 0x1) != 0 { + mmio_write_u32(regs.base, usbcmd_off, cmd & !0x1); + } + + wait_until(300, || (mmio_read_u32(regs.base, usbsts_off) & 0x1) != 0) +} + +fn reset_xhci(regs: &XhciRegs) -> bool { + if !halt_xhci(regs) { + println!("[xHCI] halt timeout"); + return false; + } + + let usbcmd_off = regs.op_base; + let usbsts_off = regs.op_base + 0x04; + + let cmd = mmio_read_u32(regs.base, usbcmd_off); + mmio_write_u32(regs.base, usbcmd_off, cmd | (1 << 1)); + + if !wait_until(1000, || (mmio_read_u32(regs.base, usbcmd_off) & (1 << 1)) == 0) { + println!("[xHCI] controller reset timeout"); + return false; + } + if !wait_until(1000, || (mmio_read_u32(regs.base, usbsts_off) & (1 << 11)) == 0) { + println!("[xHCI] CNR clear timeout"); + return false; + } + true +} + +fn ring_doorbell(regs: &XhciRegs, doorbell: usize, value: u32) { + mmio_write_u32(regs.base, regs.db_off + doorbell * 4, value); +} + +fn setup_command_ring_register(regs: &XhciRegs, ring: &CommandRing) { + let crcr = (ring.ring_phys() & !0x3F) | 1; + mmio_write_u64(regs.base, regs.op_base + OP_CRCR, crcr); +} + +fn setup_interrupter(regs: &XhciRegs, event_ring: &EventRing) { + let ir_base = regs.rt_off + RT_IR0_BASE; + + mmio_write_u32(regs.base, ir_base + IR_IMOD, 0); + mmio_write_u32(regs.base, ir_base + IR_ERSTSZ, 1); + mmio_write_u64(regs.base, ir_base + IR_ERSTBA, event_ring.erst.phys); + mmio_write_u64(regs.base, ir_base + IR_ERDP, event_ring.dequeue_phys() | ERDP_EHB); + + let iman = mmio_read_u32(regs.base, ir_base + IR_IMAN); + mmio_write_u32(regs.base, ir_base + IR_IMAN, iman | IMAN_IE | IMAN_IP); +} + +fn start_xhci(regs: &XhciRegs) -> bool { + let mut cfg = mmio_read_u32(regs.base, regs.op_base + OP_CONFIG); + cfg = (cfg & !0xFF) | u32::from(core::cmp::max(regs.max_slots, 1)); + mmio_write_u32(regs.base, regs.op_base + OP_CONFIG, cfg); + + let st = mmio_read_u32(regs.base, regs.op_base + OP_USBSTS); + mmio_write_u32(regs.base, regs.op_base + OP_USBSTS, st); + + let mut cmd = mmio_read_u32(regs.base, regs.op_base + OP_USBCMD); + cmd |= USBCMD_INTE; + cmd |= USBCMD_RUN_STOP; + cmd &= !USBCMD_HCRST; + mmio_write_u32(regs.base, regs.op_base + OP_USBCMD, cmd); + + wait_until(1000, || (mmio_read_u32(regs.base, regs.op_base + OP_USBSTS) & USBSTS_HCHALTED) == 0) +} + +#[inline] +fn input_ctx_offset(ctx_size: usize, index: usize) -> usize { + ctx_size * index +} + +#[inline] +fn device_ctx_offset(ctx_size: usize, dci: usize) -> usize { + ctx_size * dci +} + +#[inline] +fn endpoint_dci_from_address(ep_addr: u8) -> u8 { + let ep_num = ep_addr & 0x0F; + if ep_num == 0 { + 1 + } else if (ep_addr & 0x80) != 0 { + ep_num.saturating_mul(2).saturating_add(1) + } else { + ep_num.saturating_mul(2) + } +} + +fn endpoint_type_from_descriptor(ep_addr: u8, attrs: u8) -> Option { + match attrs & 0x3 { + 0 => Some(4), // control + 1 => Some(if (ep_addr & 0x80) != 0 { 5 } else { 1 }), // isoch + 2 => Some(if (ep_addr & 0x80) != 0 { 6 } else { 2 }), // bulk + 3 => Some(if (ep_addr & 0x80) != 0 { 7 } else { 3 }), // interrupt + _ => None, + } +} + +fn default_ep0_max_packet(speed: u8) -> u16 { + match speed { + 4 | 5 => 512, + 3 => 64, + 2 | 1 => 8, + _ => 8, + } +} + +fn portsc_offset(regs: &XhciRegs, port_id: u8) -> usize { + regs.op_base + 0x400 + (usize::from(port_id.saturating_sub(1)) * 0x10) +} + +fn read_port_speed(regs: &XhciRegs, port_id: u8) -> u8 { + let portsc = mmio_read_u32(regs.base, portsc_offset(regs, port_id)); + ((portsc >> 10) & 0x0F) as u8 +} + +fn submit_next_connected_port(runtime: &mut XhciRuntime) { + if runtime.enumerating_port.is_some() { + return; + } + for port in 1..=runtime.regs.max_ports { + if runtime.devices.iter().any(|d| d.port_id == port) { + continue; + } + let portsc = mmio_read_u32(runtime.regs.base, portsc_offset(&runtime.regs, port)); + if (portsc & 1) == 0 { + continue; + } + submit_enable_slot_for_port(runtime, port); + if runtime.enumerating_port.is_some() { + break; + } + } +} + +fn submit_command(runtime: &mut XhciRuntime, trb: [u32; 4], kind: PendingCommandKind) -> u64 { + let trb_phys = runtime.command_ring.push_command(trb); + runtime.pending_commands.push(PendingCommand { trb_phys, kind }); + ring_doorbell(&runtime.regs, 0, 0); + trb_phys +} + +fn write_dcbaa_slot(dcbaa: &DmaPage, slot_id: u8, value: u64) { + if slot_id == 0 { + return; + } + let off = usize::from(slot_id) * 8; + if off + 8 > dcbaa.size { + return; + } + if dcbaa.write_u32(off, value as u32).is_err() + || dcbaa.write_u32(off + 4, (value >> 32) as u32).is_err() + { + println!("[xHCI] dcbaa write out of bounds: slot={} off={}", slot_id, off); + } +} + +fn find_device_index(runtime: &XhciRuntime, slot_id: u8) -> Option { + runtime.devices.iter().position(|d| d.slot_id == slot_id) +} + +fn build_address_input_context(regs: &XhciRegs, dev: &mut UsbDeviceState) -> bool { + dev.input_ctx.zero(); + if dev.input_ctx.write_u32(0x00, 0).is_err() || dev.input_ctx.write_u32(0x04, 0x3).is_err() { + return false; + } + + let slot_off = input_ctx_offset(regs.context_size, 1); + let slot_dw0 = ((u32::from(dev.port_speed) & 0xF) << 20) | (1 << 27); + let slot_dw1 = (u32::from(dev.port_id) & 0xFF) << 16; + if dev.input_ctx.write_u32(slot_off + 0x00, slot_dw0).is_err() + || dev.input_ctx.write_u32(slot_off + 0x04, slot_dw1).is_err() + { + return false; + } + + let ep0_off = input_ctx_offset(regs.context_size, 2); + let tr_deq = (dev.ep0_ring.ring_phys() & !0xF) | 1; + let ep0_dw1 = 3 | (4 << 3) | (u32::from(dev.ep0_max_packet) << 16); // CErr=3, Control EP + if dev.input_ctx.write_u32(ep0_off + 0x00, 0).is_err() + || dev.input_ctx.write_u32(ep0_off + 0x04, ep0_dw1).is_err() + || dev.input_ctx.write_u32(ep0_off + 0x08, tr_deq as u32).is_err() + || dev.input_ctx.write_u32(ep0_off + 0x0C, (tr_deq >> 32) as u32).is_err() + || dev + .input_ctx + .write_u32(ep0_off + 0x10, u32::from(dev.ep0_max_packet)) + .is_err() + { + return false; + } + + true +} + +fn build_configure_endpoint_input_context(regs: &XhciRegs, dev: &mut UsbDeviceState) -> Option { + let hid = dev.hid_ep.as_ref()?; + let dci = hid.config.dci; + + dev.input_ctx.zero(); + dev.input_ctx.write_u32(0x00, 0).ok()?; // drop flags + dev.input_ctx + .write_u32(0x04, (1u32 << 0) | (1u32 << u32::from(dci))) + .ok()?; // add slot + endpoint + + // slot context は既存 device context をコピー + let slot_in_off = input_ctx_offset(regs.context_size, 1); + let slot_dev_off = device_ctx_offset(regs.context_size, 0); + for off in (0..regs.context_size).step_by(4) { + let v = dev.dev_ctx.read_u32(slot_dev_off + off).ok()?; + dev.input_ctx.write_u32(slot_in_off + off, v).ok()?; + } + + let mut slot_dw0 = dev.input_ctx.read_u32(slot_in_off).ok()?; + slot_dw0 &= !((0x1F << 27) | (0x0F << 20)); + slot_dw0 |= ((u32::from(dci) & 0x1F) << 27) | ((u32::from(dev.port_speed) & 0x0F) << 20); + dev.input_ctx.write_u32(slot_in_off, slot_dw0).ok()?; + + let ep_off = input_ctx_offset(regs.context_size, usize::from(dci) + 1); + let tr_deq = (hid.ring.ring_phys() & !0xF) | 1; + let interval = u32::from(core::cmp::max(hid.config.interval, 1)); + let ep_dw0 = interval << 16; + let ep_dw1 = 3 | (u32::from(hid.config.ep_type) << 3) | (u32::from(hid.config.max_packet) << 16); + dev.input_ctx.write_u32(ep_off + 0x00, ep_dw0).ok()?; + dev.input_ctx.write_u32(ep_off + 0x04, ep_dw1).ok()?; + dev.input_ctx.write_u32(ep_off + 0x08, tr_deq as u32).ok()?; + dev.input_ctx + .write_u32(ep_off + 0x0C, (tr_deq >> 32) as u32) + .ok()?; + dev.input_ctx + .write_u32(ep_off + 0x10, u32::from(hid.config.max_packet)) + .ok()?; + + Some(dci) +} + +fn submit_enable_slot_for_port(runtime: &mut XhciRuntime, port_id: u8) { + if runtime.enumerating_port.is_some() { + return; + } + if runtime.devices.iter().any(|d| d.port_id == port_id) { + return; + } + + let portsc_off = portsc_offset(&runtime.regs, port_id); + let portsc = mmio_read_u32(runtime.regs.base, portsc_off); + if (portsc & 1) == 0 { + return; + } + + // Enable Slot 前に Port Reset を実行して、デバイスを既知状態にする。 + if (portsc & (1 << 1)) == 0 { + let reset_req = (portsc & !PORTSC_CHANGE_MASK) | (portsc & (1 << 17)) | (1 << 4); + mmio_write_u32(runtime.regs.base, portsc_off, reset_req); + if !wait_until(200, || { + (mmio_read_u32(runtime.regs.base, portsc_off) & (1 << 4)) == 0 + }) { + println!("[xHCI] port reset timeout: port={}", port_id); + return; + } + let post = mmio_read_u32(runtime.regs.base, portsc_off); + if (post & 1) == 0 { + println!("[xHCI] port disconnected after reset: port={}", port_id); + return; + } + } + + runtime.enumerating_port = Some(port_id); + submit_command( + runtime, + [0, 0, 0, TRB_TYPE_ENABLE_SLOT_CMD << 10], + PendingCommandKind::EnableSlot { port_id }, + ); + println!("[xHCI] submitted Enable Slot for port {}", port_id); +} + +fn create_device_for_slot(runtime: &mut XhciRuntime, port_id: u8, slot_id: u8) -> Result<(), u64> { + let speed = read_port_speed(&runtime.regs, port_id); + let mut dev = UsbDeviceState { + port_id, + port_speed: speed, + slot_id, + ep0_max_packet: default_ep0_max_packet(speed), + input_ctx: DmaPage::alloc(PAGE_SIZE)?, + dev_ctx: DmaPage::alloc(PAGE_SIZE)?, + ep0_ring: TransferRing::new()?, + descriptor_buf: DmaPage::alloc(PAGE_SIZE)?, + hid_ep: None, + }; + dev.dev_ctx.zero(); + if !build_address_input_context(&runtime.regs, &mut dev) { + return Err(EINVAL); + } + write_dcbaa_slot(&runtime.dcbaa, slot_id, dev.dev_ctx.phys); + let input_ctx_phys = dev.input_ctx.phys; + + runtime.devices.push(dev); + submit_command( + runtime, + [ + input_ctx_phys as u32, + (input_ctx_phys >> 32) as u32, + 0, + (TRB_TYPE_ADDRESS_DEVICE_CMD << 10) | (u32::from(slot_id) << 24), + ], + PendingCommandKind::AddressDevice { slot_id }, + ); + println!( + "[xHCI] slot {} assigned to port {} (speed={} {})", + slot_id, + port_id, + speed, + decode_port_speed(speed) + ); + Ok(()) +} + +fn submit_control_in_transfer( + runtime: &mut XhciRuntime, + slot_id: u8, + request: u8, + value: u16, + index: u16, + length: u16, + kind: PendingTransferKind, +) -> bool { + let Some(dev_idx) = find_device_index(runtime, slot_id) else { + return false; + }; + + let (status_trb_phys, data_phys, data_len) = { + let dev = &mut runtime.devices[dev_idx]; + dev.descriptor_buf.zero(); + + let setup_packet = u64::from(0x80u8) + | (u64::from(request) << 8) + | (u64::from(value) << 16) + | (u64::from(index) << 32) + | (u64::from(length) << 48); + + let setup_trb = [ + setup_packet as u32, + (setup_packet >> 32) as u32, + 8, + (TRB_TYPE_SETUP_STAGE << 10) | (3 << 16) | (1 << 6), // TRT=IN, IDT + ]; + let data_trb = [ + dev.descriptor_buf.phys as u32, + (dev.descriptor_buf.phys >> 32) as u32, + u32::from(length), + (TRB_TYPE_DATA_STAGE << 10) | (1 << 16) | (1 << 4), // IN + CH + ]; + let status_trb = [0, 0, 0, (TRB_TYPE_STATUS_STAGE << 10) | (1 << 5)]; // IOC + + let setup_phys = dev.ep0_ring.push_trb(setup_trb); + if setup_phys == 0 { + println!("[xHCI] failed to queue setup TRB for control transfer"); + return false; + } + let data_phys_trb = dev.ep0_ring.push_trb(data_trb); + if data_phys_trb == 0 { + println!("[xHCI] failed to queue data TRB for control transfer"); + return false; + } + let status_trb_phys = dev.ep0_ring.push_trb(status_trb); + if status_trb_phys == 0 { + println!("[xHCI] failed to queue status TRB for control transfer"); + return false; + } + ( + status_trb_phys, + dev.descriptor_buf.phys, + usize::from(length).min(dev.descriptor_buf.size), + ) + }; + + runtime.pending_transfers.push(PendingTransfer { + trb_phys: status_trb_phys, + data_phys, + data_len, + kind, + }); + ring_doorbell(&runtime.regs, usize::from(slot_id), 1); // EP0 + true +} + +fn submit_get_device_descriptor8(runtime: &mut XhciRuntime, slot_id: u8) -> bool { + submit_control_in_transfer( + runtime, + slot_id, + 0x06, + USB_DESC_DEVICE << 8, + 0, + 8, + PendingTransferKind::DeviceDescriptor8 { slot_id }, + ) +} + +fn submit_get_config_header(runtime: &mut XhciRuntime, slot_id: u8) -> bool { + submit_control_in_transfer( + runtime, + slot_id, + 0x06, + USB_DESC_CONFIGURATION << 8, + 0, + 9, + PendingTransferKind::ConfigHeader { slot_id }, + ) +} + +fn submit_get_config_full(runtime: &mut XhciRuntime, slot_id: u8, total_len: u16) -> bool { + submit_control_in_transfer( + runtime, + slot_id, + 0x06, + USB_DESC_CONFIGURATION << 8, + 0, + total_len, + PendingTransferKind::ConfigFull { slot_id }, + ) +} + +fn submit_set_configuration(runtime: &mut XhciRuntime, slot_id: u8, config_value: u8) -> bool { + let Some(dev_idx) = find_device_index(runtime, slot_id) else { + return false; + }; + + let status_trb_phys = { + let dev = &mut runtime.devices[dev_idx]; + let setup_packet = u64::from(0x00u8) + | (u64::from(0x09u8) << 8) + | (u64::from(config_value) << 16); + let setup_trb = [ + setup_packet as u32, + (setup_packet >> 32) as u32, + 8, + (TRB_TYPE_SETUP_STAGE << 10) | (1 << 6), // TRT=NoData, IDT + ]; + let status_trb = [ + 0, + 0, + 0, + (TRB_TYPE_STATUS_STAGE << 10) | (1 << 5) | (1 << 16), // IOC + DIR(IN) + ]; + let setup_phys = dev.ep0_ring.push_trb(setup_trb); + if setup_phys == 0 { + println!("[xHCI] failed to queue setup TRB for SET_CONFIGURATION"); + return false; + } + let status_phys = dev.ep0_ring.push_trb(status_trb); + if status_phys == 0 { + println!("[xHCI] failed to queue status TRB for SET_CONFIGURATION"); + return false; + } + status_phys + }; + + runtime.pending_transfers.push(PendingTransfer { + trb_phys: status_trb_phys, + data_phys: 0, + data_len: 0, + kind: PendingTransferKind::SetConfiguration { slot_id }, + }); + ring_doorbell(&runtime.regs, usize::from(slot_id), 1); // EP0 + true +} + +fn parse_hid_endpoint_from_config(config: &[u8]) -> Option { + let mut idx = 0usize; + let mut in_hid_interface = false; + let mut report_kind = HidReportKind::Unknown; + let mut interface_number = 0u8; + let mut interface_subclass = 0u8; + let mut interface_protocol = 0u8; + let mut report_desc_len = 0u16; + + while idx + 2 <= config.len() { + let len = config[idx] as usize; + if len < 2 || idx + len > config.len() { + break; + } + + let desc_type = config[idx + 1]; + match desc_type { + 0x04 => { + if len >= 9 { + interface_number = config[idx + 2]; + interface_subclass = config[idx + 6]; + let class = config[idx + 5]; + let protocol = config[idx + 7]; + interface_protocol = protocol; + in_hid_interface = class == 0x03; + report_desc_len = 0; + report_kind = if in_hid_interface { + match protocol { + 1 => HidReportKind::Keyboard, + 2 => HidReportKind::Mouse, + _ => HidReportKind::Unknown, + } + } else { + HidReportKind::Unknown + }; + } else { + in_hid_interface = false; + report_kind = HidReportKind::Unknown; + report_desc_len = 0; + } + } + 0x21 if in_hid_interface && len >= 9 => { + report_desc_len = u16::from_le_bytes([config[idx + 7], config[idx + 8]]); + } + 0x05 if in_hid_interface && len >= 7 => { + let ep_addr = config[idx + 2]; + let attrs = config[idx + 3]; + let transfer_type = attrs & 0x3; + if (ep_addr & 0x80) == 0 || transfer_type != 0x03 { + idx += len; + continue; + } + + let max_packet = + u16::from_le_bytes([config[idx + 4], config[idx + 5]]) & 0x07FF; + let interval = core::cmp::max(config[idx + 6], 1); + let dci = endpoint_dci_from_address(ep_addr); + let ep_type = endpoint_type_from_descriptor(ep_addr, attrs)?; + return Some(HidEndpointConfig { + interface_number, + interface_subclass, + interface_protocol, + report_desc_len, + report_id: 0, + ep_addr, + dci, + ep_type, + max_packet, + interval, + kind: report_kind, + }); + } + _ => {} + } + + idx += len; + } + + None +} + +fn classify_hid_from_report_descriptor(report: &[u8]) -> (HidReportKind, u8) { + // HID short item を最小限パースし、Application Collection の usage を見る。 + let mut i = 0usize; + let mut usage_page: u16 = 0; + let mut last_usage: u16 = 0; + let mut current_report_id: u8 = 0; + + while i < report.len() { + let b = report[i]; + i += 1; + if b == 0xFE { + if i + 1 >= report.len() { + break; + } + let data_len = report[i] as usize; + i = i.saturating_add(2 + data_len); + continue; + } + let size_code = (b & 0x03) as usize; + let data_len = if size_code == 3 { 4 } else { size_code }; + let item_type = (b >> 2) & 0x03; + let tag = (b >> 4) & 0x0F; + if i + data_len > report.len() { + break; + } + let mut v: u32 = 0; + for k in 0..data_len { + v |= u32::from(report[i + k]) << (k * 8); + } + i += data_len; + + match (item_type, tag) { + (1, 0x0) => usage_page = v as u16, // Global: Usage Page + (1, 0x8) => current_report_id = v as u8, // Global: Report ID + (2, 0x0) => last_usage = v as u16, // Local: Usage + (0, 0xA) => { + // Main: Collection + let is_application = (v as u8) == 0x01; + if is_application && usage_page == 0x01 { + if last_usage == 0x02 { + return (HidReportKind::Mouse, current_report_id); + } + if last_usage == 0x06 { + return (HidReportKind::Keyboard, current_report_id); + } + } + } + (0, _) => { + // Main item で local usage をリセット + last_usage = 0; + } + _ => {} + } + } + (HidReportKind::Unknown, 0) +} + +fn submit_get_hid_report_descriptor( + runtime: &mut XhciRuntime, + slot_id: u8, + config_value: u8, + hid_cfg: HidEndpointConfig, +) -> bool { + let length = core::cmp::min(hid_cfg.report_desc_len, PAGE_SIZE as u16); + if length == 0 { + return false; + } + submit_control_in_transfer( + runtime, + slot_id, + 0x06, + (0x22u16 << 8), + u16::from(hid_cfg.interface_number), + length, + PendingTransferKind::HidReportDescriptor { + slot_id, + config_value, + hid_cfg, + }, + ) +} + +fn setup_hid_endpoint( + runtime: &mut XhciRuntime, + slot_id: u8, + hid_cfg: HidEndpointConfig, + config_value: u8, +) { + if hid_cfg.kind == HidReportKind::Unknown { + println!( + "[xHCI] skip unknown HID endpoint: slot={} if={} subclass={} protocol={} report_len={}", + slot_id, + hid_cfg.interface_number, + hid_cfg.interface_subclass, + hid_cfg.interface_protocol, + hid_cfg.report_desc_len + ); + return; + } + + let Some(dev_idx) = find_device_index(runtime, slot_id) else { + return; + }; + + let ring = match TransferRing::new() { + Ok(r) => r, + Err(err) => { + println!("[xHCI] HID ring alloc failed: {:#x}", err); + return; + } + }; + let report_buf = match DmaPage::alloc(PAGE_SIZE) { + Ok(p) => p, + Err(err) => { + println!("[xHCI] HID report buffer alloc failed: {:#x}", err); + return; + } + }; + let report_len = core::cmp::max(8usize, core::cmp::min(usize::from(hid_cfg.max_packet), 64usize)); + runtime.devices[dev_idx].hid_ep = Some(HidEndpointState { + config: hid_cfg, + ring, + report_buf, + report_len, + }); + println!( + "[xHCI] HID endpoint selected: slot={} if={} ep=0x{:02x} dci={} max_packet={} interval={} kind={} report_id={}", + slot_id, + hid_cfg.interface_number, + hid_cfg.ep_addr, + hid_cfg.dci, + hid_cfg.max_packet, + hid_cfg.interval, + match hid_cfg.kind { + HidReportKind::Keyboard => "keyboard", + HidReportKind::Mouse => "mouse", + HidReportKind::Unknown => "unknown", + }, + hid_cfg.report_id + ); + if !submit_set_configuration(runtime, slot_id, config_value) { + println!( + "[xHCI] failed to submit SET_CONFIGURATION: slot={} config={}", + slot_id, config_value + ); + } +} + +fn submit_configure_endpoint_command(runtime: &mut XhciRuntime, slot_id: u8) -> bool { + let Some(dev_idx) = find_device_index(runtime, slot_id) else { + return false; + }; + let Some(dci) = build_configure_endpoint_input_context(&runtime.regs, &mut runtime.devices[dev_idx]) else { + return false; + }; + let input_ctx_phys = runtime.devices[dev_idx].input_ctx.phys; + submit_command( + runtime, + [ + input_ctx_phys as u32, + (input_ctx_phys >> 32) as u32, + 0, + (TRB_TYPE_CONFIGURE_ENDPOINT_CMD << 10) | (u32::from(slot_id) << 24), + ], + PendingCommandKind::ConfigureEndpoint { slot_id, dci }, + ); + true +} + +fn submit_interrupt_in_transfer(runtime: &mut XhciRuntime, slot_id: u8, dci: u8, retries: u8) -> bool { + let Some(dev_idx) = find_device_index(runtime, slot_id) else { + return false; + }; + + let (trb_phys, data_phys, data_len, doorbell_target) = { + let dev = &mut runtime.devices[dev_idx]; + let Some(hid) = dev.hid_ep.as_mut() else { + return false; + }; + let report_len = core::cmp::max(1usize, hid.report_len); + hid.report_buf.zero(); + let trb = [ + hid.report_buf.phys as u32, + (hid.report_buf.phys >> 32) as u32, + report_len as u32, + (TRB_TYPE_NORMAL << 10) | (1 << 5) | (1 << 2), // IOC + ISP + ]; + let trb_phys = hid.ring.push_trb(trb); + (trb_phys, hid.report_buf.phys, report_len, hid.config.dci) + }; + + let target = if dci == 0 { doorbell_target } else { dci }; + runtime.pending_transfers.push(PendingTransfer { + trb_phys, + data_phys, + data_len, + kind: PendingTransferKind::InterruptIn { + slot_id, + dci: target, + retries, + }, + }); + ring_doorbell(&runtime.regs, usize::from(slot_id), u32::from(target)); + true +} + +fn handle_command_completion_event( + runtime: &mut XhciRuntime, + cmd_ptr: u64, + completion_code: u8, + slot_id_from_event: u8, +) { + let Some(pos) = runtime + .pending_commands + .iter() + .position(|c| c.trb_phys == cmd_ptr) + else { + println!( + "[xHCI] command completion (untracked): code={} slot={} ptr={:#x}", + completion_code, slot_id_from_event, cmd_ptr + ); + return; + }; + + let pending = runtime.pending_commands.remove(pos); + match pending.kind { + PendingCommandKind::Noop => { + println!( + "[xHCI] No-Op command completion: code={} slot={}", + completion_code, slot_id_from_event + ); + } + PendingCommandKind::EnableSlot { port_id } => { + runtime.enumerating_port = None; + if completion_code != 1 || slot_id_from_event == 0 { + println!( + "[xHCI] Enable Slot failed: port={} code={}", + port_id, completion_code + ); + submit_next_connected_port(runtime); + return; + } + if let Err(err) = create_device_for_slot(runtime, port_id, slot_id_from_event) { + println!( + "[xHCI] create slot/device failed: slot={} port={} err={:#x}", + slot_id_from_event, port_id, err + ); + } + submit_next_connected_port(runtime); + } + PendingCommandKind::AddressDevice { slot_id } => { + if completion_code != 1 { + println!( + "[xHCI] Address Device failed: slot={} code={}", + slot_id, completion_code + ); + return; + } + println!("[xHCI] Address Device completed: slot={}", slot_id); + let _ = submit_get_device_descriptor8(runtime, slot_id); + } + PendingCommandKind::ConfigureEndpoint { slot_id, dci } => { + if completion_code != 1 { + println!( + "[xHCI] Configure Endpoint failed: slot={} dci={} code={}", + slot_id, dci, completion_code + ); + return; + } + println!( + "[xHCI] Configure Endpoint completed: slot={} dci={}", + slot_id, dci + ); + let _ = submit_interrupt_in_transfer(runtime, slot_id, dci, 0); + } + } +} + +fn handle_transfer_event( + runtime: &mut XhciRuntime, + transfer_ptr: u64, + completion_code: u8, + transfer_len_remaining: u32, + slot_id: u8, + ep_id: u8, +) { + let Some(pos) = runtime + .pending_transfers + .iter() + .position(|t| t.trb_phys == transfer_ptr) + else { + println!( + "[xHCI] transfer event (untracked): slot={} ep={} code={} trb={:#x}", + slot_id, ep_id, completion_code, transfer_ptr + ); + return; + }; + + let pending = runtime.pending_transfers.remove(pos); + let actual_len = pending + .data_len + .saturating_sub(transfer_len_remaining as usize); + let success = completion_code == CC_SUCCESS || completion_code == CC_SHORT_PACKET; + + match pending.kind { + PendingTransferKind::DeviceDescriptor8 { slot_id } => { + if !success { + println!( + "[xHCI] device descriptor transfer failed: slot={} code={}", + slot_id, completion_code + ); + return; + } + if let Some(dev_idx) = find_device_index(runtime, slot_id) { + let desc = runtime.devices[dev_idx].descriptor_buf.read_bytes(0, 8); + if desc.len() >= 8 { + let mps = desc[7]; + if matches!(mps, 8 | 16 | 32 | 64) { + runtime.devices[dev_idx].ep0_max_packet = u16::from(mps); + } + println!( + "[xHCI] device descriptor: slot={} max_packet0={}", + slot_id, runtime.devices[dev_idx].ep0_max_packet + ); + } + } + let _ = submit_get_config_header(runtime, slot_id); + } + PendingTransferKind::ConfigHeader { slot_id } => { + if !success { + println!( + "[xHCI] config header transfer failed: slot={} code={}", + slot_id, completion_code + ); + return; + } + if let Some(dev_idx) = find_device_index(runtime, slot_id) { + let head = runtime.devices[dev_idx].descriptor_buf.read_bytes(0, 9); + if head.len() >= 4 { + let total = u16::from_le_bytes([head[2], head[3]]); + let clamped = usize::from(total).min(runtime.devices[dev_idx].descriptor_buf.size); + if clamped >= 9 { + let _ = submit_get_config_full(runtime, slot_id, clamped as u16); + } + } + } + } + PendingTransferKind::ConfigFull { slot_id } => { + if !success { + println!( + "[xHCI] full config transfer failed: slot={} code={}", + slot_id, completion_code + ); + return; + } + let Some(dev_idx) = find_device_index(runtime, slot_id) else { + return; + }; + let config_bytes = runtime.devices[dev_idx] + .descriptor_buf + .read_bytes(0, pending.data_len); + let Some(hid_cfg) = parse_hid_endpoint_from_config(&config_bytes) else { + println!("[xHCI] HID interrupt endpoint not found in config descriptor"); + return; + }; + let config_value = config_bytes.get(5).copied().unwrap_or(1); + if hid_cfg.kind == HidReportKind::Unknown && hid_cfg.report_desc_len > 0 { + if submit_get_hid_report_descriptor(runtime, slot_id, config_value, hid_cfg) { + return; + } + } + setup_hid_endpoint(runtime, slot_id, hid_cfg, config_value); + } + PendingTransferKind::HidReportDescriptor { + slot_id, + config_value, + mut hid_cfg, + } => { + if !success { + println!( + "[xHCI] HID report descriptor transfer failed: slot={} code={}", + slot_id, completion_code + ); + return; + } + let Some(dev_idx) = find_device_index(runtime, slot_id) else { + return; + }; + let read_len = if actual_len == 0 { + pending.data_len + } else { + core::cmp::min(actual_len, pending.data_len) + }; + let report_desc = runtime.devices[dev_idx].descriptor_buf.read_bytes(0, read_len); + let (classified, report_id) = classify_hid_from_report_descriptor(&report_desc); + if hid_cfg.kind == HidReportKind::Unknown { + hid_cfg.kind = classified; + } + if hid_cfg.report_id == 0 { + hid_cfg.report_id = report_id; + } + println!( + "[xHCI] HID report descriptor: slot={} if={} len={} classified={} report_id={}", + slot_id, + hid_cfg.interface_number, + read_len, + match hid_cfg.kind { + HidReportKind::Keyboard => "keyboard", + HidReportKind::Mouse => "mouse", + HidReportKind::Unknown => "unknown", + }, + hid_cfg.report_id + ); + setup_hid_endpoint(runtime, slot_id, hid_cfg, config_value); + } + PendingTransferKind::SetConfiguration { slot_id } => { + if !success { + println!( + "[xHCI] SET_CONFIGURATION failed: slot={} code={}", + slot_id, completion_code + ); + return; + } + println!("[xHCI] SET_CONFIGURATION completed: slot={}", slot_id); + let _ = submit_configure_endpoint_command(runtime, slot_id); + } + PendingTransferKind::InterruptIn { + slot_id, + dci, + retries, + } => { + if !success { + println!( + "[xHCI] interrupt IN transfer failed: slot={} dci={} code={}", + slot_id, dci, completion_code + ); + } + if success { + if let Some(dev_idx) = find_device_index(runtime, slot_id) { + let (kind, report_id, report) = + if let Some(hid) = runtime.devices[dev_idx].hid_ep.as_ref() { + let read_len = if actual_len == 0 { + hid.report_len + } else { + core::cmp::min(actual_len, hid.report_len) + }; + ( + hid.config.kind, + hid.config.report_id, + hid.report_buf.read_bytes(0, read_len), + ) + } else { + (HidReportKind::Unknown, 0, Vec::new()) + }; + if !report.is_empty() { + let payload: &[u8] = if report_id != 0 { + if report[0] != report_id { + &[] + } else { + &report[1..] + } + } else { + &report + }; + if !payload.is_empty() { + parse_hid_report(slot_id, dci, payload, kind, &mut runtime.hid); + } + } + } + let _ = submit_interrupt_in_transfer(runtime, slot_id, dci, 0); + return; + } + + if completion_code == CC_STALL_ERROR { + println!( + "[xHCI] interrupt IN aborted (STALL): slot={} dci={} code={}", + slot_id, dci, completion_code + ); + } else { + println!( + "[xHCI] interrupt IN aborted: slot={} dci={} code={} retries={}", + slot_id, dci, completion_code, retries + ); + } + } + } +} + +fn handle_port_status_change_event(runtime: &mut XhciRuntime, port_id: u8, completion_code: u8) { + println!( + "[xHCI] port status change event: port={} code={}", + port_id, completion_code + ); + dump_ports(&runtime.regs); + let portsc = mmio_read_u32(runtime.regs.base, portsc_offset(&runtime.regs, port_id)); + if (portsc & 1) != 0 { + submit_enable_slot_for_port(runtime, port_id); + } + let clear_mask = portsc & PORTSC_CHANGE_MASK; + if clear_mask != 0 { + mmio_write_u32( + runtime.regs.base, + portsc_offset(&runtime.regs, port_id), + clear_mask, + ); + } +} + +fn poll_xhci_events(runtime: &mut XhciRuntime) -> bool { + let mut handled = false; + let ir_base = runtime.regs.rt_off + RT_IR0_BASE; + + for _ in 0..EVENT_DISPATCH_BUDGET { + let Some(event) = runtime.event_ring.pop_event() else { + break; + }; + handled = true; + + let trb_type = (event[3] >> 10) & 0x3F; + let completion_code = ((event[2] >> 24) & 0xFF) as u8; + let slot_id = ((event[3] >> 24) & 0xFF) as u8; + let ep_id = ((event[3] >> 16) & 0x1F) as u8; + + match trb_type { + TRB_TYPE_COMMAND_COMPLETION => { + let cmd_ptr = u64::from(event[0]) | (u64::from(event[1]) << 32); + handle_command_completion_event(runtime, cmd_ptr, completion_code, slot_id); + } + TRB_TYPE_PORT_STATUS_CHANGE => { + let port_id = ((event[0] >> 24) & 0xFF) as u8; + handle_port_status_change_event(runtime, port_id, completion_code); + } + TRB_TYPE_TRANSFER_EVENT => { + let transfer_ptr = u64::from(event[0]) | (u64::from(event[1]) << 32); + let transfer_len_remaining = event[2] & 0x00FF_FFFF; + handle_transfer_event( + runtime, + transfer_ptr, + completion_code, + transfer_len_remaining, + slot_id, + ep_id, + ); + } + _ => { + println!( + "[xHCI] event: type={} code={} slot={} ep={}", + trb_type, completion_code, slot_id, ep_id + ); + } + } + + mmio_write_u64( + runtime.regs.base, + ir_base + IR_ERDP, + runtime.event_ring.dequeue_phys() | ERDP_EHB, + ); + } + + if handled { + mmio_write_u32(runtime.regs.base, runtime.regs.op_base + OP_USBSTS, USBSTS_EINT); + let iman = mmio_read_u32(runtime.regs.base, ir_base + IR_IMAN); + mmio_write_u32(runtime.regs.base, ir_base + IR_IMAN, iman | IMAN_IP | IMAN_IE); + } + + handled +} + +fn decode_port_speed(speed: u8) -> &'static str { + match speed { + 1 => "full", + 2 => "low", + 3 => "high", + 4 => "super", + 5 => "super+", + _ => "unknown", + } +} + +fn dump_ports(regs: &XhciRegs) { + if regs.max_ports == 0 { + println!("[xHCI] no root hub ports reported"); + return; + } + + for port_index in 0..usize::from(regs.max_ports) { + let portsc_off = regs.op_base + 0x400 + port_index * 0x10; + let portsc = mmio_read_u32(regs.base, portsc_off); + let connected = (portsc & (1 << 0)) != 0; + let enabled = (portsc & (1 << 1)) != 0; + let connect_change = (portsc & (1 << 17)) != 0; + let speed = ((portsc >> 10) & 0x0F) as u8; + + if connected || enabled || connect_change { + println!( + "[xHCI] port {:02}: connected={} enabled={} speed={}({})", + port_index + 1, + connected as u8, + enabled as u8, + speed, + decode_port_speed(speed) + ); + } + } +} + +fn init_xhci_controller() -> Option { + let Some(controller) = find_xhci_controller() else { + println!("[xHCI] no controller found on PCI bus"); + return None; + }; + + println!( + "[xHCI] controller {:02x}:{:02x}.{} vendor={:04x} device={:04x}", + controller.bdf.bus, + controller.bdf.device, + controller.bdf.function, + controller.vendor_id, + controller.device_id + ); + println!( + "[xHCI] BAR0={:#010x} BAR1={:#010x} {} MMIO={:#018x}", + controller.bar0, + controller.bar1, + if controller.bar_is_64bit { "64-bit" } else { "32-bit" }, + controller.mmio_base + ); + + let mapped = match map_xhci_mmio(&controller) { + Ok(ptr) => ptr, + Err(err) => { + println!("[xHCI] map mmio failed: {:#x}", err); + return None; + } + }; + + let Some(regs) = read_xhci_regs(mapped) else { + println!("[xHCI] invalid capability header"); + return None; + }; + + println!( + "[xHCI] version={:x}.{:02x} caplen=0x{:x} max_slots={} max_ports={}", + regs.hci_version >> 8, + regs.hci_version & 0xFF, + regs.cap_len, + regs.max_slots, + regs.max_ports + ); + println!( + "[xHCI] runtime_off=0x{:x} doorbell_off=0x{:x}", + regs.rt_off, + regs.db_off + ); + + if reset_xhci(®s) { + println!("[xHCI] controller halted+reset complete"); + } else { + println!("[xHCI] controller reset skipped due to timeout"); + return None; + } + + dump_ports(®s); + + let command_ring = match CommandRing::new() { + Ok(r) => r, + Err(err) => { + println!("[xHCI] command ring alloc failed: {:#x}", err); + return None; + } + }; + let event_ring = match EventRing::new() { + Ok(r) => r, + Err(err) => { + println!("[xHCI] event ring alloc failed: {:#x}", err); + return None; + } + }; + let dcbaa = match DmaPage::alloc(PAGE_SIZE) { + Ok(p) => p, + Err(err) => { + println!("[xHCI] DCBAA alloc failed: {:#x}", err); + return None; + } + }; + dcbaa.zero(); + + setup_command_ring_register(®s, &command_ring); + setup_interrupter(®s, &event_ring); + mmio_write_u64(regs.base, regs.op_base + OP_DCBAAP, dcbaa.phys); + if !start_xhci(®s) { + println!("[xHCI] failed to run controller"); + return None; + } + + let mut runtime = XhciRuntime { + regs, + dcbaa, + command_ring, + event_ring, + devices: Vec::new(), + pending_commands: Vec::new(), + pending_transfers: Vec::new(), + enumerating_port: None, + hid: HidParserState::default(), + }; + let _ = submit_command( + &mut runtime, + [0, 0, 0, TRB_TYPE_NOOP_CMD << 10], + PendingCommandKind::Noop, + ); + submit_next_connected_port(&mut runtime); + println!("[xHCI] command/event ring + interrupter configured"); + + Some(runtime) +} + +fn run_input_monitor_loop(mut xhci: Option) { + loop { + let handled_any = if let Some(runtime) = xhci.as_mut() { + poll_xhci_events(runtime) + } else { + false + }; + + if !handled_any { + time::sleep_ms(2); + } + } +} + +fn main() { + println!("[xHCI] driver started"); + let xhci_runtime = init_xhci_controller(); + println!("[xHCI] USB HID injection mode enabled"); + run_input_monitor_loop(xhci_runtime); +} diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index ac04fbe..0000000 --- a/src/error.rs +++ /dev/null @@ -1,140 +0,0 @@ -//! SwiftCoreエラー型定義 -//! -//! すべてのカーネルエラーをResult型で表現し、panicを禁止 - -use core::fmt; - -/// トップレベルエラー型 -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum KernelError { - /// メモリエラー - Memory(MemoryError), - /// プロセスエラー - Process(ProcessError), - /// デバイスエラー - Device(DeviceError), - /// 無効なパラメータ - InvalidParam, - /// 未実装の機能 - NotImplemented, -} - -/// メモリ関連のエラー -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum MemoryError { - /// 利用可能なメモリがない - OutOfMemory, - /// 無効なアドレスへのアクセス - InvalidAddress, - /// メモリ保護違反 - PermissionDenied, - /// 既にマップされたアドレス - AlreadyMapped, - /// マップされていないアドレス - NotMapped, - /// アライメントエラー - AlignmentError, -} - -/// プロセス関連のエラー -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ProcessError { - /// 無効なプロセスID - InvalidPid, - /// プロセスが見つからない - ProcessNotFound, - /// ゾンビプロセス - ZombieProcess, - /// プロセス数の上限に達した - MaxProcessesReached, - /// 権限不足 - InsufficientPrivilege, -} - -/// デバイス関連のエラー -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum DeviceError { - /// デバイスがビジー状態 - Busy, - /// ハードウェアエラー - HardwareFailure, - /// タイムアウト - Timeout, - /// 不正な操作 - InvalidOperation, -} - -impl KernelError { - /// このエラーが致命的かどうか - /// - /// 致命的なエラーは回復不能であり、システムの継続が不可能 - /// - `MemoryError::OutOfMemory` - /// - `DeviceError::HardwareFailure` - pub fn is_fatal(&self) -> bool { - match self { - KernelError::Memory(MemoryError::OutOfMemory) => true, - KernelError::Device(DeviceError::HardwareFailure) => true, - _ => false, - } - } - - /// このエラーがリトライ可能かどうか - /// - /// リトライ可能なエラーは、一時的な問題であり、再試行によって成功する可能性がある - /// - `DeviceError::Busy` - /// - `DeviceError::Timeout` - pub fn is_retryable(&self) -> bool { - match self { - KernelError::Device(DeviceError::Busy) => true, - KernelError::Device(DeviceError::Timeout) => true, - _ => false, - } - } -} - -impl fmt::Display for KernelError { - /// エラーをフォーマット表示 - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - KernelError::Memory(e) => write!(f, "Memory error: {:?}", e), - KernelError::Process(e) => write!(f, "Process error: {:?}", e), - KernelError::Device(e) => write!(f, "Device error: {:?}", e), - KernelError::InvalidParam => write!(f, "Invalid parameter"), - KernelError::NotImplemented => write!(f, "Not implemented"), - } - } -} - -impl fmt::Display for MemoryError { - /// エラーをフォーマット表示 - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - MemoryError::OutOfMemory => write!(f, "Out of memory"), - MemoryError::InvalidAddress => write!(f, "Invalid address"), - MemoryError::PermissionDenied => write!(f, "Permission denied"), - MemoryError::AlreadyMapped => write!(f, "Already mapped"), - MemoryError::NotMapped => write!(f, "Not mapped"), - MemoryError::AlignmentError => write!(f, "Alignment error"), - } - } -} - -/// 結果型のエイリアス -pub type Result = core::result::Result; - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_error_is_fatal() { - assert!(KernelError::Memory(MemoryError::OutOfMemory).is_fatal()); - assert!(!KernelError::Memory(MemoryError::InvalidAddress).is_fatal()); - } - - #[test] - fn test_error_is_retryable() { - assert!(KernelError::Device(DeviceError::Busy).is_retryable()); - assert!(!KernelError::Memory(MemoryError::OutOfMemory).is_retryable()); - } -} diff --git a/src/interrupt/idt.rs b/src/interrupt/idt.rs deleted file mode 100644 index f9a85de..0000000 --- a/src/interrupt/idt.rs +++ /dev/null @@ -1,263 +0,0 @@ -//! IDT (Interrupt Descriptor Table) 管理 -//! -//! IDTの初期化と例外ハンドラの定義 - -use crate::{debug, error, mem::gdt, warn}; -use spin::Once; -use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame}; - -static IDT: Once = Once::new(); - -/// IDTを初期化 -pub fn init() { - debug!("Initializing IDT..."); - - let idt = IDT.call_once(|| { - let mut idt = InterruptDescriptorTable::new(); - - // CPU例外ハンドラ - idt.divide_error.set_handler_fn(divide_error_handler); - idt.debug.set_handler_fn(debug_handler); - idt.non_maskable_interrupt.set_handler_fn(nmi_handler); - idt.breakpoint.set_handler_fn(breakpoint_handler); - idt.overflow.set_handler_fn(overflow_handler); - idt.bound_range_exceeded - .set_handler_fn(bound_range_exceeded_handler); - idt.invalid_opcode.set_handler_fn(invalid_opcode_handler); - idt.device_not_available - .set_handler_fn(device_not_available_handler); - - // ダブルフォルトハンドラ(専用スタック使用) - unsafe { - idt.double_fault - .set_handler_fn(double_fault_handler) - .set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX); - } - - idt.invalid_tss.set_handler_fn(invalid_tss_handler); - idt.segment_not_present - .set_handler_fn(segment_not_present_handler); - idt.stack_segment_fault - .set_handler_fn(stack_segment_fault_handler); - idt.general_protection_fault - .set_handler_fn(general_protection_fault_handler); - idt.page_fault.set_handler_fn(page_fault_handler); - idt.x87_floating_point - .set_handler_fn(x87_floating_point_handler); - idt.alignment_check.set_handler_fn(alignment_check_handler); - idt.machine_check.set_handler_fn(machine_check_handler); - idt.simd_floating_point - .set_handler_fn(simd_floating_point_handler); - idt.virtualization.set_handler_fn(virtualization_handler); - - // ハードウェア割り込みハンドラ(32-47番) - idt[32].set_handler_fn(super::timer::timer_interrupt_handler); // Timer - idt[33].set_handler_fn(keyboard_interrupt_handler); // Keyboard - - // それ以外のハードウェア割り込みはとりあえずスタブ - for i in 34..48 { - idt[i].set_handler_fn(generic_interrupt_handler); - } - - // 48-255番も念のため設定(未使用の割り込みベクタ) - for i in 48..=255 { - idt[i].set_handler_fn(generic_interrupt_handler); - } - - idt - }); - - idt.load(); - - // IDTが正しくロードされたか確認 - use x86_64::instructions::tables::sidt; - let idtr = sidt(); - debug!( - "IDT loaded: base={:p}, limit={}", - idtr.base.as_ptr::(), - idtr.limit - ); -} - -// ======================================== -// CPU例外ハンドラ -// ======================================== - -extern "x86-interrupt" fn divide_error_handler(stack_frame: InterruptStackFrame) { - error!("EXCEPTION: DIVIDE ERROR"); - debug!("{:#?}", stack_frame); - halt_cpu(); -} - -extern "x86-interrupt" fn debug_handler(stack_frame: InterruptStackFrame) { - debug!("EXCEPTION: DEBUG"); - debug!("{:#?}", stack_frame); -} - -extern "x86-interrupt" fn nmi_handler(stack_frame: InterruptStackFrame) { - error!("EXCEPTION: NON-MASKABLE INTERRUPT"); - debug!("{:#?}", stack_frame); - halt_cpu(); -} - -extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) { - warn!("EXCEPTION: BREAKPOINT"); - debug!("{:#?}", stack_frame); -} - -extern "x86-interrupt" fn overflow_handler(stack_frame: InterruptStackFrame) { - error!("EXCEPTION: OVERFLOW"); - debug!("{:#?}", stack_frame); - halt_cpu(); -} - -extern "x86-interrupt" fn bound_range_exceeded_handler(stack_frame: InterruptStackFrame) { - error!("EXCEPTION: BOUND RANGE EXCEEDED"); - debug!("{:#?}", stack_frame); - halt_cpu(); -} - -extern "x86-interrupt" fn invalid_opcode_handler(stack_frame: InterruptStackFrame) { - error!("EXCEPTION: INVALID OPCODE"); - debug!("{:#?}", stack_frame); - halt_cpu(); -} - -extern "x86-interrupt" fn device_not_available_handler(stack_frame: InterruptStackFrame) { - error!("EXCEPTION: DEVICE NOT AVAILABLE"); - debug!("{:#?}", stack_frame); - halt_cpu(); -} - -extern "x86-interrupt" fn double_fault_handler( - stack_frame: InterruptStackFrame, - error_code: u64, -) -> ! { - error!("EXCEPTION: DOUBLE FAULT"); - error!("Error code: {:#x}", error_code); - debug!("{:#?}", stack_frame); - halt_forever(); -} - -extern "x86-interrupt" fn invalid_tss_handler(stack_frame: InterruptStackFrame, error_code: u64) { - error!("EXCEPTION: INVALID TSS"); - error!("Error code: {:#x}", error_code); - debug!("{:#?}", stack_frame); - halt_cpu(); -} - -extern "x86-interrupt" fn segment_not_present_handler( - stack_frame: InterruptStackFrame, - error_code: u64, -) { - error!("EXCEPTION: SEGMENT NOT PRESENT"); - error!("Error code: {:#x}", error_code); - debug!("{:#?}", stack_frame); - halt_cpu(); -} - -extern "x86-interrupt" fn stack_segment_fault_handler( - stack_frame: InterruptStackFrame, - error_code: u64, -) { - error!("EXCEPTION: STACK SEGMENT FAULT"); - error!("Error code: {:#x}", error_code); - debug!("{:#?}", stack_frame); - halt_cpu(); -} - -extern "x86-interrupt" fn general_protection_fault_handler( - stack_frame: InterruptStackFrame, - error_code: u64, -) { - error!("EXCEPTION: GENERAL PROTECTION FAULT"); - error!("Error code: {:#x}", error_code); - debug!("{:#?}", stack_frame); - halt_cpu(); -} - -extern "x86-interrupt" fn page_fault_handler( - stack_frame: InterruptStackFrame, - error_code: x86_64::structures::idt::PageFaultErrorCode, -) { - use x86_64::registers::control::Cr2; - error!("EXCEPTION: PAGE FAULT"); - error!("Accessed address: {:?}", Cr2::read()); - error!("Error code: {:?}", error_code); - debug!("{:#?}", stack_frame); - halt_cpu(); -} - -extern "x86-interrupt" fn x87_floating_point_handler(stack_frame: InterruptStackFrame) { - error!("EXCEPTION: X87 FLOATING POINT"); - debug!("{:#?}", stack_frame); - halt_cpu(); -} - -extern "x86-interrupt" fn alignment_check_handler( - stack_frame: InterruptStackFrame, - error_code: u64, -) { - error!("EXCEPTION: ALIGNMENT CHECK"); - error!("Error code: {:#x}", error_code); - debug!("{:#?}", stack_frame); - halt_cpu(); -} - -extern "x86-interrupt" fn machine_check_handler(stack_frame: InterruptStackFrame) -> ! { - error!("EXCEPTION: MACHINE CHECK"); - debug!("{:#?}", stack_frame); - halt_forever(); -} - -extern "x86-interrupt" fn simd_floating_point_handler(stack_frame: InterruptStackFrame) { - error!("EXCEPTION: SIMD FLOATING POINT"); - debug!("{:#?}", stack_frame); - halt_cpu(); -} - -extern "x86-interrupt" fn virtualization_handler(stack_frame: InterruptStackFrame) { - error!("EXCEPTION: VIRTUALIZATION"); - debug!("{:#?}", stack_frame); - halt_cpu(); -} - -// ======================================== -// ハードウェア割り込みハンドラ -// ======================================== - -extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStackFrame) { - debug!("INTERRUPT: KEYBOARD"); - // キーボード入力を処理 - // TODO: キーボードドライバ実装 - super::send_eoi(33); -} - -extern "x86-interrupt" fn generic_interrupt_handler(_stack_frame: InterruptStackFrame) { - debug!("INTERRUPT: GENERIC"); - // EOIを送信 - unsafe { - super::pic::PIC_SLAVE.end_of_interrupt(); - super::pic::PIC_MASTER.end_of_interrupt(); - } -} - -// ======================================== -// ヘルパー関数 -// ======================================== - -/// CPU割り込みを無効化してシステムを停止 -fn halt_cpu() { - x86_64::instructions::interrupts::disable(); - loop { - x86_64::instructions::hlt(); - } -} - -/// CPU割り込みを無効化してシステムを停止(戻らない) -fn halt_forever() -> ! { - x86_64::instructions::interrupts::disable(); - loop { - x86_64::instructions::hlt(); - } -} diff --git a/src/kernel.rs b/src/kernel.rs deleted file mode 100644 index 35faae1..0000000 --- a/src/kernel.rs +++ /dev/null @@ -1,128 +0,0 @@ -//! カーネルエントリーポイント - -use crate::{ - debug, info, interrupt, mem, util, vprintln, BootInfo, KernelError, MemoryRegion, Result, -}; - -/// カーネルエントリーポイント -#[no_mangle] -pub extern "C" fn kernel_entry(boot_info: &'static BootInfo) -> ! { - util::console::init(); - - // フレームバッファ初期化 - util::vga::init( - boot_info.framebuffer_addr, - boot_info.screen_width, - boot_info.screen_height, - boot_info.stride, - ); - - vprintln!("SwiftCore v0.1.0"); - vprintln!("Framebuffer: {:#x}", boot_info.framebuffer_addr); - vprintln!( - "Resolution: {}x{}", - boot_info.screen_width, - boot_info.screen_height - ); - vprintln!(""); - - info!( - "Physical memory offset: {:#x}", - boot_info.physical_memory_offset - ); - - // メモリマップを取得 - let memory_map = unsafe { - core::slice::from_raw_parts( - boot_info.memory_map_addr as *const MemoryRegion, - boot_info.memory_map_len, - ) - }; - - info!("Memory map entries: {}", boot_info.memory_map_len); - for (i, region) in memory_map.iter().enumerate() { - debug!( - " Region {}: {:#x} - {:#x} ({:?})", - i, - region.start, - region.start + region.len, - region.region_type - ); - } - - // カーネル初期化を実行 - match kernel_main(boot_info, memory_map) { - Ok(_) => { - // 正常に完了(通常は到達しない) - info!("Kernel shutdown gracefully"); - halt_forever(); - } - Err(e) => { - // エラー時の処理 - handle_kernel_error(e); - halt_forever(); - } - } -} - -/// カーネルメイン処理 -fn kernel_main(boot_info: &'static BootInfo, memory_map: &'static [MemoryRegion]) -> Result<()> { - info!("Initializing kernel..."); - - // メモリ管理初期化 - mem::init(boot_info.physical_memory_offset); - mem::init_frame_allocator(memory_map)?; - - info!("Kernel ready"); - - // 割込みを有効化 - debug!("Enabling interrupts..."); - unsafe { - x86_64::instructions::interrupts::enable(); - } - - // タイマー割り込みを設定(10ms周期) - interrupt::init_pit(); - interrupt::enable_timer_interrupt(); - - info!("Timer interrupt configured (10ms period)"); - - // 無限ループ(永遠に実行) - info!("Entering idle loop"); - loop { - x86_64::instructions::hlt(); - } -} - -/// カーネルエラーを処理 -fn handle_kernel_error(error: KernelError) { - use crate::error::*; - - crate::warn!("KERNEL ERROR: {}", error); - debug!("Is fatal: {}", error.is_fatal()); - debug!("Is retryable: {}", error.is_retryable()); - - match error { - KernelError::Memory(mem_err) => { - crate::error!("Memory error: {:?}", mem_err); - } - KernelError::Process(proc_err) => { - crate::error!("Process error: {:?}", proc_err); - } - KernelError::Device(dev_err) => { - crate::error!("Device error: {:?}", dev_err); - } - _ => { - crate::error!("Unknown error: {:?}", error); - } - } - - info!("System halted."); -} - -/// システムを無限ループで停止 -fn halt_forever() -> ! { - loop { - x86_64::instructions::hlt(); - } -} diff --git a/src/lib b/src/lib new file mode 160000 index 0000000..a7c6149 --- /dev/null +++ b/src/lib @@ -0,0 +1 @@ +Subproject commit a7c614986ab2e21a342afe3105fb88985a653996 diff --git a/src/linker.ld b/src/linker.ld new file mode 100644 index 0000000..b734fbb --- /dev/null +++ b/src/linker.ld @@ -0,0 +1,33 @@ +OUTPUT_FORMAT("elf64-x86-64") +OUTPUT_ARCH(i386:x86-64) +ENTRY(_start) + +SECTIONS +{ + . = 0x400000; + + .text : { + __text_start = .; + *(.text._start) + *(.text .text.*) + __text_end = .; + } + + .rodata : ALIGN(4K) { + *(.rodata .rodata.*) + } + + .data : ALIGN(4K) { + *(.data .data.*) + } + + .bss : ALIGN(4K) { + *(COMMON) + *(.bss .bss.*) + } + + /DISCARD/ : { + *(.eh_frame) + *(.comment) + } +} diff --git a/src/mem/frame.rs b/src/mem/frame.rs deleted file mode 100644 index bd16091..0000000 --- a/src/mem/frame.rs +++ /dev/null @@ -1,91 +0,0 @@ -//! 物理フレームアロケータ -//! -//! 4KBページ単位で物理メモリを管理 - -use crate::{MemoryRegion, MemoryType, error::{KernelError, MemoryError, Result}}; -use spin::Mutex; -use x86_64::{ - structures::paging::{FrameAllocator, PhysFrame, Size4KiB}, - PhysAddr, -}; - -/// グローバルフレームアロケータ -pub static FRAME_ALLOCATOR: Mutex> = Mutex::new(None); - -/// ビットマップベースのフレームアロケータ -pub struct BitmapFrameAllocator { - /// メモリマップ - memory_map: &'static [MemoryRegion], - /// 次に割り当てるフレーム - next_frame: usize, -} - -impl BitmapFrameAllocator { - /// 新しいフレームアロケータを作成 - pub fn new(memory_map: &'static [MemoryRegion]) -> Self { - Self { - memory_map, - next_frame: 0, - } - } - - /// 使用可能な物理メモリの総量を計算(バイト) - pub fn usable_memory(&self) -> u64 { - self.memory_map - .iter() - .filter(|r| r.region_type == MemoryType::Usable) - .map(|r| r.len) - .sum() - } - - /// 使用可能なフレーム数を計算 - pub fn usable_frames(&self) -> usize { - (self.usable_memory() / 4096) as usize - } - - /// 使用可能なフレームのイテレータを返す - fn usable_frames_iter(&self) -> impl Iterator + '_ { - self.memory_map - .iter() - .filter(|r| r.region_type == MemoryType::Usable) - .flat_map(|r| { - let start_addr = r.start; - let end_addr = r.start + r.len; - let start_frame = start_addr / 4096; - let end_frame = end_addr / 4096; - (start_frame..end_frame) - .map(|f| PhysFrame::containing_address(PhysAddr::new(f * 4096))) - }) - } -} - -unsafe impl FrameAllocator for BitmapFrameAllocator { - fn allocate_frame(&mut self) -> Option { - let frame = self.usable_frames_iter().nth(self.next_frame); - self.next_frame += 1; - frame - } -} - -/// フレームアロケータを初期化 -pub fn init(memory_map: &'static [MemoryRegion]) { - let allocator = BitmapFrameAllocator::new(memory_map); - *FRAME_ALLOCATOR.lock() = Some(allocator); -} - -/// フレームを割り当て -pub fn allocate_frame() -> Result { - FRAME_ALLOCATOR - .lock() - .as_mut() - .and_then(|a| a.allocate_frame()) - .ok_or(KernelError::Memory(MemoryError::OutOfMemory)) -} - -/// 使用可能なメモリ情報を取得 -pub fn get_memory_info() -> Option<(u64, usize)> { - FRAME_ALLOCATOR - .lock() - .as_ref() - .map(|a| (a.usable_memory(), a.usable_frames())) -} diff --git a/src/mem/gdt.rs b/src/mem/gdt.rs deleted file mode 100644 index 2e82580..0000000 --- a/src/mem/gdt.rs +++ /dev/null @@ -1,96 +0,0 @@ -//! GDT管理モジュール -//! -//! Global Descriptor Tableを管理 - -use crate::mem::tss; -use crate::sprintln; -use core::arch::asm; -use spin::Once; -use x86_64::instructions::tables::load_tss; -use x86_64::structures::gdt::{Descriptor, GlobalDescriptorTable, SegmentSelector}; - -/// ダブルフォルト用ISTインデックス(TSSと同じ値を使用) -pub const DOUBLE_FAULT_IST_INDEX: u16 = tss::DOUBLE_FAULT_IST_INDEX; - -static GDT: Once<(GlobalDescriptorTable, Selectors)> = Once::new(); - -/// GDTセレクタ -#[allow(unused)] -struct Selectors { - code_selector: SegmentSelector, - data_selector: SegmentSelector, - tss_selector: SegmentSelector, -} - -/// GDTを初期化 -pub fn init() { - sprintln!("Initializing GDT..."); - - // TSSを初期化 - let tss = tss::init(); - - // GDTを初期化 - let (gdt, selectors) = GDT.call_once(|| { - let mut gdt = GlobalDescriptorTable::new(); - let code_selector = gdt.append(Descriptor::kernel_code_segment()); - let data_selector = gdt.append(Descriptor::kernel_data_segment()); - let tss_selector = gdt.append(Descriptor::tss_segment(tss)); - - sprintln!("GDT entries created:"); - sprintln!(" Code selector: {:?}", code_selector); - sprintln!(" Data selector: {:?}", data_selector); - sprintln!(" TSS selector: {:?}", tss_selector); - - ( - gdt, - Selectors { - code_selector, - data_selector, - tss_selector, - }, - ) - }); - - unsafe { - // GDTをロード - gdt.load(); - - // Boot Services終了後はカーネルのセグメントに切り替え - set_cs(selectors.code_selector); - set_data_segments(selectors.data_selector); - - // TSSをロード - load_tss(selectors.tss_selector); - } - - sprintln!("GDT loaded with TSS"); -} - -#[allow(unused)] -/// データセグメントレジスタを設定 -unsafe fn set_data_segments(selector: SegmentSelector) { - asm!( - "mov ds, {0:x}", - "mov es, {0:x}", - "mov fs, {0:x}", - "mov gs, {0:x}", - "mov ss, {0:x}", - in(reg) selector.0, - options(nostack, preserves_flags) - ); -} - -#[allow(unused)] -/// コードセグメントを設定(far returnを使用) -unsafe fn set_cs(selector: SegmentSelector) { - asm!( - "push {sel}", - "lea {tmp}, [rip + 2f]", - "push {tmp}", - "retfq", - "2:", - sel = in(reg) u64::from(selector.0), - tmp = lateout(reg) _, - options(preserves_flags) - ); -} diff --git a/src/mem/mod.rs b/src/mem/mod.rs deleted file mode 100644 index a8308c7..0000000 --- a/src/mem/mod.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! メモリ管理モジュール -//! -//! GDT、TSS、ページング、フレームアロケータ - -use crate::{interrupt, sprintln, MemoryRegion, Result}; - -pub mod frame; -pub mod gdt; -pub mod paging; -pub mod tss; - -pub fn init(physical_memory_offset: u64) { - sprintln!("Initializing memory..."); - - // 割り込みを確実に無効化 - x86_64::instructions::interrupts::disable(); - - paging::init(physical_memory_offset); - gdt::init(); - interrupt::init_idt(); - - // PITを停止してからPICを初期化 - interrupt::disable_pit(); - interrupt::init_pic(); - - sprintln!("Memory initialized"); -} - -/// メモリマップを設定してフレームアロケータを初期化 -pub fn init_frame_allocator(memory_map: &'static [MemoryRegion]) -> Result<()> { - frame::init(memory_map); - - if let Some((total, frames)) = frame::get_memory_info() { - sprintln!( - "Physical memory: {} MB ({} frames)", - total / 1024 / 1024, - frames - ); - } - - Ok(()) -} diff --git a/src/mem/paging.rs b/src/mem/paging.rs deleted file mode 100644 index b49394f..0000000 --- a/src/mem/paging.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! ページング管理モジュール -//! -//! 仮想メモリとページテーブル管理 - -use crate::error::{KernelError, MemoryError, Result}; -use crate::sprintln; -use spin::Mutex; -use x86_64::{ - structures::paging::{ - Mapper, OffsetPageTable, Page, PageTable, PageTableFlags, PhysFrame, Size4KiB, - }, - VirtAddr, -}; - -static PAGE_TABLE: Mutex>> = Mutex::new(None); - -/// ページングシステムを初期化 -pub fn init(physical_memory_offset: u64) { - sprintln!("Initializing paging..."); - - unsafe { - let level_4_table = active_level_4_table(physical_memory_offset); - let page_table = OffsetPageTable::new(level_4_table, VirtAddr::new(physical_memory_offset)); - *PAGE_TABLE.lock() = Some(page_table); - } - - sprintln!("Paging initialized"); -} - -/// アクティブなレベル4ページテーブルへの参照を取得 -unsafe fn active_level_4_table(physical_memory_offset: u64) -> &'static mut PageTable { - use x86_64::registers::control::Cr3; - - let (level_4_table_frame, _) = Cr3::read(); - let phys = level_4_table_frame.start_address(); - let virt = VirtAddr::new(phys.as_u64() + physical_memory_offset); - let page_table_ptr: *mut PageTable = virt.as_mut_ptr(); - - &mut *page_table_ptr -} - -/// ページをマップ -pub fn map_page(page: Page, frame: PhysFrame, flags: PageTableFlags) -> Result<()> { - let mut page_table_lock = PAGE_TABLE.lock(); - let page_table = page_table_lock - .as_mut() - .ok_or(KernelError::Memory(MemoryError::NotMapped))?; - - let mut allocator_lock = super::frame::FRAME_ALLOCATOR.lock(); - let allocator = allocator_lock - .as_mut() - .ok_or(KernelError::Memory(MemoryError::OutOfMemory))?; - - unsafe { - page_table - .map_to(page, frame, flags, allocator) - .map_err(|_| KernelError::Memory(MemoryError::InvalidAddress))? - .flush(); - } - - Ok(()) -} - -/// 仮想アドレスを物理アドレスに変換 -pub fn translate_addr(addr: VirtAddr) -> Option { - use x86_64::structures::paging::mapper::Translate; - - let page_table = PAGE_TABLE.lock(); - page_table.as_ref()?.translate_addr(addr) -} - -pub use x86_64::PhysAddr; diff --git a/src/mem/tss.rs b/src/mem/tss.rs deleted file mode 100644 index b8b20d8..0000000 --- a/src/mem/tss.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! TSS管理モジュール -//! -//! TSSを管理 - -use crate::sprintln; -use spin::Once; -use x86_64::structures::tss::TaskStateSegment; -use x86_64::VirtAddr; - -/// ダブルフォルト用ISTインデックス -pub const DOUBLE_FAULT_IST_INDEX: u16 = 0; - -static TSS: Once = Once::new(); - -/// TSSを初期化して返す -#[allow(unused_unsafe)] -pub fn init() -> &'static TaskStateSegment { - sprintln!("Initializing TSS..."); - - TSS.call_once(|| { - let mut tss = TaskStateSegment::new(); - - // ダブルフォルト用の専用スタックを設定 - tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX as usize] = { - const STACK_SIZE: usize = 4096 * 5; - static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE]; - - let stack_start = VirtAddr::from_ptr(unsafe { &raw const STACK }); - let stack_end = stack_start + STACK_SIZE as u64; - stack_end - }; - - sprintln!("TSS configured with IST[{}] stack", DOUBLE_FAULT_IST_INDEX); - tss - }) -} diff --git a/src/modules/disk/.cargo/config.toml b/src/modules/disk/.cargo/config.toml new file mode 100644 index 0000000..32f6c2c --- /dev/null +++ b/src/modules/disk/.cargo/config.toml @@ -0,0 +1,15 @@ +[build] +target = "../../../src/x86_64-mochios.json" + +[unstable] +build-std = ["core", "alloc", "panic_abort"] +json-target-spec = true + +[target.x86_64-mochios] +rustflags = [ + "-C", "link-arg=-nostdlib", + "-C", "link-arg=--no-gc-sections", + "-C", "relocation-model=pic", + "-C", "link-arg=-pie", + "-C", "llvm-args=-stackrealign", +] diff --git a/src/modules/disk/Cargo.toml b/src/modules/disk/Cargo.toml new file mode 100644 index 0000000..9b546d5 --- /dev/null +++ b/src/modules/disk/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "mcx-disk-module" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "disk_module" +path = "src/main.rs" +test = false +bench = false + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +lto = true +opt-level = "z" diff --git a/src/modules/disk/src/main.rs b/src/modules/disk/src/main.rs new file mode 100644 index 0000000..403b10f --- /dev/null +++ b/src/modules/disk/src/main.rs @@ -0,0 +1,35 @@ +#![no_std] +#![no_main] + +#[repr(C)] +pub struct McxDiskOps { + pub probe: extern "C" fn() -> i32, +} + +extern "C" fn probe() -> i32 { + 0 +} + +static DISK_OPS: McxDiskOps = McxDiskOps { probe }; + +#[no_mangle] +pub extern "C" fn mochi_module_init() -> *const McxDiskOps { + &DISK_OPS +} + +#[used] +static KEEP_INIT_REF: extern "C" fn() -> *const McxDiskOps = mochi_module_init; + +#[no_mangle] +pub extern "C" fn _start() -> ! { + loop { + core::hint::spin_loop(); + } +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop { + core::hint::spin_loop(); + } +} diff --git a/src/modules/fs/.cargo/config.toml b/src/modules/fs/.cargo/config.toml new file mode 100644 index 0000000..32f6c2c --- /dev/null +++ b/src/modules/fs/.cargo/config.toml @@ -0,0 +1,15 @@ +[build] +target = "../../../src/x86_64-mochios.json" + +[unstable] +build-std = ["core", "alloc", "panic_abort"] +json-target-spec = true + +[target.x86_64-mochios] +rustflags = [ + "-C", "link-arg=-nostdlib", + "-C", "link-arg=--no-gc-sections", + "-C", "relocation-model=pic", + "-C", "link-arg=-pie", + "-C", "llvm-args=-stackrealign", +] diff --git a/src/modules/fs/Cargo.toml b/src/modules/fs/Cargo.toml new file mode 100644 index 0000000..438ab06 --- /dev/null +++ b/src/modules/fs/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "mcx-fs-module" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "fs_module" +path = "src/main.rs" +test = false +bench = false + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +lto = true +opt-level = "z" diff --git a/src/modules/fs/src/main.rs b/src/modules/fs/src/main.rs new file mode 100644 index 0000000..fe6e5d8 --- /dev/null +++ b/src/modules/fs/src/main.rs @@ -0,0 +1,1014 @@ +#![no_std] +#![no_main] + +use core::arch::asm; +use core::cmp::min; +use core::cell::UnsafeCell; +use core::sync::atomic::{AtomicBool, Ordering}; + +const ATA_DATA: u16 = 0x1F0; +const ATA_SECTOR_COUNT: u16 = 0x1F2; +const ATA_LBA_LOW: u16 = 0x1F3; +const ATA_LBA_MID: u16 = 0x1F4; +const ATA_LBA_HIGH: u16 = 0x1F5; +const ATA_DRIVE_HEAD: u16 = 0x1F6; +const ATA_STATUS_CMD: u16 = 0x1F7; +const ATA_ALT_STATUS: u16 = 0x3F6; + +const ATA_CMD_READ_SECTORS: u8 = 0x20; +const ATA_CMD_READ_DMA: u8 = 0xC8; +const ATA_STATUS_ERR: u8 = 1 << 0; +const ATA_STATUS_DRQ: u8 = 1 << 3; +const ATA_STATUS_DF: u8 = 1 << 5; +const ATA_STATUS_BSY: u8 = 1 << 7; + +const EXT2_MAGIC: u16 = 0xEF53; +const BLOCK_CACHE_SLOTS: usize = 64; +const INODE_CACHE_SLOTS: usize = 128; +const PATH_CACHE_SLOTS: usize = 128; +const PATH_CACHE_MAX: usize = 192; + +const PCI_CFG_ADDR: u16 = 0xCF8; +const PCI_CFG_DATA: u16 = 0xCFC; +const PCI_CLASS_MASS_STORAGE: u8 = 0x01; +const PCI_SUBCLASS_IDE: u8 = 0x01; + +#[repr(C)] +pub struct McxBuffer { + pub ptr: *mut u8, + pub len: usize, +} + +#[repr(C)] +pub struct McxPath { + pub ptr: *const u8, + pub len: usize, +} + +#[repr(C)] +pub struct McxFsOps { + pub mount: extern "C" fn(device_id: u32) -> i32, + pub read: extern "C" fn(path: McxPath, offset: u64, buf: McxBuffer, out_read: *mut usize) -> i32, + pub stat: extern "C" fn(path: McxPath, out_mode: *mut u16, out_size: *mut u64) -> i32, + pub readdir: extern "C" fn(path: McxPath, buf: McxBuffer, out_len: *mut usize) -> i32, +} + +#[derive(Clone, Copy)] +struct FsMount { + drive: u8, // 0=master, 1=slave + block_size: u32, + sectors_per_block: u32, + inode_size: u16, + inodes_per_group: u32, + gdt_block: u32, +} + +#[derive(Clone, Copy)] +struct BlockCacheEntry { + valid: bool, + drive: u8, + block_num: u32, + data: [u8; 4096], +} + +impl BlockCacheEntry { + const fn empty() -> Self { + Self { + valid: false, + drive: 0, + block_num: 0, + data: [0u8; 4096], + } + } +} + +#[derive(Clone, Copy)] +struct InodeCacheEntry { + valid: bool, + drive: u8, + inode_num: u32, + inode: [u8; 256], +} + +impl InodeCacheEntry { + const fn empty() -> Self { + Self { + valid: false, + drive: 0, + inode_num: 0, + inode: [0u8; 256], + } + } +} + +#[derive(Clone, Copy)] +struct PathCacheEntry { + valid: bool, + drive: u8, + path_len: u16, + path_hash: u64, + inode_num: u32, + path: [u8; PATH_CACHE_MAX], +} + +impl PathCacheEntry { + const fn empty() -> Self { + Self { + valid: false, + drive: 0, + path_len: 0, + path_hash: 0, + inode_num: 0, + path: [0u8; PATH_CACHE_MAX], + } + } +} + +static mut MOUNT: Option = None; +static OP_LOCK: AtomicBool = AtomicBool::new(false); + +struct SharedBuf(UnsafeCell<[u8; 4096]>); + +unsafe impl Sync for SharedBuf {} + +impl SharedBuf { + const fn new() -> Self { + Self(UnsafeCell::new([0u8; 4096])) + } + + #[inline] + unsafe fn as_mut(&self) -> &mut [u8; 4096] { + &mut *self.0.get() + } + + #[inline] + unsafe fn as_ref(&self) -> &[u8; 4096] { + &*self.0.get() + } +} + +static READ_INODE_GDT_BLK: SharedBuf = SharedBuf::new(); +static READ_INODE_IBLK: SharedBuf = SharedBuf::new(); +static LOOKUP_BLK: SharedBuf = SharedBuf::new(); +static LOOKUP_IND: SharedBuf = SharedBuf::new(); +static READ_RANGE_BLK: SharedBuf = SharedBuf::new(); +static READ_RANGE_IND: SharedBuf = SharedBuf::new(); +static READDIR_BLK: SharedBuf = SharedBuf::new(); +static READDIR_IND: SharedBuf = SharedBuf::new(); + +static mut BLOCK_CACHE: [BlockCacheEntry; BLOCK_CACHE_SLOTS] = + [BlockCacheEntry::empty(); BLOCK_CACHE_SLOTS]; +static mut BLOCK_CACHE_CURSOR: usize = 0; + +static mut INODE_CACHE: [InodeCacheEntry; INODE_CACHE_SLOTS] = + [InodeCacheEntry::empty(); INODE_CACHE_SLOTS]; +static mut INODE_CACHE_CURSOR: usize = 0; + +static mut PATH_CACHE: [PathCacheEntry; PATH_CACHE_SLOTS] = [PathCacheEntry::empty(); PATH_CACHE_SLOTS]; +static mut PATH_CACHE_CURSOR: usize = 0; +static mut BMIDE_BASE: u16 = 0; +static mut BMIDE_SCANNED: bool = false; + +#[repr(C)] +#[derive(Clone, Copy)] +struct PrdtEntry { + base_phys: u32, + byte_count: u16, + flags: u16, +} + +#[repr(align(16))] +struct PrdtAligned([PrdtEntry; 2]); + +#[repr(align(65536))] +struct DmaBuf([u8; 4096]); + +static mut DMA_PRDT: PrdtAligned = PrdtAligned( + [PrdtEntry { + base_phys: 0, + byte_count: 0, + flags: 0x8000, + }; 2], +); +static mut DMA_BUF: DmaBuf = DmaBuf([0u8; 4096]); + +struct OpLockGuard; + +impl Drop for OpLockGuard { + fn drop(&mut self) { + OP_LOCK.store(false, Ordering::Release); + } +} + +#[inline] +fn lock_ops() -> OpLockGuard { + while OP_LOCK + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_err() + { + core::hint::spin_loop(); + } + OpLockGuard +} + +#[inline] +fn path_hash(bytes: &[u8]) -> u64 { + let mut h: u64 = 0xcbf29ce484222325; + for b in bytes { + h ^= *b as u64; + h = h.wrapping_mul(0x100000001b3); + } + h +} + +unsafe fn reset_caches() { + for e in &mut BLOCK_CACHE { + e.valid = false; + } + BLOCK_CACHE_CURSOR = 0; + for e in &mut INODE_CACHE { + e.valid = false; + } + INODE_CACHE_CURSOR = 0; + for e in &mut PATH_CACHE { + e.valid = false; + } + PATH_CACHE_CURSOR = 0; +} + +unsafe fn block_cache_lookup(drive: u8, block_num: u32, out: &mut [u8], block_size: usize) -> bool { + for e in &BLOCK_CACHE { + if e.valid && e.drive == drive && e.block_num == block_num { + out[..block_size].copy_from_slice(&e.data[..block_size]); + return true; + } + } + false +} + +unsafe fn block_cache_insert(drive: u8, block_num: u32, data: &[u8], block_size: usize) { + let slot = BLOCK_CACHE_CURSOR % BLOCK_CACHE_SLOTS; + BLOCK_CACHE_CURSOR = (BLOCK_CACHE_CURSOR + 1) % BLOCK_CACHE_SLOTS; + let ent = &mut BLOCK_CACHE[slot]; + ent.valid = true; + ent.drive = drive; + ent.block_num = block_num; + ent.data[..block_size].copy_from_slice(&data[..block_size]); +} + +unsafe fn inode_cache_lookup(drive: u8, inode_num: u32, out: &mut [u8; 256], isz: usize) -> bool { + for e in &INODE_CACHE { + if e.valid && e.drive == drive && e.inode_num == inode_num { + out[..isz].copy_from_slice(&e.inode[..isz]); + return true; + } + } + false +} + +unsafe fn inode_cache_insert(drive: u8, inode_num: u32, inode: &[u8; 256], isz: usize) { + let slot = INODE_CACHE_CURSOR % INODE_CACHE_SLOTS; + INODE_CACHE_CURSOR = (INODE_CACHE_CURSOR + 1) % INODE_CACHE_SLOTS; + let ent = &mut INODE_CACHE[slot]; + ent.valid = true; + ent.drive = drive; + ent.inode_num = inode_num; + ent.inode[..isz].copy_from_slice(&inode[..isz]); +} + +unsafe fn path_cache_lookup(drive: u8, path: &[u8]) -> Option { + if path.len() > PATH_CACHE_MAX { + return None; + } + let h = path_hash(path); + for e in &PATH_CACHE { + if !e.valid || e.drive != drive || e.path_hash != h { + continue; + } + let n = e.path_len as usize; + if n == path.len() && e.path[..n] == path[..] { + return Some(e.inode_num); + } + } + None +} + +unsafe fn path_cache_insert(drive: u8, path: &[u8], inode_num: u32) { + if path.len() > PATH_CACHE_MAX { + return; + } + let slot = PATH_CACHE_CURSOR % PATH_CACHE_SLOTS; + PATH_CACHE_CURSOR = (PATH_CACHE_CURSOR + 1) % PATH_CACHE_SLOTS; + let ent = &mut PATH_CACHE[slot]; + ent.valid = true; + ent.drive = drive; + ent.path_len = path.len() as u16; + ent.path_hash = path_hash(path); + ent.inode_num = inode_num; + ent.path[..path.len()].copy_from_slice(path); +} + +#[inline] +unsafe fn inb(port: u16) -> u8 { + let mut value: u8; + asm!("in al, dx", in("dx") port, out("al") value, options(nomem, nostack, preserves_flags)); + value +} + +#[inline] +unsafe fn outb(port: u16, value: u8) { + asm!("out dx, al", in("dx") port, in("al") value, options(nomem, nostack, preserves_flags)); +} + +#[inline] +unsafe fn inw(port: u16) -> u16 { + let mut value: u16; + asm!("in ax, dx", in("dx") port, out("ax") value, options(nomem, nostack, preserves_flags)); + value +} + +#[inline] +unsafe fn inl(port: u16) -> u32 { + let mut value: u32; + asm!("in eax, dx", in("dx") port, out("eax") value, options(nomem, nostack, preserves_flags)); + value +} + +#[inline] +unsafe fn outl(port: u16, value: u32) { + asm!("out dx, eax", in("dx") port, in("eax") value, options(nomem, nostack, preserves_flags)); +} + +#[inline] +unsafe fn io_wait_400ns() { + let _ = inb(ATA_ALT_STATUS); + let _ = inb(ATA_ALT_STATUS); + let _ = inb(ATA_ALT_STATUS); + let _ = inb(ATA_ALT_STATUS); +} + +#[inline] +unsafe fn select_drive(drive: u8, lba: u32) { + let head = 0xE0 | ((drive & 1) << 4) | (((lba >> 24) & 0x0F) as u8); + outb(ATA_DRIVE_HEAD, head); + io_wait_400ns(); +} + +unsafe fn wait_not_busy(timeout: usize) -> bool { + for _ in 0..timeout { + let st = inb(ATA_STATUS_CMD); + if (st & ATA_STATUS_BSY) == 0 { + return true; + } + core::hint::spin_loop(); + } + false +} + +unsafe fn wait_drq(timeout: usize) -> bool { + for _ in 0..timeout { + let st = inb(ATA_STATUS_CMD); + if (st & ATA_STATUS_BSY) != 0 { + core::hint::spin_loop(); + continue; + } + if (st & (ATA_STATUS_ERR | ATA_STATUS_DF)) != 0 { + return false; + } + if (st & ATA_STATUS_DRQ) != 0 { + return true; + } + core::hint::spin_loop(); + } + false +} + +unsafe fn read_sector_ata(drive: u8, lba: u32, out: &mut [u8; 512]) -> bool { + if !wait_not_busy(200_000) { + return false; + } + select_drive(drive, lba); + outb(ATA_SECTOR_COUNT, 1); + outb(ATA_LBA_LOW, (lba & 0xFF) as u8); + outb(ATA_LBA_MID, ((lba >> 8) & 0xFF) as u8); + outb(ATA_LBA_HIGH, ((lba >> 16) & 0xFF) as u8); + outb(ATA_STATUS_CMD, ATA_CMD_READ_SECTORS); + if !wait_drq(200_000) { + return false; + } + for i in 0..256 { + let w = inw(ATA_DATA); + let b = w.to_le_bytes(); + out[i * 2] = b[0]; + out[i * 2 + 1] = b[1]; + } + true +} + +#[inline] +unsafe fn pci_config_read_u32(bus: u8, dev: u8, func: u8, offset: u8) -> u32 { + let addr = 0x8000_0000u32 + | ((bus as u32) << 16) + | ((dev as u32) << 11) + | ((func as u32) << 8) + | ((offset as u32) & 0xfc); + outl(PCI_CFG_ADDR, addr); + inl(PCI_CFG_DATA) +} + +unsafe fn find_bmide_base() -> Option { + if BMIDE_SCANNED { + return if BMIDE_BASE == 0 { None } else { Some(BMIDE_BASE) }; + } + BMIDE_SCANNED = true; + for bus in 0u16..=255 { + let bus = bus as u8; + for dev in 0u8..32 { + for func in 0u8..8 { + let vendor_device = pci_config_read_u32(bus, dev, func, 0x00); + if vendor_device == 0xffff_ffff || (vendor_device & 0xffff) == 0xffff { + if func == 0 { + break; + } + continue; + } + let class_reg = pci_config_read_u32(bus, dev, func, 0x08); + let class_code = ((class_reg >> 24) & 0xff) as u8; + let subclass = ((class_reg >> 16) & 0xff) as u8; + if class_code != PCI_CLASS_MASS_STORAGE || subclass != PCI_SUBCLASS_IDE { + continue; + } + let bar4 = pci_config_read_u32(bus, dev, func, 0x20); + if (bar4 & 0x1) == 0 { + continue; + } + let base = (bar4 & 0xfffc) as u16; + if base != 0 { + BMIDE_BASE = base; + return Some(base); + } + } + } + } + None +} + +#[inline] +unsafe fn virt_to_phys(vaddr: u64) -> Option { + let cr3: u64; + asm!("mov {}, cr3", out(reg) cr3, options(nomem, nostack, preserves_flags)); + let l4 = cr3 & 0x000f_ffff_ffff_f000; + let l4e_ptr = (l4 + (((vaddr >> 39) & 0x1ff) * 8)) as *const u64; + let l4e = core::ptr::read_volatile(l4e_ptr); + if (l4e & 1) == 0 { + return None; + } + let l3 = l4e & 0x000f_ffff_ffff_f000; + let l3e_ptr = (l3 + (((vaddr >> 30) & 0x1ff) * 8)) as *const u64; + let l3e = core::ptr::read_volatile(l3e_ptr); + if (l3e & 1) == 0 { + return None; + } + if (l3e & (1 << 7)) != 0 { + return Some((l3e & 0x000f_ffff_c000_0000) | (vaddr & 0x3fff_ffff)); + } + let l2 = l3e & 0x000f_ffff_ffff_f000; + let l2e_ptr = (l2 + (((vaddr >> 21) & 0x1ff) * 8)) as *const u64; + let l2e = core::ptr::read_volatile(l2e_ptr); + if (l2e & 1) == 0 { + return None; + } + if (l2e & (1 << 7)) != 0 { + return Some((l2e & 0x000f_ffff_ffe0_0000) | (vaddr & 0x1f_ffff)); + } + let l1 = l2e & 0x000f_ffff_ffff_f000; + let l1e_ptr = (l1 + (((vaddr >> 12) & 0x1ff) * 8)) as *const u64; + let l1e = core::ptr::read_volatile(l1e_ptr); + if (l1e & 1) == 0 { + return None; + } + Some((l1e & 0x000f_ffff_ffff_f000) | (vaddr & 0xfff)) +} + +unsafe fn read_fs_block_dma(m: &FsMount, block_num: u32, out: &mut [u8], block_size: usize) -> bool { + if m.sectors_per_block == 0 || m.sectors_per_block > 8 { + return false; + } + let Some(bm_base) = find_bmide_base() else { + return false; + }; + let lba = block_num.saturating_mul(m.sectors_per_block); + let dma_buf_vaddr = (&mut DMA_BUF.0 as *mut [u8; 4096]) as u64; + let prdt_vaddr = (&mut DMA_PRDT.0 as *mut [PrdtEntry; 2]) as u64; + let Some(dma_buf_phys) = virt_to_phys(dma_buf_vaddr) else { + return false; + }; + let Some(prdt_phys) = virt_to_phys(prdt_vaddr) else { + return false; + }; + if dma_buf_phys > u32::MAX as u64 || prdt_phys > u32::MAX as u64 { + return false; + } + if ((dma_buf_phys & 0xffff) + block_size as u64) > 0x10000 { + return false; + } + + DMA_PRDT.0[0] = PrdtEntry { + base_phys: dma_buf_phys as u32, + byte_count: block_size as u16, + flags: 0x8000, + }; + + let bm_cmd = bm_base; + let bm_status = bm_base + 2; + let bm_prdt = bm_base + 4; + + outb(bm_cmd, inb(bm_cmd) & !0x01); + outb(bm_status, inb(bm_status) | 0x06); + outl(bm_prdt, prdt_phys as u32); + outb(bm_cmd, (inb(bm_cmd) & !0x01) | 0x08); // read from disk -> memory + + if !wait_not_busy(200_000) { + return false; + } + select_drive(m.drive, lba); + outb(ATA_SECTOR_COUNT, m.sectors_per_block as u8); + outb(ATA_LBA_LOW, (lba & 0xFF) as u8); + outb(ATA_LBA_MID, ((lba >> 8) & 0xFF) as u8); + outb(ATA_LBA_HIGH, ((lba >> 16) & 0xFF) as u8); + outb(ATA_STATUS_CMD, ATA_CMD_READ_DMA); + outb(bm_cmd, inb(bm_cmd) | 0x01); + + let mut done = false; + for _ in 0..800_000 { + let s = inb(bm_status); + let ata = inb(ATA_STATUS_CMD); + if (s & 0x02) != 0 || (ata & (ATA_STATUS_ERR | ATA_STATUS_DF)) != 0 { + break; + } + if (s & 0x01) == 0 { + done = true; + break; + } + core::hint::spin_loop(); + } + outb(bm_cmd, inb(bm_cmd) & !0x01); + outb(bm_status, inb(bm_status) | 0x06); + if !done { + return false; + } + out[..block_size].copy_from_slice(&DMA_BUF.0[..block_size]); + true +} + +#[inline] +fn read_u16(buf: &[u8], off: usize) -> Option { + let s = buf.get(off..off + 2)?; + Some(u16::from_le_bytes([s[0], s[1]])) +} + +#[inline] +fn read_u32(buf: &[u8], off: usize) -> Option { + let s = buf.get(off..off + 4)?; + Some(u32::from_le_bytes([s[0], s[1], s[2], s[3]])) +} + +unsafe fn read_fs_block(m: &FsMount, block_num: u32, out: &mut [u8]) -> bool { + let block_size = m.block_size as usize; + if out.len() < block_size { + return false; + } + if block_cache_lookup(m.drive, block_num, out, block_size) { + return true; + } + if read_fs_block_dma(m, block_num, out, block_size) { + block_cache_insert(m.drive, block_num, out, block_size); + return true; + } + let spb = m.sectors_per_block as usize; + for i in 0..spb { + let lba = block_num + .saturating_mul(m.sectors_per_block) + .saturating_add(i as u32); + let mut sec = [0u8; 512]; + if !read_sector_ata(m.drive, lba, &mut sec) { + return false; + } + let dst = i * 512; + out[dst..dst + 512].copy_from_slice(&sec); + } + block_cache_insert(m.drive, block_num, out, block_size); + true +} + +unsafe fn probe_ext2_drive(drive: u8) -> Option { + let mut s2 = [0u8; 512]; + let mut s3 = [0u8; 512]; + if !read_sector_ata(drive, 2, &mut s2) || !read_sector_ata(drive, 3, &mut s3) { + return None; + } + let mut sb = [0u8; 1024]; + sb[..512].copy_from_slice(&s2); + sb[512..].copy_from_slice(&s3); + if read_u16(&sb, 56)? != EXT2_MAGIC { + return None; + } + let log_block_size = read_u32(&sb, 24)?; + if log_block_size > 2 { + return None; + } + let block_size = 1024u32.checked_shl(log_block_size)?; + if block_size < 1024 || block_size % 512 != 0 { + return None; + } + let inode_size = read_u16(&sb, 88).unwrap_or(128); + let inodes_per_group = read_u32(&sb, 40)?; + if inodes_per_group == 0 { + return None; + } + let gdt_block = if block_size == 1024 { 2 } else { 1 }; + Some(FsMount { + drive, + block_size, + sectors_per_block: block_size / 512, + inode_size, + inodes_per_group, + gdt_block, + }) +} + +unsafe fn read_inode(m: &FsMount, inode_num: u32, inode_out: &mut [u8; 256]) -> bool { + if inode_num == 0 || m.inodes_per_group == 0 { + return false; + } + let isz = m.inode_size as usize; + if inode_cache_lookup(m.drive, inode_num, inode_out, isz) { + return true; + } + let group = (inode_num - 1) / m.inodes_per_group; + let index = (inode_num - 1) % m.inodes_per_group; + + let gdt_entry_off = (group as usize) * 32; + let gdt_block_off = gdt_entry_off / (m.block_size as usize); + let gdt_inner = gdt_entry_off % (m.block_size as usize); + if !read_fs_block( + m, + m.gdt_block + gdt_block_off as u32, + READ_INODE_GDT_BLK.as_mut(), + ) { + return false; + } + let inode_table = match read_u32(READ_INODE_GDT_BLK.as_ref(), gdt_inner + 8) { + Some(v) => v, + None => return false, + }; + let inode_off = (index as usize) * (m.inode_size as usize); + let blk = inode_off / (m.block_size as usize); + let off = inode_off % (m.block_size as usize); + if !read_fs_block(m, inode_table + blk as u32, READ_INODE_IBLK.as_mut()) { + return false; + } + let iblk = READ_INODE_IBLK.as_ref(); + if off + isz > iblk.len() || isz > inode_out.len() { + return false; + } + inode_out[..isz].copy_from_slice(&iblk[off..off + isz]); + inode_cache_insert(m.drive, inode_num, inode_out, isz); + true +} + +#[inline] +fn inode_mode(inode: &[u8]) -> u16 { + read_u16(inode, 0).unwrap_or(0) +} + +#[inline] +fn inode_size(inode: &[u8]) -> u32 { + read_u32(inode, 4).unwrap_or(0) +} + +#[inline] +fn inode_block(inode: &[u8], idx: usize) -> u32 { + read_u32(inode, 40 + idx * 4).unwrap_or(0) +} + +#[inline] +fn is_dir(mode: u16) -> bool { + (mode & 0xF000) == 0x4000 +} + +unsafe fn read_data_block_num( + m: &FsMount, + inode: &[u8], + block_idx: usize, + scratch: &mut [u8; 4096], +) -> Option { + if block_idx < 12 { + let n = inode_block(inode, block_idx); + return if n == 0 { None } else { Some(n) }; + } + let idx = block_idx - 12; + let per = (m.block_size / 4) as usize; + if idx >= per { + return None; + } + let indirect = inode_block(inode, 12); + if indirect == 0 { + return None; + } + if !read_fs_block(m, indirect, scratch) { + return None; + } + let n = read_u32(scratch, idx * 4)?; + if n == 0 { None } else { Some(n) } +} + +unsafe fn lookup_child(m: &FsMount, dir_inode_num: u32, name: &[u8]) -> Option { + let mut inode = [0u8; 256]; + if !read_inode(m, dir_inode_num, &mut inode) || !is_dir(inode_mode(&inode)) { + return None; + } + let dir_size = inode_size(&inode) as usize; + let block_size = m.block_size as usize; + let blocks = dir_size.div_ceil(block_size); + for bi in 0..blocks { + let bnum = read_data_block_num(m, &inode, bi, LOOKUP_IND.as_mut())?; + if !read_fs_block(m, bnum, LOOKUP_BLK.as_mut()) { + return None; + } + let blk = LOOKUP_BLK.as_ref(); + let mut off = 0usize; + while off + 8 <= block_size { + let ino = read_u32(blk, off)?; + let rec_len = read_u16(blk, off + 4)? as usize; + let nlen = *blk.get(off + 6)? as usize; + if rec_len == 0 || off + rec_len > block_size { + break; + } + if ino != 0 && nlen > 0 && off + 8 + nlen <= block_size { + let nm = &blk[off + 8..off + 8 + nlen]; + if nm == name { + return Some(ino); + } + } + off += rec_len; + } + } + None +} + +unsafe fn resolve_path_inode(m: &FsMount, path: &[u8]) -> Option { + if let Some(inode) = path_cache_lookup(m.drive, path) { + return Some(inode); + } + let mut cur = 2u32; + let mut i = 0usize; + while i < path.len() { + while i < path.len() && path[i] == b'/' { + i += 1; + } + if i >= path.len() { + break; + } + let start = i; + while i < path.len() && path[i] != b'/' { + i += 1; + } + let seg = &path[start..i]; + if seg.is_empty() || seg == b"." || seg == b".." { + continue; + } + cur = lookup_child(m, cur, seg)?; + } + path_cache_insert(m.drive, path, cur); + Some(cur) +} + +unsafe fn read_inode_range( + m: &FsMount, + inode_num: u32, + offset: u64, + dst: &mut [u8], +) -> Option { + let mut inode = [0u8; 256]; + if !read_inode(m, inode_num, &mut inode) { + return None; + } + let size = inode_size(&inode) as u64; + if offset >= size { + return Some(0); + } + let to_read = min(dst.len() as u64, size - offset) as usize; + let block_size = m.block_size as usize; + let mut done = 0usize; + while done < to_read { + let file_off = offset as usize + done; + let bi = file_off / block_size; + let boff = file_off % block_size; + let bnum = read_data_block_num(m, &inode, bi, READ_RANGE_IND.as_mut())?; + if !read_fs_block(m, bnum, READ_RANGE_BLK.as_mut()) { + return None; + } + let n = min(block_size - boff, to_read - done); + let blk = READ_RANGE_BLK.as_ref(); + dst[done..done + n].copy_from_slice(&blk[boff..boff + n]); + done += n; + } + Some(done) +} + +unsafe fn read_path_inode(path: McxPath) -> Option { + let m = MOUNT.as_ref()?; + let raw = core::slice::from_raw_parts(path.ptr, path.len); + let p = if !raw.is_empty() && raw[0] == b'/' { + &raw[1..] + } else { + raw + }; + resolve_path_inode(m, p) +} + +extern "C" fn fs_mount(_device_id: u32) -> i32 { + let _guard = lock_ops(); + unsafe { + // rootfs は qemu-runner の disk0 (IDE index=1, primary slave) を優先。 + // 起動直後はデバイス準備に時間がかかるため複数回リトライする。 + for _ in 0..16 { + if let Some(m) = probe_ext2_drive(1) { + reset_caches(); + MOUNT = Some(m); + return 0; + } + if let Some(m) = probe_ext2_drive(0) { + reset_caches(); + MOUNT = Some(m); + return 0; + } + for _ in 0..2_000_000 { + core::hint::spin_loop(); + } + } + } + -5 +} + +extern "C" fn fs_read(path: McxPath, offset: u64, buf: McxBuffer, out_read: *mut usize) -> i32 { + if path.ptr.is_null() || buf.ptr.is_null() || out_read.is_null() { + return -22; + } + let _guard = lock_ops(); + unsafe { + let inode = match read_path_inode(path) { + Some(v) => v, + None => { + return if MOUNT.is_some() { -2 } else { -5 }; + } + }; + let m = match MOUNT.as_ref() { + Some(v) => v, + None => return -5, + }; + let dst = core::slice::from_raw_parts_mut(buf.ptr, buf.len); + match read_inode_range(m, inode, offset, dst) { + Some(n) => { + *out_read = n; + 0 + } + None => -5, + } + } +} + +extern "C" fn fs_stat(path: McxPath, out_mode: *mut u16, out_size: *mut u64) -> i32 { + if path.ptr.is_null() || out_mode.is_null() || out_size.is_null() { + return -22; + } + let _guard = lock_ops(); + unsafe { + let inode_num = match read_path_inode(path) { + Some(v) => v, + None => { + return if MOUNT.is_some() { -2 } else { -5 }; + } + }; + let m = match MOUNT.as_ref() { + Some(v) => v, + None => return -5, + }; + let mut inode = [0u8; 256]; + if !read_inode(m, inode_num, &mut inode) { + return -5; + } + *out_mode = inode_mode(&inode); + *out_size = inode_size(&inode) as u64; + 0 + } +} + +extern "C" fn fs_readdir(path: McxPath, buf: McxBuffer, out_len: *mut usize) -> i32 { + if path.ptr.is_null() || buf.ptr.is_null() || out_len.is_null() { + return -22; + } + let _guard = lock_ops(); + unsafe { + let inode_num = match read_path_inode(path) { + Some(v) => v, + None => { + return if MOUNT.is_some() { -2 } else { -5 }; + } + }; + let m = match MOUNT.as_ref() { + Some(v) => v, + None => return -5, + }; + let mut inode = [0u8; 256]; + if !read_inode(m, inode_num, &mut inode) { + return -5; + } + if !is_dir(inode_mode(&inode)) { + return -20; + } + + let block_size = m.block_size as usize; + let dir_size = inode_size(&inode) as usize; + let mut written = 0usize; + let out = core::slice::from_raw_parts_mut(buf.ptr, buf.len); + let blocks = dir_size.div_ceil(block_size); + for bi in 0..blocks { + let bnum = match read_data_block_num(m, &inode, bi, READDIR_IND.as_mut()) { + Some(v) => v, + None => return -5, + }; + if !read_fs_block(m, bnum, READDIR_BLK.as_mut()) { + return -5; + } + let data_blk = READDIR_BLK.as_ref(); + let mut off = 0usize; + while off + 8 <= block_size { + let ino = match read_u32(data_blk, off) { + Some(v) => v, + None => break, + }; + let rec_len = match read_u16(data_blk, off + 4) { + Some(v) => v as usize, + None => break, + }; + let nlen = match data_blk.get(off + 6) { + Some(v) => *v as usize, + None => break, + }; + if rec_len == 0 || off + rec_len > block_size { + break; + } + if ino != 0 && nlen > 0 && off + 8 + nlen <= block_size { + let nm = &data_blk[off + 8..off + 8 + nlen]; + if nm != b"." && nm != b".." { + let need = nlen + if written == 0 { 0 } else { 1 }; + if written + need > out.len() { + *out_len = written; + return 0; + } + if written != 0 { + out[written] = b'\n'; + written += 1; + } + out[written..written + nlen].copy_from_slice(nm); + written += nlen; + } + } + off += rec_len; + } + } + *out_len = written; + 0 + } +} + +static FS_OPS: McxFsOps = McxFsOps { + mount: fs_mount, + read: fs_read, + stat: fs_stat, + readdir: fs_readdir, +}; + +#[no_mangle] +pub extern "C" fn mochi_module_init() -> *const McxFsOps { + &FS_OPS +} + +#[used] +static KEEP_INIT_REF: extern "C" fn() -> *const McxFsOps = mochi_module_init; + +#[no_mangle] +pub extern "C" fn _start() -> ! { + loop { + core::hint::spin_loop(); + } +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop { + core::hint::spin_loop(); + } +} diff --git a/src/posix/Cargo.toml b/src/posix/Cargo.toml new file mode 100644 index 0000000..b6aa8fb --- /dev/null +++ b/src/posix/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "posix-types" +version = "0.2.0" +authors = ["tas0dev"] +edition = "2021" + +[lib] +name = "posix" +test = false +bench = false \ No newline at end of file diff --git a/src/posix/src/lib.rs b/src/posix/src/lib.rs new file mode 100644 index 0000000..bb3d479 --- /dev/null +++ b/src/posix/src/lib.rs @@ -0,0 +1,60 @@ +#![no_std] +#![allow(non_snake_case)] +#![allow(non_camel_case_types)] + +pub mod prelude { + pub use core::prelude::v1::*; +} + +pub type c_int = i32; +pub type c_long = i64; +pub type c_ulong = u64; +pub type c_char = i8; +pub type c_uchar = u8; +pub type c_uint = u32; +pub type c_void = core::ffi::c_void; +pub type size_t = usize; +pub type ssize_t = isize; +pub type off_t = i64; +pub type regoff_t = i64; +pub type __u64 = u64; +pub type __s64 = i64; +pub type Ioctl = u64; +pub type pid_t = i32; +pub type uid_t = u32; +pub type gid_t = u32; +pub type mode_t = u32; +pub type dev_t = u64; +pub type ino_t = u64; +pub type nlink_t = u64; +pub type blksize_t = i64; +pub type blkcnt_t = i64; +pub type time_t = i64; +pub type nfds_t = u64; + +pub enum sigset_t {} +pub enum siginfo_t {} +pub enum sem_t {} +pub enum stack_t {} +pub enum regex_t {} +pub enum msghdr {} +pub enum cmsghdr {} +pub enum mmsghdr {} +pub enum termios {} +pub enum termios2 {} +pub struct sysinfo {} +pub struct statfs {} +pub struct statfs64 {} +pub struct statvfs64 {} +pub struct stat64 {} +pub struct file_clone_range {} + +pub const T_TYPE: u32 = 0; +pub const _IOC_SIZESHIFT: u32 = 0; + +pub unsafe fn ioctl(_fd: c_int, _request: Ioctl, _arg: u64) -> c_int { 0 } +pub unsafe fn CMSG_FIRSTHDR(_mhdr: *const msghdr) -> *mut cmsghdr { core::ptr::null_mut() } +pub unsafe fn CMSG_DATA(_cmsg: *const cmsghdr) -> *mut c_uchar { core::ptr::null_mut() } +pub const fn CMSG_ALIGN(_len: usize) -> usize { 0 } +pub const fn CMSG_SPACE(_len: c_uint) -> c_uint { 0 } +pub const fn CMSG_LEN(_len: c_uint) -> c_uint { 0 } diff --git a/src/posix/src/main.rs b/src/posix/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/src/posix/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/src/resources/config/env.txt b/src/resources/config/env.txt new file mode 100644 index 0000000..076417f --- /dev/null +++ b/src/resources/config/env.txt @@ -0,0 +1 @@ +PATH=/bin:/applications diff --git a/src/resources/system/about.txt b/src/resources/system/about.txt new file mode 100644 index 0000000..052d4d6 --- /dev/null +++ b/src/resources/system/about.txt @@ -0,0 +1,3 @@ +mochiOS 0.1 develop + +2026 (c) tas0dev \ No newline at end of file diff --git a/src/resources/system/fonts/NotoSansJP-Regular.ttf b/src/resources/system/fonts/NotoSansJP-Regular.ttf new file mode 100644 index 0000000..d13df30 Binary files /dev/null and b/src/resources/system/fonts/NotoSansJP-Regular.ttf differ diff --git a/src/resources/system/fonts/OFL.TXT b/src/resources/system/fonts/OFL.TXT new file mode 100644 index 0000000..5168372 --- /dev/null +++ b/src/resources/system/fonts/OFL.TXT @@ -0,0 +1,94 @@ +Copyright (C) 2020 Dimitar Toshkov Zhekov, +with Reserved Font Name "Terminus Font". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/resources/system/fonts/OFL.txt b/src/resources/system/fonts/OFL.txt new file mode 100644 index 0000000..d57ea9c --- /dev/null +++ b/src/resources/system/fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source' + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/resources/system/fonts/ter-u12b.bdf b/src/resources/system/fonts/ter-u12b.bdf new file mode 100644 index 0000000..d2e3fd0 --- /dev/null +++ b/src/resources/system/fonts/ter-u12b.bdf @@ -0,0 +1,25792 @@ +STARTFONT 2.1 +FONT -xos4-Terminus-Bold-R-Normal--12-120-72-72-C-60-ISO10646-1 +SIZE 12 72 72 +FONTBOUNDINGBOX 6 12 0 -2 +STARTPROPERTIES 20 +FAMILY_NAME "Terminus" +FOUNDRY "xos4" +SETWIDTH_NAME "Normal" +ADD_STYLE_NAME "" +COPYRIGHT "Copyright (C) 2020 Dimitar Toshkov Zhekov" +NOTICE "Licensed under the SIL Open Font License, Version 1.1" +WEIGHT_NAME "Bold" +SLANT "R" +PIXEL_SIZE 12 +POINT_SIZE 120 +RESOLUTION_X 72 +RESOLUTION_Y 72 +SPACING "C" +AVERAGE_WIDTH 60 +CHARSET_REGISTRY "ISO10646" +CHARSET_ENCODING "1" +MIN_SPACE 6 +FONT_ASCENT 10 +FONT_DESCENT 2 +DEFAULT_CHAR 65533 +ENDPROPERTIES +CHARS 1356 +STARTCHAR char0 +ENCODING 0 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +D8 +88 +00 +88 +88 +00 +88 +D8 +00 +00 +ENDCHAR +STARTCHAR space +ENCODING 32 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR exclam +ENCODING 33 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +20 +20 +20 +20 +00 +20 +20 +00 +00 +ENDCHAR +STARTCHAR quotedbl +ENCODING 34 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +50 +50 +50 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR numbersign +ENCODING 35 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +F8 +50 +50 +F8 +50 +50 +00 +00 +ENDCHAR +STARTCHAR dollar +ENCODING 36 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +70 +A8 +A0 +70 +28 +A8 +70 +20 +00 +ENDCHAR +STARTCHAR percent +ENCODING 37 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +48 +A8 +50 +10 +20 +28 +54 +48 +00 +00 +ENDCHAR +STARTCHAR ampersand +ENCODING 38 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +50 +50 +20 +68 +90 +90 +68 +00 +00 +ENDCHAR +STARTCHAR quotesingle +ENCODING 39 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +20 +20 +20 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR parenleft +ENCODING 40 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +20 +40 +40 +40 +40 +20 +10 +00 +00 +ENDCHAR +STARTCHAR parenright +ENCODING 41 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +40 +20 +10 +10 +10 +10 +20 +40 +00 +00 +ENDCHAR +STARTCHAR asterisk +ENCODING 42 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +50 +20 +F8 +20 +50 +00 +00 +00 +ENDCHAR +STARTCHAR plus +ENCODING 43 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +20 +20 +F8 +20 +20 +00 +00 +00 +ENDCHAR +STARTCHAR comma +ENCODING 44 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +20 +20 +40 +00 +ENDCHAR +STARTCHAR hyphen +ENCODING 45 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +F8 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR period +ENCODING 46 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +20 +20 +00 +00 +ENDCHAR +STARTCHAR slash +ENCODING 47 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +08 +08 +10 +10 +20 +20 +40 +40 +00 +00 +ENDCHAR +STARTCHAR zero +ENCODING 48 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +98 +A8 +C8 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR one +ENCODING 49 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +60 +20 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR two +ENCODING 50 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +88 +08 +10 +20 +40 +F8 +00 +00 +ENDCHAR +STARTCHAR three +ENCODING 51 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +08 +30 +08 +08 +88 +70 +00 +00 +ENDCHAR +STARTCHAR four +ENCODING 52 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +08 +18 +28 +48 +88 +F8 +08 +08 +00 +00 +ENDCHAR +STARTCHAR five +ENCODING 53 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +80 +80 +F0 +08 +08 +88 +70 +00 +00 +ENDCHAR +STARTCHAR six +ENCODING 54 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +80 +80 +F0 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR seven +ENCODING 55 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +08 +08 +10 +10 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR eight +ENCODING 56 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +88 +70 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR nine +ENCODING 57 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +88 +88 +78 +08 +08 +70 +00 +00 +ENDCHAR +STARTCHAR colon +ENCODING 58 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +20 +20 +00 +00 +20 +20 +00 +00 +ENDCHAR +STARTCHAR semicolon +ENCODING 59 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +20 +20 +00 +00 +20 +20 +40 +00 +ENDCHAR +STARTCHAR less +ENCODING 60 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +08 +10 +20 +40 +20 +10 +08 +00 +00 +ENDCHAR +STARTCHAR equal +ENCODING 61 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F8 +00 +00 +F8 +00 +00 +00 +00 +ENDCHAR +STARTCHAR greater +ENCODING 62 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +40 +20 +10 +08 +10 +20 +40 +00 +00 +ENDCHAR +STARTCHAR question +ENCODING 63 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +88 +10 +20 +00 +20 +20 +00 +00 +ENDCHAR +STARTCHAR at +ENCODING 64 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +98 +A8 +A8 +98 +80 +78 +00 +00 +ENDCHAR +STARTCHAR A +ENCODING 65 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +88 +88 +F8 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR B +ENCODING 66 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F0 +88 +88 +F0 +88 +88 +88 +F0 +00 +00 +ENDCHAR +STARTCHAR C +ENCODING 67 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +80 +80 +80 +80 +88 +70 +00 +00 +ENDCHAR +STARTCHAR D +ENCODING 68 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +E0 +90 +88 +88 +88 +88 +90 +E0 +00 +00 +ENDCHAR +STARTCHAR E +ENCODING 69 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +80 +80 +F0 +80 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR F +ENCODING 70 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +80 +80 +F0 +80 +80 +80 +80 +00 +00 +ENDCHAR +STARTCHAR G +ENCODING 71 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +80 +80 +B8 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR H +ENCODING 72 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +88 +F8 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR I +ENCODING 73 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +20 +20 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR J +ENCODING 74 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +38 +10 +10 +10 +10 +90 +90 +60 +00 +00 +ENDCHAR +STARTCHAR K +ENCODING 75 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +90 +A0 +C0 +C0 +A0 +90 +88 +00 +00 +ENDCHAR +STARTCHAR L +ENCODING 76 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +80 +80 +80 +80 +80 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR M +ENCODING 77 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +D8 +A8 +A8 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR N +ENCODING 78 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +C8 +A8 +98 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR O +ENCODING 79 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR P +ENCODING 80 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F0 +88 +88 +88 +F0 +80 +80 +80 +00 +00 +ENDCHAR +STARTCHAR Q +ENCODING 81 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +88 +88 +88 +88 +A8 +70 +08 +00 +ENDCHAR +STARTCHAR R +ENCODING 82 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F0 +88 +88 +88 +F0 +A0 +90 +88 +00 +00 +ENDCHAR +STARTCHAR S +ENCODING 83 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +80 +70 +08 +08 +88 +70 +00 +00 +ENDCHAR +STARTCHAR T +ENCODING 84 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +20 +20 +20 +20 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR U +ENCODING 85 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR V +ENCODING 86 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +88 +50 +50 +50 +20 +20 +00 +00 +ENDCHAR +STARTCHAR W +ENCODING 87 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +88 +88 +A8 +A8 +D8 +88 +00 +00 +ENDCHAR +STARTCHAR X +ENCODING 88 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +50 +20 +20 +50 +88 +88 +00 +00 +ENDCHAR +STARTCHAR Y +ENCODING 89 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +50 +50 +20 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR Z +ENCODING 90 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +08 +10 +20 +40 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR bracketleft +ENCODING 91 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +40 +40 +40 +40 +40 +40 +70 +00 +00 +ENDCHAR +STARTCHAR backslash +ENCODING 92 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +40 +40 +20 +20 +10 +10 +08 +08 +00 +00 +ENDCHAR +STARTCHAR bracketright +ENCODING 93 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +10 +10 +10 +10 +10 +10 +70 +00 +00 +ENDCHAR +STARTCHAR asciicircum +ENCODING 94 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +20 +50 +88 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR underscore +ENCODING 95 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +F8 +00 +ENDCHAR +STARTCHAR grave +ENCODING 96 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +20 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR a +ENCODING 97 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +08 +78 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR b +ENCODING 98 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +80 +80 +F0 +88 +88 +88 +88 +F0 +00 +00 +ENDCHAR +STARTCHAR c +ENCODING 99 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +80 +80 +88 +70 +00 +00 +ENDCHAR +STARTCHAR d +ENCODING 100 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +08 +08 +78 +88 +88 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR e +ENCODING 101 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +F8 +80 +80 +78 +00 +00 +ENDCHAR +STARTCHAR f +ENCODING 102 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +18 +20 +70 +20 +20 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR g +ENCODING 103 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +78 +88 +88 +88 +88 +78 +08 +70 +ENDCHAR +STARTCHAR h +ENCODING 104 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +80 +80 +F0 +88 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR i +ENCODING 105 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +20 +20 +00 +60 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR j +ENCODING 106 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +08 +08 +00 +18 +08 +08 +08 +08 +08 +48 +30 +ENDCHAR +STARTCHAR k +ENCODING 107 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +40 +40 +48 +50 +60 +60 +50 +48 +00 +00 +ENDCHAR +STARTCHAR l +ENCODING 108 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +60 +20 +20 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR m +ENCODING 109 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F0 +A8 +A8 +A8 +A8 +A8 +00 +00 +ENDCHAR +STARTCHAR n +ENCODING 110 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F0 +88 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR o +ENCODING 111 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR p +ENCODING 112 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F0 +88 +88 +88 +88 +F0 +80 +80 +ENDCHAR +STARTCHAR q +ENCODING 113 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +78 +88 +88 +88 +88 +78 +08 +08 +ENDCHAR +STARTCHAR r +ENCODING 114 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +B8 +C0 +80 +80 +80 +80 +00 +00 +ENDCHAR +STARTCHAR s +ENCODING 115 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +78 +80 +70 +08 +08 +F0 +00 +00 +ENDCHAR +STARTCHAR t +ENCODING 116 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +20 +70 +20 +20 +20 +20 +18 +00 +00 +ENDCHAR +STARTCHAR u +ENCODING 117 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +88 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR v +ENCODING 118 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +50 +50 +20 +20 +00 +00 +ENDCHAR +STARTCHAR w +ENCODING 119 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +A8 +A8 +A8 +70 +00 +00 +ENDCHAR +STARTCHAR x +ENCODING 120 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +50 +20 +20 +50 +88 +00 +00 +ENDCHAR +STARTCHAR y +ENCODING 121 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +88 +88 +88 +78 +08 +70 +ENDCHAR +STARTCHAR z +ENCODING 122 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F8 +10 +20 +40 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR braceleft +ENCODING 123 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +18 +20 +20 +40 +20 +20 +20 +18 +00 +00 +ENDCHAR +STARTCHAR bar +ENCODING 124 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +20 +20 +20 +20 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR braceright +ENCODING 125 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +60 +10 +10 +08 +10 +10 +10 +60 +00 +00 +ENDCHAR +STARTCHAR asciitilde +ENCODING 126 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +48 +A8 +90 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR nbspace +ENCODING 160 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR exclamdown +ENCODING 161 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +20 +00 +20 +20 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR cent +ENCODING 162 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +20 +70 +A8 +A0 +A0 +A8 +70 +20 +00 +ENDCHAR +STARTCHAR sterling +ENCODING 163 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +30 +48 +40 +F0 +40 +40 +48 +F8 +00 +00 +ENDCHAR +STARTCHAR currency +ENCODING 164 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +48 +30 +48 +48 +30 +48 +00 +00 +00 +ENDCHAR +STARTCHAR yen +ENCODING 165 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +50 +20 +70 +20 +70 +20 +00 +00 +ENDCHAR +STARTCHAR brokenbar +ENCODING 166 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +20 +20 +00 +00 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR section +ENCODING 167 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +30 +48 +20 +50 +48 +28 +10 +48 +30 +00 +00 +ENDCHAR +STARTCHAR dieresis +ENCODING 168 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR copyright +ENCODING 169 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +78 +84 +B4 +A4 +A4 +B4 +84 +78 +00 +00 +ENDCHAR +STARTCHAR ordfeminine +ENCODING 170 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +08 +38 +48 +38 +00 +78 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR guillemotleft +ENCODING 171 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +14 +28 +50 +A0 +50 +28 +14 +00 +00 +ENDCHAR +STARTCHAR logicalnot +ENCODING 172 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F8 +08 +08 +08 +00 +00 +00 +00 +ENDCHAR +STARTCHAR softhyphen +ENCODING 173 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +78 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR registered +ENCODING 174 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +78 +84 +B4 +AC +B4 +AC +84 +78 +00 +00 +ENDCHAR +STARTCHAR macron +ENCODING 175 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +70 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR degree +ENCODING 176 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +20 +50 +20 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR plusminus +ENCODING 177 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +20 +20 +F8 +20 +20 +00 +F8 +00 +00 +ENDCHAR +STARTCHAR twosuperior +ENCODING 178 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +30 +48 +10 +20 +78 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR threesuperior +ENCODING 179 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +70 +08 +30 +08 +70 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR acute +ENCODING 180 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +20 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR mu +ENCODING 181 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +88 +88 +98 +E8 +80 +80 +ENDCHAR +STARTCHAR paragraph +ENCODING 182 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +78 +A8 +A8 +A8 +68 +28 +28 +28 +00 +00 +ENDCHAR +STARTCHAR periodcentered +ENCODING 183 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +20 +20 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR cedilla +ENCODING 184 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +20 +20 +40 +ENDCHAR +STARTCHAR onesuperior +ENCODING 185 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +10 +30 +10 +10 +38 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR ordmasculine +ENCODING 186 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +48 +48 +48 +30 +00 +78 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR guillemotright +ENCODING 187 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +A0 +50 +28 +14 +28 +50 +A0 +00 +00 +ENDCHAR +STARTCHAR onequarter +ENCODING 188 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +C0 +44 +48 +50 +20 +48 +98 +28 +78 +08 +08 +ENDCHAR +STARTCHAR onehalf +ENCODING 189 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +C0 +44 +48 +50 +20 +40 +98 +24 +08 +10 +3C +ENDCHAR +STARTCHAR threequarters +ENCODING 190 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +E0 +10 +60 +14 +E8 +10 +24 +4C +94 +3C +04 +04 +ENDCHAR +STARTCHAR questiondown +ENCODING 191 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +20 +00 +20 +40 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR Agrave +ENCODING 192 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +20 +70 +88 +88 +88 +F8 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR Aacute +ENCODING 193 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +20 +70 +88 +88 +88 +F8 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR Acircumflex +ENCODING 194 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +50 +70 +88 +88 +88 +F8 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR Atilde +ENCODING 195 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +28 +50 +70 +88 +88 +88 +F8 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR Adieresis +ENCODING 196 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +70 +88 +88 +88 +F8 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR Aring +ENCODING 197 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +50 +70 +88 +88 +88 +F8 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR AE +ENCODING 198 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +7C +90 +90 +FC +90 +90 +90 +9C +00 +00 +ENDCHAR +STARTCHAR Ccedilla +ENCODING 199 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +80 +80 +80 +80 +88 +70 +20 +40 +ENDCHAR +STARTCHAR Egrave +ENCODING 200 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +20 +F8 +80 +80 +F0 +80 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR Eacute +ENCODING 201 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +20 +F8 +80 +80 +F0 +80 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR Ecircumflex +ENCODING 202 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +50 +F8 +80 +80 +F0 +80 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR Edieresis +ENCODING 203 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +F8 +80 +80 +F0 +80 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR Igrave +ENCODING 204 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +20 +70 +20 +20 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR Iacute +ENCODING 205 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +20 +70 +20 +20 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR Icircumflex +ENCODING 206 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +50 +70 +20 +20 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR Idieresis +ENCODING 207 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +70 +20 +20 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR Eth +ENCODING 208 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +E0 +90 +88 +E8 +88 +88 +90 +E0 +00 +00 +ENDCHAR +STARTCHAR Ntilde +ENCODING 209 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +28 +50 +88 +88 +C8 +A8 +98 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR Ograve +ENCODING 210 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +20 +70 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR Oacute +ENCODING 211 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +20 +70 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR Ocircumflex +ENCODING 212 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +50 +70 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR Otilde +ENCODING 213 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +28 +50 +70 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR Odieresis +ENCODING 214 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +70 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR multiply +ENCODING 215 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +50 +20 +50 +88 +00 +00 +00 +ENDCHAR +STARTCHAR Oslash +ENCODING 216 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +74 +88 +98 +A8 +C8 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR Ugrave +ENCODING 217 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +20 +88 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR Uacute +ENCODING 218 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +20 +88 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR Ucircumflex +ENCODING 219 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +50 +88 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR Udieresis +ENCODING 220 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +88 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR Yacute +ENCODING 221 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +20 +88 +88 +50 +50 +20 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR Thorn +ENCODING 222 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +80 +F0 +88 +88 +88 +F0 +80 +80 +00 +00 +ENDCHAR +STARTCHAR germandbls +ENCODING 223 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +E0 +90 +90 +F0 +88 +88 +C8 +B0 +00 +00 +ENDCHAR +STARTCHAR agrave +ENCODING 224 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +40 +20 +70 +08 +78 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR aacute +ENCODING 225 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +20 +70 +08 +78 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR acircumflex +ENCODING 226 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +50 +70 +08 +78 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR atilde +ENCODING 227 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +28 +50 +70 +08 +78 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR adieresis +ENCODING 228 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +70 +08 +78 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR aring +ENCODING 229 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +50 +70 +08 +78 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR ae +ENCODING 230 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +28 +68 +B0 +A0 +78 +00 +00 +ENDCHAR +STARTCHAR ccedilla +ENCODING 231 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +80 +80 +88 +70 +20 +40 +ENDCHAR +STARTCHAR egrave +ENCODING 232 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +40 +20 +70 +88 +F8 +80 +80 +78 +00 +00 +ENDCHAR +STARTCHAR eacute +ENCODING 233 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +20 +70 +88 +F8 +80 +80 +78 +00 +00 +ENDCHAR +STARTCHAR ecircumflex +ENCODING 234 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +50 +70 +88 +F8 +80 +80 +78 +00 +00 +ENDCHAR +STARTCHAR edieresis +ENCODING 235 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +70 +88 +F8 +80 +80 +78 +00 +00 +ENDCHAR +STARTCHAR igrave +ENCODING 236 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +40 +20 +60 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR iacute +ENCODING 237 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +20 +60 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR icircumflex +ENCODING 238 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +50 +60 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR idieresis +ENCODING 239 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +60 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR eth +ENCODING 240 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +A0 +40 +A0 +70 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR ntilde +ENCODING 241 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +28 +50 +F0 +88 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR ograve +ENCODING 242 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +40 +20 +70 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR oacute +ENCODING 243 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +20 +70 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR ocircumflex +ENCODING 244 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +50 +70 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR otilde +ENCODING 245 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +28 +50 +70 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR odieresis +ENCODING 246 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +70 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR divide +ENCODING 247 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +20 +20 +00 +F8 +00 +20 +20 +00 +00 +ENDCHAR +STARTCHAR oslash +ENCODING 248 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +74 +98 +A8 +C8 +88 +70 +00 +00 +ENDCHAR +STARTCHAR ugrave +ENCODING 249 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +40 +20 +88 +88 +88 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR uacute +ENCODING 250 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +20 +88 +88 +88 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR ucircumflex +ENCODING 251 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +50 +88 +88 +88 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR udieresis +ENCODING 252 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +88 +88 +88 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR yacute +ENCODING 253 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +20 +88 +88 +88 +88 +88 +78 +08 +70 +ENDCHAR +STARTCHAR thorn +ENCODING 254 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +80 +80 +F0 +88 +88 +88 +88 +F0 +80 +80 +ENDCHAR +STARTCHAR ydieresis +ENCODING 255 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +88 +88 +88 +88 +88 +78 +08 +70 +ENDCHAR +STARTCHAR Amacron +ENCODING 256 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +70 +00 +70 +88 +88 +88 +F8 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR amacron +ENCODING 257 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +00 +70 +08 +78 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR Abreve +ENCODING 258 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +70 +88 +88 +88 +F8 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR abreve +ENCODING 259 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +20 +70 +08 +78 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR Aogonek +ENCODING 260 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +88 +88 +F8 +88 +88 +88 +10 +0C +ENDCHAR +STARTCHAR aogonek +ENCODING 261 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +08 +78 +88 +88 +78 +10 +0C +ENDCHAR +STARTCHAR Cacute +ENCODING 262 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +20 +70 +88 +80 +80 +80 +80 +88 +70 +00 +00 +ENDCHAR +STARTCHAR cacute +ENCODING 263 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +20 +70 +88 +80 +80 +88 +70 +00 +00 +ENDCHAR +STARTCHAR Ccircumflex +ENCODING 264 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +50 +70 +88 +80 +80 +80 +80 +88 +70 +00 +00 +ENDCHAR +STARTCHAR ccircumflex +ENCODING 265 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +50 +70 +88 +80 +80 +88 +70 +00 +00 +ENDCHAR +STARTCHAR Cdotaccent +ENCODING 266 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +70 +88 +80 +80 +80 +80 +88 +70 +00 +00 +ENDCHAR +STARTCHAR cdotaccent +ENCODING 267 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +20 +70 +88 +80 +80 +88 +70 +00 +00 +ENDCHAR +STARTCHAR Ccaron +ENCODING 268 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +70 +88 +80 +80 +80 +80 +88 +70 +00 +00 +ENDCHAR +STARTCHAR ccaron +ENCODING 269 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +20 +70 +88 +80 +80 +88 +70 +00 +00 +ENDCHAR +STARTCHAR Dcaron +ENCODING 270 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +A0 +40 +E0 +90 +88 +88 +88 +88 +90 +E0 +00 +00 +ENDCHAR +STARTCHAR dcaron +ENCODING 271 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +08 +08 +78 +88 +88 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR Dcroat +ENCODING 272 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +E0 +90 +88 +E8 +88 +88 +90 +E0 +00 +00 +ENDCHAR +STARTCHAR dcroat +ENCODING 273 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +08 +3C +08 +78 +88 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR Emacron +ENCODING 274 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +70 +00 +F8 +80 +80 +F0 +80 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR emacron +ENCODING 275 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +00 +70 +88 +F8 +80 +80 +78 +00 +00 +ENDCHAR +STARTCHAR Ebreve +ENCODING 276 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +F8 +80 +80 +F0 +80 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR ebreve +ENCODING 277 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +20 +70 +88 +F8 +80 +80 +78 +00 +00 +ENDCHAR +STARTCHAR Edotaccent +ENCODING 278 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +F8 +80 +80 +F0 +80 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR edotaccent +ENCODING 279 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +20 +70 +88 +F8 +80 +80 +78 +00 +00 +ENDCHAR +STARTCHAR Eogonek +ENCODING 280 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +80 +80 +F0 +80 +80 +80 +F8 +10 +0C +ENDCHAR +STARTCHAR eogonek +ENCODING 281 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +F8 +80 +80 +78 +20 +18 +ENDCHAR +STARTCHAR Ecaron +ENCODING 282 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +F8 +80 +80 +F0 +80 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR ecaron +ENCODING 283 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +20 +70 +88 +F8 +80 +80 +78 +00 +00 +ENDCHAR +STARTCHAR Gcircumflex +ENCODING 284 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +50 +70 +88 +80 +80 +B8 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR gcircumflex +ENCODING 285 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +50 +78 +88 +88 +88 +88 +78 +08 +70 +ENDCHAR +STARTCHAR Gbreve +ENCODING 286 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +70 +88 +80 +80 +B8 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR gbreve +ENCODING 287 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +20 +78 +88 +88 +88 +88 +78 +08 +70 +ENDCHAR +STARTCHAR Gdotaccent +ENCODING 288 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +70 +88 +80 +80 +B8 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR gdotaccent +ENCODING 289 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +20 +78 +88 +88 +88 +88 +78 +08 +70 +ENDCHAR +STARTCHAR Gcommaaccent +ENCODING 290 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +80 +80 +B8 +88 +88 +70 +20 +40 +ENDCHAR +STARTCHAR gcommaaccent +ENCODING 291 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +20 +78 +88 +88 +88 +88 +78 +08 +70 +ENDCHAR +STARTCHAR Hcircumflex +ENCODING 292 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +50 +88 +88 +88 +F8 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR hcircumflex +ENCODING 293 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +50 +80 +80 +F0 +88 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR Hbar +ENCODING 294 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +48 +FC +48 +78 +48 +48 +48 +48 +00 +00 +ENDCHAR +STARTCHAR hbar +ENCODING 295 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +40 +F0 +40 +70 +48 +48 +48 +48 +00 +00 +ENDCHAR +STARTCHAR Itilde +ENCODING 296 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +28 +50 +70 +20 +20 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR itilde +ENCODING 297 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +28 +50 +60 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR Imacron +ENCODING 298 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +70 +00 +70 +20 +20 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR imacron +ENCODING 299 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +00 +60 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR Ibreve +ENCODING 300 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +70 +20 +20 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR ibreve +ENCODING 301 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +20 +60 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR Iogonek +ENCODING 302 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +20 +20 +20 +20 +20 +20 +70 +20 +18 +ENDCHAR +STARTCHAR iogonek +ENCODING 303 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +20 +20 +00 +60 +20 +20 +20 +20 +70 +20 +18 +ENDCHAR +STARTCHAR Idotaccent +ENCODING 304 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +70 +20 +20 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR dotlessi +ENCODING 305 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +60 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR IJ +ENCODING 306 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +88 +88 +88 +A8 +A8 +90 +00 +00 +ENDCHAR +STARTCHAR ij +ENCODING 307 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +88 +88 +00 +98 +88 +88 +88 +88 +88 +28 +10 +ENDCHAR +STARTCHAR Jcircumflex +ENCODING 308 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +28 +38 +10 +10 +10 +10 +90 +90 +60 +00 +00 +ENDCHAR +STARTCHAR jcircumflex +ENCODING 309 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +08 +14 +18 +08 +08 +08 +08 +08 +48 +30 +ENDCHAR +STARTCHAR Kcommaaccent +ENCODING 310 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +90 +A0 +C0 +C0 +A0 +90 +A8 +20 +40 +ENDCHAR +STARTCHAR kcommaaccent +ENCODING 311 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +40 +40 +48 +50 +60 +60 +50 +68 +20 +40 +ENDCHAR +STARTCHAR kgreenlandic +ENCODING 312 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +48 +50 +60 +60 +50 +48 +00 +00 +ENDCHAR +STARTCHAR Lacute +ENCODING 313 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +40 +80 +80 +80 +80 +80 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR lacute +ENCODING 314 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +20 +60 +20 +20 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR Lcommaaccent +ENCODING 315 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +80 +80 +80 +80 +80 +80 +80 +F8 +20 +40 +ENDCHAR +STARTCHAR lcommaaccent +ENCODING 316 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +60 +20 +20 +20 +20 +20 +20 +70 +20 +40 +ENDCHAR +STARTCHAR Lcaron +ENCODING 317 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +80 +80 +80 +80 +80 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR lcaron +ENCODING 318 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +60 +20 +20 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR Ldot +ENCODING 319 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +80 +80 +80 +90 +90 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR ldot +ENCODING 320 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +60 +20 +20 +24 +24 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR Lslash +ENCODING 321 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +40 +40 +40 +60 +C0 +40 +40 +7C +00 +00 +ENDCHAR +STARTCHAR lslash +ENCODING 322 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +60 +20 +20 +30 +60 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR Nacute +ENCODING 323 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +20 +88 +88 +C8 +A8 +98 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR nacute +ENCODING 324 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +20 +F0 +88 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR Ncommaaccent +ENCODING 325 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +C8 +A8 +98 +88 +88 +A8 +20 +40 +ENDCHAR +STARTCHAR ncommaaccent +ENCODING 326 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F0 +88 +88 +88 +88 +A8 +20 +40 +ENDCHAR +STARTCHAR Ncaron +ENCODING 327 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +88 +88 +C8 +A8 +98 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR ncaron +ENCODING 328 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +20 +F0 +88 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR napostrophe +ENCODING 329 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +40 +40 +80 +F0 +88 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR Eng +ENCODING 330 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +C8 +A8 +98 +88 +88 +88 +08 +10 +ENDCHAR +STARTCHAR eng +ENCODING 331 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F0 +88 +88 +88 +88 +88 +08 +10 +ENDCHAR +STARTCHAR Omacron +ENCODING 332 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +70 +00 +70 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR omacron +ENCODING 333 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +00 +70 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR Obreve +ENCODING 334 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +70 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR obreve +ENCODING 335 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +20 +70 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR Ohungarumlaut +ENCODING 336 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +28 +50 +70 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR ohungarumlaut +ENCODING 337 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +28 +50 +70 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR OE +ENCODING 338 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +7C +90 +90 +9C +90 +90 +90 +7C +00 +00 +ENDCHAR +STARTCHAR oe +ENCODING 339 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +A8 +A8 +B0 +A0 +78 +00 +00 +ENDCHAR +STARTCHAR Racute +ENCODING 340 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +20 +F0 +88 +88 +88 +F0 +A0 +90 +88 +00 +00 +ENDCHAR +STARTCHAR racute +ENCODING 341 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +20 +B8 +C0 +80 +80 +80 +80 +00 +00 +ENDCHAR +STARTCHAR Rcommaaccent +ENCODING 342 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F0 +88 +88 +88 +F0 +A0 +90 +A8 +20 +40 +ENDCHAR +STARTCHAR rcommaaccent +ENCODING 343 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +B8 +C0 +80 +80 +80 +C0 +40 +80 +ENDCHAR +STARTCHAR Rcaron +ENCODING 344 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +F0 +88 +88 +88 +F0 +A0 +90 +88 +00 +00 +ENDCHAR +STARTCHAR rcaron +ENCODING 345 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +20 +B8 +C0 +80 +80 +80 +80 +00 +00 +ENDCHAR +STARTCHAR Sacute +ENCODING 346 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +20 +70 +88 +80 +70 +08 +08 +88 +70 +00 +00 +ENDCHAR +STARTCHAR sacute +ENCODING 347 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +20 +78 +80 +70 +08 +08 +F0 +00 +00 +ENDCHAR +STARTCHAR Scircumflex +ENCODING 348 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +50 +70 +88 +80 +70 +08 +08 +88 +70 +00 +00 +ENDCHAR +STARTCHAR scircumflex +ENCODING 349 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +50 +78 +80 +70 +08 +08 +F0 +00 +00 +ENDCHAR +STARTCHAR Scedilla +ENCODING 350 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +80 +70 +08 +08 +88 +70 +20 +40 +ENDCHAR +STARTCHAR scedilla +ENCODING 351 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +78 +80 +70 +08 +08 +F0 +20 +40 +ENDCHAR +STARTCHAR Scaron +ENCODING 352 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +70 +88 +80 +70 +08 +08 +88 +70 +00 +00 +ENDCHAR +STARTCHAR scaron +ENCODING 353 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +20 +78 +80 +70 +08 +08 +F0 +00 +00 +ENDCHAR +STARTCHAR Tcedilla +ENCODING 354 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +20 +20 +20 +20 +20 +20 +30 +10 +20 +ENDCHAR +STARTCHAR tcedilla +ENCODING 355 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +20 +70 +20 +20 +20 +20 +18 +10 +20 +ENDCHAR +STARTCHAR Tcaron +ENCODING 356 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +F8 +20 +20 +20 +20 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR tcaron +ENCODING 357 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +20 +20 +70 +20 +20 +20 +20 +18 +00 +00 +ENDCHAR +STARTCHAR Tbar +ENCODING 358 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +20 +20 +70 +20 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR tbar +ENCODING 359 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +20 +70 +20 +70 +20 +20 +18 +00 +00 +ENDCHAR +STARTCHAR Utilde +ENCODING 360 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +28 +50 +88 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR utilde +ENCODING 361 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +28 +50 +88 +88 +88 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR Umacron +ENCODING 362 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +70 +00 +88 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR umacron +ENCODING 363 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +00 +88 +88 +88 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR Ubreve +ENCODING 364 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +88 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR ubreve +ENCODING 365 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +20 +88 +88 +88 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR Uring +ENCODING 366 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +50 +A8 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uring +ENCODING 367 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +50 +A8 +88 +88 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR Uhungarumlaut +ENCODING 368 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +28 +50 +88 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uhungarumlaut +ENCODING 369 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +28 +50 +88 +88 +88 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR Uogonek +ENCODING 370 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +88 +88 +88 +88 +88 +70 +20 +18 +ENDCHAR +STARTCHAR uogonek +ENCODING 371 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +88 +88 +88 +78 +10 +0C +ENDCHAR +STARTCHAR Wcircumflex +ENCODING 372 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +50 +88 +88 +88 +88 +A8 +A8 +D8 +88 +00 +00 +ENDCHAR +STARTCHAR wcircumflex +ENCODING 373 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +50 +88 +88 +A8 +A8 +A8 +70 +00 +00 +ENDCHAR +STARTCHAR Ycircumflex +ENCODING 374 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +50 +88 +88 +50 +50 +20 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR ycircumflex +ENCODING 375 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +50 +88 +88 +88 +88 +88 +78 +08 +70 +ENDCHAR +STARTCHAR Ydieresis +ENCODING 376 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +88 +88 +50 +50 +20 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR Zacute +ENCODING 377 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +20 +F8 +08 +10 +20 +40 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR zacute +ENCODING 378 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +20 +F8 +10 +20 +40 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR Zdotaccent +ENCODING 379 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +F8 +08 +10 +20 +40 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR zdotaccent +ENCODING 380 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +20 +F8 +10 +20 +40 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR Zcaron +ENCODING 381 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +F8 +08 +10 +20 +40 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR zcaron +ENCODING 382 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +20 +F8 +10 +20 +40 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR longs +ENCODING 383 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +18 +20 +20 +20 +20 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR uni0186 +ENCODING 390 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +08 +08 +08 +08 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni018E +ENCODING 398 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +08 +08 +78 +08 +08 +08 +F8 +00 +00 +ENDCHAR +STARTCHAR Schwa +ENCODING 399 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +08 +08 +F8 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni0190 +ENCODING 400 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +80 +60 +80 +80 +88 +70 +00 +00 +ENDCHAR +STARTCHAR florin +ENCODING 402 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +28 +20 +70 +20 +20 +20 +20 +A0 +40 +ENDCHAR +STARTCHAR uni019D +ENCODING 413 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +C8 +A8 +98 +88 +88 +C8 +40 +80 +ENDCHAR +STARTCHAR uni019E +ENCODING 414 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F0 +88 +88 +88 +88 +88 +08 +08 +ENDCHAR +STARTCHAR uni01B5 +ENCODING 437 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +08 +10 +F8 +20 +40 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR uni01B6 +ENCODING 438 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F8 +10 +78 +20 +40 +F8 +00 +00 +ENDCHAR +STARTCHAR Ezh +ENCODING 439 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +08 +10 +30 +08 +08 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni01CD +ENCODING 461 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +70 +88 +88 +88 +F8 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR uni01CE +ENCODING 462 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +20 +70 +08 +78 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR uni01CF +ENCODING 463 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +70 +20 +20 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR uni01D0 +ENCODING 464 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +20 +60 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR uni01D1 +ENCODING 465 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +70 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni01D2 +ENCODING 466 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +20 +70 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni01D3 +ENCODING 467 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +88 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni01D4 +ENCODING 468 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +20 +88 +88 +88 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR uni01E2 +ENCODING 482 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +78 +00 +7C +90 +90 +FC +90 +90 +90 +9C +00 +00 +ENDCHAR +STARTCHAR uni01E3 +ENCODING 483 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +00 +70 +28 +68 +B0 +A0 +78 +00 +00 +ENDCHAR +STARTCHAR uni01E4 +ENCODING 484 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +78 +80 +80 +B8 +88 +9C +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni01E5 +ENCODING 485 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +78 +88 +9C +88 +88 +78 +08 +70 +ENDCHAR +STARTCHAR Gcaron +ENCODING 486 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +70 +88 +80 +80 +B8 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR gcaron +ENCODING 487 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +20 +78 +88 +88 +88 +88 +78 +08 +70 +ENDCHAR +STARTCHAR uni01E8 +ENCODING 488 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +88 +90 +A0 +C0 +C0 +A0 +90 +88 +00 +00 +ENDCHAR +STARTCHAR uni01E9 +ENCODING 489 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +40 +40 +48 +50 +60 +60 +50 +48 +00 +00 +ENDCHAR +STARTCHAR uni01EA +ENCODING 490 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +88 +88 +88 +88 +88 +70 +20 +18 +ENDCHAR +STARTCHAR uni01EB +ENCODING 491 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +88 +88 +88 +70 +20 +18 +ENDCHAR +STARTCHAR uni01EC +ENCODING 492 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +70 +00 +70 +88 +88 +88 +88 +88 +88 +70 +20 +18 +ENDCHAR +STARTCHAR uni01ED +ENCODING 493 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +00 +70 +88 +88 +88 +88 +70 +20 +18 +ENDCHAR +STARTCHAR uni01EE +ENCODING 494 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +F8 +08 +10 +30 +08 +08 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni01EF +ENCODING 495 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +20 +F8 +08 +10 +30 +08 +08 +88 +70 +ENDCHAR +STARTCHAR uni01F0 +ENCODING 496 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +14 +08 +18 +08 +08 +08 +08 +08 +48 +30 +ENDCHAR +STARTCHAR uni01F4 +ENCODING 500 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +20 +70 +88 +80 +80 +B8 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni01F5 +ENCODING 501 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +20 +78 +88 +88 +88 +88 +78 +08 +70 +ENDCHAR +STARTCHAR AEacute +ENCODING 508 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +10 +7C +90 +90 +FC +90 +90 +90 +9C +00 +00 +ENDCHAR +STARTCHAR aeacute +ENCODING 509 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +20 +70 +28 +68 +B0 +A0 +78 +00 +00 +ENDCHAR +STARTCHAR Oslashacute +ENCODING 510 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +20 +74 +88 +98 +A8 +C8 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR oslashacute +ENCODING 511 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +20 +74 +98 +A8 +C8 +88 +70 +00 +00 +ENDCHAR +STARTCHAR Scommaaccent +ENCODING 536 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +80 +70 +08 +08 +88 +70 +20 +40 +ENDCHAR +STARTCHAR scommaaccent +ENCODING 537 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +78 +80 +70 +08 +08 +F0 +20 +40 +ENDCHAR +STARTCHAR Tcommaaccent +ENCODING 538 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +20 +20 +20 +20 +20 +20 +30 +10 +20 +ENDCHAR +STARTCHAR tcommaaccent +ENCODING 539 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +20 +70 +20 +20 +20 +20 +18 +08 +10 +ENDCHAR +STARTCHAR uni0232 +ENCODING 562 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +70 +00 +88 +88 +50 +50 +20 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR uni0233 +ENCODING 563 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +00 +88 +88 +88 +88 +88 +78 +08 +70 +ENDCHAR +STARTCHAR dotlessj +ENCODING 567 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +18 +08 +08 +08 +08 +08 +48 +30 +ENDCHAR +STARTCHAR uni0254 +ENCODING 596 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +08 +08 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni0258 +ENCODING 600 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +F8 +08 +88 +70 +00 +00 +ENDCHAR +STARTCHAR schwa +ENCODING 601 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +08 +F8 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni025B +ENCODING 603 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +60 +80 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni0272 +ENCODING 626 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F0 +88 +88 +88 +88 +C8 +40 +80 +ENDCHAR +STARTCHAR ezh +ENCODING 658 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F8 +08 +10 +30 +08 +08 +88 +70 +ENDCHAR +STARTCHAR commaturnedmod +ENCODING 699 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +20 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR afii57929 +ENCODING 700 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +20 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR afii64937 +ENCODING 701 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +10 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR circumflex +ENCODING 710 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +50 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR caron +ENCODING 711 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR breve +ENCODING 728 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR dotaccent +ENCODING 729 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR ogonek +ENCODING 731 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +10 +20 +18 +ENDCHAR +STARTCHAR tilde +ENCODING 732 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +28 +50 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR hungarumlaut +ENCODING 733 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +28 +50 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR gravecomb +ENCODING 768 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +20 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR acutecomb +ENCODING 769 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +20 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni0302 +ENCODING 770 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +50 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR tildecomb +ENCODING 771 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +28 +50 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni0304 +ENCODING 772 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +70 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni0305 +ENCODING 773 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +F8 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni0306 +ENCODING 774 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni0307 +ENCODING 775 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni0308 +ENCODING 776 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni030A +ENCODING 778 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +50 +20 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni030B +ENCODING 779 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +28 +50 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni030C +ENCODING 780 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni0329 +ENCODING 809 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +20 +20 +ENDCHAR +STARTCHAR tonos +ENCODING 900 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +80 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR dieresistonos +ENCODING 901 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +20 +50 +50 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR Alphatonos +ENCODING 902 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +80 +70 +88 +88 +88 +F8 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR anoteleia +ENCODING 903 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +20 +20 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR Epsilontonos +ENCODING 904 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +80 +F8 +80 +80 +F0 +80 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR Etatonos +ENCODING 905 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +80 +88 +88 +88 +F8 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR Iotatonos +ENCODING 906 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +80 +70 +20 +20 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR Omicrontonos +ENCODING 908 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +80 +70 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR Upsilontonos +ENCODING 910 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +80 +88 +88 +50 +50 +20 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR Omegatonos +ENCODING 911 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +80 +70 +88 +88 +88 +88 +88 +50 +D8 +00 +00 +ENDCHAR +STARTCHAR iotadieresistonos +ENCODING 912 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +20 +50 +50 +60 +20 +20 +20 +20 +18 +00 +00 +ENDCHAR +STARTCHAR Alpha +ENCODING 913 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +88 +88 +F8 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR Beta +ENCODING 914 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F0 +88 +88 +F0 +88 +88 +88 +F0 +00 +00 +ENDCHAR +STARTCHAR Gamma +ENCODING 915 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +80 +80 +80 +80 +80 +80 +80 +00 +00 +ENDCHAR +STARTCHAR Delta +ENCODING 916 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +20 +50 +50 +88 +88 +88 +F8 +00 +00 +ENDCHAR +STARTCHAR Epsilon +ENCODING 917 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +80 +80 +F0 +80 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR Zeta +ENCODING 918 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +08 +10 +20 +40 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR Eta +ENCODING 919 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +88 +F8 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR Theta +ENCODING 920 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +88 +A8 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR Iota +ENCODING 921 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +20 +20 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR Kappa +ENCODING 922 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +90 +A0 +C0 +C0 +A0 +90 +88 +00 +00 +ENDCHAR +STARTCHAR Lambda +ENCODING 923 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +20 +50 +50 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR Mu +ENCODING 924 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +D8 +A8 +A8 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR Nu +ENCODING 925 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +C8 +A8 +98 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR Xi +ENCODING 926 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +00 +00 +70 +00 +00 +00 +F8 +00 +00 +ENDCHAR +STARTCHAR Omicron +ENCODING 927 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR Pi +ENCODING 928 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +88 +88 +88 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR Rho +ENCODING 929 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F0 +88 +88 +88 +F0 +80 +80 +80 +00 +00 +ENDCHAR +STARTCHAR Sigma +ENCODING 931 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +40 +20 +10 +10 +20 +40 +F8 +00 +00 +ENDCHAR +STARTCHAR Tau +ENCODING 932 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +20 +20 +20 +20 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR Upsilon +ENCODING 933 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +50 +50 +20 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR Phi +ENCODING 934 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +70 +A8 +A8 +A8 +A8 +70 +20 +00 +00 +ENDCHAR +STARTCHAR Chi +ENCODING 935 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +50 +20 +20 +50 +88 +88 +00 +00 +ENDCHAR +STARTCHAR Psi +ENCODING 936 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +A8 +A8 +A8 +A8 +A8 +70 +20 +20 +00 +00 +ENDCHAR +STARTCHAR Omega +ENCODING 937 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +88 +88 +88 +88 +50 +D8 +00 +00 +ENDCHAR +STARTCHAR Iotadieresis +ENCODING 938 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +70 +20 +20 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR Upsilondieresis +ENCODING 939 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +88 +88 +50 +50 +20 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR alphatonos +ENCODING 940 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +20 +68 +90 +90 +90 +90 +68 +00 +00 +ENDCHAR +STARTCHAR epsilontonos +ENCODING 941 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +20 +70 +88 +60 +80 +88 +70 +00 +00 +ENDCHAR +STARTCHAR etatonos +ENCODING 942 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +20 +F0 +88 +88 +88 +88 +88 +08 +08 +ENDCHAR +STARTCHAR iotatonos +ENCODING 943 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +20 +60 +20 +20 +20 +20 +18 +00 +00 +ENDCHAR +STARTCHAR upsilondieresistonos +ENCODING 944 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +20 +50 +50 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR alpha +ENCODING 945 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +68 +90 +90 +90 +90 +68 +00 +00 +ENDCHAR +STARTCHAR beta +ENCODING 946 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +E0 +90 +90 +F0 +88 +88 +88 +F0 +80 +80 +ENDCHAR +STARTCHAR gamma +ENCODING 947 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +88 +50 +50 +20 +20 +20 +ENDCHAR +STARTCHAR delta +ENCODING 948 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F0 +40 +20 +70 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR epsilon +ENCODING 949 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +60 +80 +88 +70 +00 +00 +ENDCHAR +STARTCHAR zeta +ENCODING 950 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +10 +20 +40 +80 +80 +80 +70 +08 +10 +ENDCHAR +STARTCHAR eta +ENCODING 951 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F0 +88 +88 +88 +88 +88 +08 +08 +ENDCHAR +STARTCHAR theta +ENCODING 952 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +30 +48 +48 +78 +48 +48 +48 +30 +00 +00 +ENDCHAR +STARTCHAR iota +ENCODING 953 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +60 +20 +20 +20 +20 +18 +00 +00 +ENDCHAR +STARTCHAR kappa +ENCODING 954 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +48 +50 +60 +60 +50 +48 +00 +00 +ENDCHAR +STARTCHAR lambda +ENCODING 955 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +40 +40 +20 +20 +50 +50 +88 +88 +00 +00 +ENDCHAR +STARTCHAR mugreek +ENCODING 956 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +88 +88 +98 +E8 +80 +80 +ENDCHAR +STARTCHAR nu +ENCODING 957 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +50 +50 +20 +20 +00 +00 +ENDCHAR +STARTCHAR xi +ENCODING 958 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +78 +80 +80 +70 +80 +80 +80 +70 +08 +10 +ENDCHAR +STARTCHAR omicron +ENCODING 959 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR pi +ENCODING 960 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F8 +88 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR rho +ENCODING 961 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +88 +88 +88 +F0 +80 +80 +ENDCHAR +STARTCHAR sigma1 +ENCODING 962 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +80 +80 +80 +70 +08 +10 +ENDCHAR +STARTCHAR sigma +ENCODING 963 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +78 +90 +90 +90 +90 +60 +00 +00 +ENDCHAR +STARTCHAR tau +ENCODING 964 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F8 +20 +20 +20 +20 +10 +00 +00 +ENDCHAR +STARTCHAR upsilon +ENCODING 965 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR phi +ENCODING 966 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +90 +A8 +A8 +A8 +A8 +70 +20 +20 +ENDCHAR +STARTCHAR chi +ENCODING 967 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +50 +20 +20 +50 +88 +88 +ENDCHAR +STARTCHAR psi +ENCODING 968 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +A8 +A8 +A8 +A8 +A8 +70 +20 +20 +ENDCHAR +STARTCHAR omega +ENCODING 969 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +50 +88 +A8 +A8 +A8 +50 +00 +00 +ENDCHAR +STARTCHAR iotadieresis +ENCODING 970 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +60 +20 +20 +20 +20 +18 +00 +00 +ENDCHAR +STARTCHAR upsilondieresis +ENCODING 971 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR omicrontonos +ENCODING 972 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +20 +70 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR upsilontonos +ENCODING 973 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +20 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR omegatonos +ENCODING 974 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +20 +50 +88 +A8 +A8 +A8 +50 +00 +00 +ENDCHAR +STARTCHAR theta1 +ENCODING 977 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +30 +48 +48 +3C +08 +C8 +48 +30 +00 +00 +ENDCHAR +STARTCHAR phi1 +ENCODING 981 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +20 +70 +A8 +A8 +A8 +A8 +70 +20 +00 +ENDCHAR +STARTCHAR uni03F0 +ENCODING 1008 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +C4 +28 +10 +20 +50 +8C +00 +00 +ENDCHAR +STARTCHAR uni03F1 +ENCODING 1009 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +88 +88 +88 +F0 +80 +70 +ENDCHAR +STARTCHAR uni03F2 +ENCODING 1010 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +80 +80 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni03F3 +ENCODING 1011 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +08 +08 +00 +18 +08 +08 +08 +08 +08 +48 +30 +ENDCHAR +STARTCHAR uni03F4 +ENCODING 1012 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +88 +F8 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni03F5 +ENCODING 1013 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +78 +80 +F0 +80 +80 +78 +00 +00 +ENDCHAR +STARTCHAR uni03F6 +ENCODING 1014 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F0 +08 +78 +08 +08 +F0 +00 +00 +ENDCHAR +STARTCHAR uni0400 +ENCODING 1024 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +20 +F8 +80 +80 +F0 +80 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR afii10023 +ENCODING 1025 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +F8 +80 +80 +F0 +80 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR afii10051 +ENCODING 1026 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +E0 +40 +40 +70 +48 +48 +48 +48 +08 +10 +ENDCHAR +STARTCHAR afii10052 +ENCODING 1027 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +20 +F8 +80 +80 +80 +80 +80 +80 +80 +00 +00 +ENDCHAR +STARTCHAR afii10053 +ENCODING 1028 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +80 +F0 +80 +80 +88 +70 +00 +00 +ENDCHAR +STARTCHAR afii10054 +ENCODING 1029 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +80 +70 +08 +08 +88 +70 +00 +00 +ENDCHAR +STARTCHAR afii10055 +ENCODING 1030 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +20 +20 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR afii10056 +ENCODING 1031 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +70 +20 +20 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR afii10057 +ENCODING 1032 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +38 +10 +10 +10 +10 +90 +90 +60 +00 +00 +ENDCHAR +STARTCHAR afii10058 +ENCODING 1033 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +60 +A0 +B0 +A8 +A8 +A8 +A8 +B0 +00 +00 +ENDCHAR +STARTCHAR afii10059 +ENCODING 1034 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +A0 +A0 +B0 +E8 +A8 +A8 +A8 +B0 +00 +00 +ENDCHAR +STARTCHAR afii10060 +ENCODING 1035 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +E0 +40 +40 +70 +48 +48 +48 +48 +00 +00 +ENDCHAR +STARTCHAR afii10061 +ENCODING 1036 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +20 +88 +90 +A0 +C0 +C0 +A0 +90 +88 +00 +00 +ENDCHAR +STARTCHAR uni040D +ENCODING 1037 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +20 +88 +88 +98 +A8 +C8 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR afii10062 +ENCODING 1038 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +88 +88 +88 +88 +78 +08 +08 +70 +00 +00 +ENDCHAR +STARTCHAR afii10145 +ENCODING 1039 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +88 +88 +88 +88 +88 +F8 +20 +20 +ENDCHAR +STARTCHAR afii10017 +ENCODING 1040 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +88 +88 +F8 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR afii10018 +ENCODING 1041 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F0 +80 +80 +F0 +88 +88 +88 +F0 +00 +00 +ENDCHAR +STARTCHAR afii10019 +ENCODING 1042 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F0 +88 +88 +F0 +88 +88 +88 +F0 +00 +00 +ENDCHAR +STARTCHAR afii10020 +ENCODING 1043 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +80 +80 +80 +80 +80 +80 +80 +00 +00 +ENDCHAR +STARTCHAR afii10021 +ENCODING 1044 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +38 +48 +48 +48 +48 +48 +48 +FC +84 +00 +ENDCHAR +STARTCHAR afii10022 +ENCODING 1045 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +80 +80 +F0 +80 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR afii10024 +ENCODING 1046 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +A8 +A8 +A8 +70 +70 +A8 +A8 +A8 +00 +00 +ENDCHAR +STARTCHAR afii10025 +ENCODING 1047 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +08 +30 +08 +08 +88 +70 +00 +00 +ENDCHAR +STARTCHAR afii10026 +ENCODING 1048 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +98 +A8 +C8 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR afii10027 +ENCODING 1049 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +88 +88 +98 +A8 +C8 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR afii10028 +ENCODING 1050 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +90 +A0 +C0 +C0 +A0 +90 +88 +00 +00 +ENDCHAR +STARTCHAR afii10029 +ENCODING 1051 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +38 +48 +48 +48 +48 +48 +48 +88 +00 +00 +ENDCHAR +STARTCHAR afii10030 +ENCODING 1052 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +D8 +A8 +A8 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR afii10031 +ENCODING 1053 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +88 +F8 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR afii10032 +ENCODING 1054 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR afii10033 +ENCODING 1055 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +88 +88 +88 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR afii10034 +ENCODING 1056 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F0 +88 +88 +88 +F0 +80 +80 +80 +00 +00 +ENDCHAR +STARTCHAR afii10035 +ENCODING 1057 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +80 +80 +80 +80 +88 +70 +00 +00 +ENDCHAR +STARTCHAR afii10036 +ENCODING 1058 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +20 +20 +20 +20 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR afii10037 +ENCODING 1059 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +88 +88 +78 +08 +08 +70 +00 +00 +ENDCHAR +STARTCHAR afii10038 +ENCODING 1060 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +20 +70 +A8 +A8 +A8 +A8 +A8 +A8 +70 +20 +00 +ENDCHAR +STARTCHAR afii10039 +ENCODING 1061 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +50 +20 +20 +50 +88 +88 +00 +00 +ENDCHAR +STARTCHAR afii10040 +ENCODING 1062 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +88 +88 +88 +88 +88 +7C +04 +04 +ENDCHAR +STARTCHAR afii10041 +ENCODING 1063 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +88 +88 +78 +08 +08 +08 +00 +00 +ENDCHAR +STARTCHAR afii10042 +ENCODING 1064 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +A8 +A8 +A8 +A8 +A8 +A8 +A8 +78 +00 +00 +ENDCHAR +STARTCHAR afii10043 +ENCODING 1065 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +A8 +A8 +A8 +A8 +A8 +A8 +A8 +7C +04 +04 +ENDCHAR +STARTCHAR afii10044 +ENCODING 1066 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +C0 +40 +70 +48 +48 +48 +48 +70 +00 +00 +ENDCHAR +STARTCHAR afii10045 +ENCODING 1067 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +C8 +A8 +A8 +A8 +A8 +C8 +00 +00 +ENDCHAR +STARTCHAR afii10046 +ENCODING 1068 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +40 +40 +70 +48 +48 +48 +48 +70 +00 +00 +ENDCHAR +STARTCHAR afii10047 +ENCODING 1069 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +08 +38 +08 +08 +88 +70 +00 +00 +ENDCHAR +STARTCHAR afii10048 +ENCODING 1070 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +90 +A8 +A8 +A8 +E8 +A8 +A8 +90 +00 +00 +ENDCHAR +STARTCHAR afii10049 +ENCODING 1071 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +78 +88 +88 +88 +78 +28 +48 +88 +00 +00 +ENDCHAR +STARTCHAR afii10065 +ENCODING 1072 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +08 +78 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR afii10066 +ENCODING 1073 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +80 +F0 +88 +88 +88 +88 +F0 +00 +00 +ENDCHAR +STARTCHAR afii10067 +ENCODING 1074 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +E0 +90 +90 +F0 +88 +88 +88 +F0 +00 +00 +ENDCHAR +STARTCHAR afii10068 +ENCODING 1075 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F8 +80 +80 +80 +80 +80 +00 +00 +ENDCHAR +STARTCHAR afii10069 +ENCODING 1076 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +78 +88 +88 +88 +88 +78 +08 +70 +ENDCHAR +STARTCHAR afii10070 +ENCODING 1077 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +F8 +80 +80 +78 +00 +00 +ENDCHAR +STARTCHAR afii10072 +ENCODING 1078 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +A8 +A8 +70 +A8 +A8 +A8 +00 +00 +ENDCHAR +STARTCHAR afii10073 +ENCODING 1079 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +30 +08 +88 +70 +00 +00 +ENDCHAR +STARTCHAR afii10074 +ENCODING 1080 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +88 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR afii10075 +ENCODING 1081 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +20 +88 +88 +88 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR afii10076 +ENCODING 1082 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +48 +50 +60 +60 +50 +48 +00 +00 +ENDCHAR +STARTCHAR afii10077 +ENCODING 1083 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +38 +48 +48 +48 +48 +88 +00 +00 +ENDCHAR +STARTCHAR afii10078 +ENCODING 1084 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +D8 +A8 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR afii10079 +ENCODING 1085 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +F8 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR afii10080 +ENCODING 1086 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR afii10081 +ENCODING 1087 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F8 +88 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR afii10082 +ENCODING 1088 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F0 +88 +88 +88 +88 +F0 +80 +80 +ENDCHAR +STARTCHAR afii10083 +ENCODING 1089 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +80 +80 +88 +70 +00 +00 +ENDCHAR +STARTCHAR afii10084 +ENCODING 1090 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F8 +20 +20 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR afii10085 +ENCODING 1091 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +88 +88 +88 +78 +08 +70 +ENDCHAR +STARTCHAR afii10086 +ENCODING 1092 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +20 +70 +A8 +A8 +A8 +A8 +70 +20 +00 +ENDCHAR +STARTCHAR afii10087 +ENCODING 1093 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +50 +20 +20 +50 +88 +00 +00 +ENDCHAR +STARTCHAR afii10088 +ENCODING 1094 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +88 +88 +88 +7C +04 +04 +ENDCHAR +STARTCHAR afii10089 +ENCODING 1095 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +88 +78 +08 +08 +00 +00 +ENDCHAR +STARTCHAR afii10090 +ENCODING 1096 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +A8 +A8 +A8 +A8 +A8 +78 +00 +00 +ENDCHAR +STARTCHAR afii10091 +ENCODING 1097 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +A8 +A8 +A8 +A8 +A8 +7C +04 +04 +ENDCHAR +STARTCHAR afii10092 +ENCODING 1098 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +C0 +40 +70 +48 +48 +70 +00 +00 +ENDCHAR +STARTCHAR afii10093 +ENCODING 1099 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +C8 +A8 +A8 +C8 +00 +00 +ENDCHAR +STARTCHAR afii10094 +ENCODING 1100 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +40 +40 +70 +48 +48 +70 +00 +00 +ENDCHAR +STARTCHAR afii10095 +ENCODING 1101 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +38 +08 +88 +70 +00 +00 +ENDCHAR +STARTCHAR afii10096 +ENCODING 1102 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +90 +A8 +A8 +E8 +A8 +90 +00 +00 +ENDCHAR +STARTCHAR afii10097 +ENCODING 1103 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +78 +88 +88 +78 +28 +48 +00 +00 +ENDCHAR +STARTCHAR uni0450 +ENCODING 1104 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +40 +20 +70 +88 +F8 +80 +80 +78 +00 +00 +ENDCHAR +STARTCHAR afii10071 +ENCODING 1105 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +70 +88 +F8 +80 +80 +78 +00 +00 +ENDCHAR +STARTCHAR afii10099 +ENCODING 1106 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +40 +F0 +40 +70 +48 +48 +48 +48 +08 +10 +ENDCHAR +STARTCHAR afii10100 +ENCODING 1107 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +20 +F8 +80 +80 +80 +80 +80 +00 +00 +ENDCHAR +STARTCHAR afii10101 +ENCODING 1108 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +E0 +80 +88 +70 +00 +00 +ENDCHAR +STARTCHAR afii10102 +ENCODING 1109 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +78 +80 +70 +08 +08 +F0 +00 +00 +ENDCHAR +STARTCHAR afii10103 +ENCODING 1110 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +20 +20 +00 +60 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR afii10104 +ENCODING 1111 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +60 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR afii10105 +ENCODING 1112 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +08 +08 +00 +18 +08 +08 +08 +08 +08 +48 +30 +ENDCHAR +STARTCHAR afii10106 +ENCODING 1113 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +60 +A0 +B0 +A8 +A8 +B0 +00 +00 +ENDCHAR +STARTCHAR afii10107 +ENCODING 1114 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +A0 +A0 +F0 +A8 +A8 +B0 +00 +00 +ENDCHAR +STARTCHAR afii10108 +ENCODING 1115 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +40 +F0 +40 +70 +48 +48 +48 +48 +00 +00 +ENDCHAR +STARTCHAR afii10109 +ENCODING 1116 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +08 +10 +48 +50 +60 +60 +50 +48 +00 +00 +ENDCHAR +STARTCHAR uni045D +ENCODING 1117 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +40 +20 +88 +88 +88 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR afii10110 +ENCODING 1118 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +20 +88 +88 +88 +88 +88 +78 +08 +70 +ENDCHAR +STARTCHAR afii10193 +ENCODING 1119 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +88 +88 +88 +F8 +20 +20 +ENDCHAR +STARTCHAR afii10146 +ENCODING 1122 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +40 +F0 +40 +70 +48 +48 +48 +70 +00 +00 +ENDCHAR +STARTCHAR afii10194 +ENCODING 1123 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +40 +40 +E0 +40 +70 +48 +48 +70 +00 +00 +ENDCHAR +STARTCHAR uni046A +ENCODING 1130 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +88 +50 +20 +70 +A8 +A8 +A8 +00 +00 +ENDCHAR +STARTCHAR uni046B +ENCODING 1131 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F8 +50 +20 +70 +A8 +A8 +00 +00 +ENDCHAR +STARTCHAR afii10050 +ENCODING 1168 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +F8 +80 +80 +80 +80 +80 +80 +80 +00 +00 +ENDCHAR +STARTCHAR afii10098 +ENCODING 1169 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +08 +08 +F8 +80 +80 +80 +80 +80 +00 +00 +ENDCHAR +STARTCHAR uni0492 +ENCODING 1170 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +7C +40 +40 +40 +F0 +40 +40 +40 +00 +00 +ENDCHAR +STARTCHAR uni0493 +ENCODING 1171 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +78 +40 +40 +F0 +40 +40 +00 +00 +ENDCHAR +STARTCHAR uni0494 +ENCODING 1172 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +80 +80 +80 +F0 +88 +88 +88 +08 +10 +ENDCHAR +STARTCHAR uni0495 +ENCODING 1173 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F8 +80 +80 +E0 +90 +90 +10 +20 +ENDCHAR +STARTCHAR uni0496 +ENCODING 1174 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +A8 +A8 +A8 +70 +70 +A8 +A8 +AC +04 +04 +ENDCHAR +STARTCHAR uni0497 +ENCODING 1175 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +A8 +A8 +70 +A8 +A8 +AC +04 +04 +ENDCHAR +STARTCHAR uni0498 +ENCODING 1176 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +08 +30 +08 +08 +88 +70 +20 +20 +ENDCHAR +STARTCHAR uni0499 +ENCODING 1177 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +30 +08 +88 +70 +20 +20 +ENDCHAR +STARTCHAR uni049A +ENCODING 1178 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +90 +A0 +C0 +C0 +A0 +90 +8C +04 +04 +ENDCHAR +STARTCHAR uni049B +ENCODING 1179 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +48 +50 +60 +60 +50 +4C +04 +04 +ENDCHAR +STARTCHAR uni049C +ENCODING 1180 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +84 +A8 +B0 +E0 +E0 +B0 +A8 +84 +00 +00 +ENDCHAR +STARTCHAR uni049D +ENCODING 1181 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +A8 +B0 +E0 +E0 +B0 +A8 +00 +00 +ENDCHAR +STARTCHAR uni04A0 +ENCODING 1184 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +C4 +48 +50 +60 +60 +50 +48 +44 +00 +00 +ENDCHAR +STARTCHAR uni04A1 +ENCODING 1185 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +C8 +50 +60 +60 +50 +48 +00 +00 +ENDCHAR +STARTCHAR uni04A2 +ENCODING 1186 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +88 +F8 +88 +88 +88 +8C +04 +04 +ENDCHAR +STARTCHAR uni04A3 +ENCODING 1187 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +F8 +88 +88 +8C +04 +04 +ENDCHAR +STARTCHAR uni04A4 +ENCODING 1188 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +9C +90 +90 +F0 +90 +90 +90 +90 +00 +00 +ENDCHAR +STARTCHAR uni04A5 +ENCODING 1189 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +9C +90 +F0 +90 +90 +90 +00 +00 +ENDCHAR +STARTCHAR uni04AA +ENCODING 1194 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +80 +80 +80 +80 +88 +70 +20 +20 +ENDCHAR +STARTCHAR uni04AB +ENCODING 1195 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +80 +80 +88 +70 +20 +20 +ENDCHAR +STARTCHAR uni04AE +ENCODING 1198 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +50 +50 +20 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR uni04AF +ENCODING 1199 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +88 +50 +50 +20 +20 +20 +ENDCHAR +STARTCHAR uni04B0 +ENCODING 1200 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +50 +50 +20 +70 +20 +20 +00 +00 +ENDCHAR +STARTCHAR uni04B1 +ENCODING 1201 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +88 +50 +50 +20 +70 +20 +ENDCHAR +STARTCHAR uni04B2 +ENCODING 1202 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +50 +20 +20 +50 +88 +8C +04 +04 +ENDCHAR +STARTCHAR uni04B3 +ENCODING 1203 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +50 +20 +20 +50 +8C +04 +04 +ENDCHAR +STARTCHAR uni04B6 +ENCODING 1206 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +88 +88 +78 +08 +08 +0C +04 +04 +ENDCHAR +STARTCHAR uni04B7 +ENCODING 1207 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +88 +78 +08 +0C +04 +04 +ENDCHAR +STARTCHAR uni04B8 +ENCODING 1208 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +A8 +A8 +78 +28 +28 +08 +00 +00 +ENDCHAR +STARTCHAR uni04B9 +ENCODING 1209 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +A8 +A8 +78 +28 +08 +00 +00 +ENDCHAR +STARTCHAR uni04BA +ENCODING 1210 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +80 +80 +80 +F0 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR uni04BB +ENCODING 1211 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +80 +80 +F0 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR uni04C0 +ENCODING 1216 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +20 +20 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR uni04C1 +ENCODING 1217 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +A8 +A8 +A8 +70 +70 +A8 +A8 +A8 +00 +00 +ENDCHAR +STARTCHAR uni04C2 +ENCODING 1218 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +20 +A8 +A8 +70 +A8 +A8 +A8 +00 +00 +ENDCHAR +STARTCHAR uni04CF +ENCODING 1231 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +60 +20 +20 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR uni04D0 +ENCODING 1232 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +70 +88 +88 +88 +F8 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR uni04D1 +ENCODING 1233 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +20 +70 +08 +78 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR uni04D2 +ENCODING 1234 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +70 +88 +88 +88 +F8 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR uni04D3 +ENCODING 1235 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +70 +08 +78 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR uni04D4 +ENCODING 1236 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +7C +90 +90 +FC +90 +90 +90 +9C +00 +00 +ENDCHAR +STARTCHAR uni04D5 +ENCODING 1237 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +28 +68 +B0 +A0 +78 +00 +00 +ENDCHAR +STARTCHAR uni04D6 +ENCODING 1238 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +20 +F8 +80 +80 +F0 +80 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR uni04D7 +ENCODING 1239 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +20 +70 +88 +F8 +80 +80 +78 +00 +00 +ENDCHAR +STARTCHAR uni04D8 +ENCODING 1240 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +08 +08 +F8 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR afii10846 +ENCODING 1241 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +08 +F8 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni04DA +ENCODING 1242 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +70 +88 +08 +08 +F8 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni04DB +ENCODING 1243 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +70 +88 +08 +F8 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni04DC +ENCODING 1244 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +A8 +A8 +A8 +70 +70 +A8 +A8 +A8 +00 +00 +ENDCHAR +STARTCHAR uni04DD +ENCODING 1245 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +A8 +A8 +70 +A8 +A8 +A8 +00 +00 +ENDCHAR +STARTCHAR uni04DE +ENCODING 1246 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +70 +88 +08 +30 +08 +08 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni04DF +ENCODING 1247 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +70 +88 +30 +08 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni04E2 +ENCODING 1250 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +70 +00 +88 +88 +98 +A8 +C8 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR uni04E3 +ENCODING 1251 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +00 +88 +88 +88 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR uni04E4 +ENCODING 1252 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +88 +88 +98 +A8 +C8 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR uni04E5 +ENCODING 1253 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +88 +88 +88 +88 +88 +78 +00 +00 +ENDCHAR +STARTCHAR uni04E6 +ENCODING 1254 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +70 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni04E7 +ENCODING 1255 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +70 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni04E8 +ENCODING 1256 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +88 +F8 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni04E9 +ENCODING 1257 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +F8 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni04EA +ENCODING 1258 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +70 +88 +88 +88 +F8 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni04EB +ENCODING 1259 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +70 +88 +F8 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni04EC +ENCODING 1260 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +70 +88 +08 +38 +08 +08 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni04ED +ENCODING 1261 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +70 +88 +38 +08 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni04EE +ENCODING 1262 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +70 +00 +88 +88 +88 +88 +78 +08 +08 +70 +00 +00 +ENDCHAR +STARTCHAR uni04EF +ENCODING 1263 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +00 +88 +88 +88 +88 +88 +78 +08 +70 +ENDCHAR +STARTCHAR uni04F0 +ENCODING 1264 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +88 +88 +88 +88 +78 +08 +08 +70 +00 +00 +ENDCHAR +STARTCHAR uni04F1 +ENCODING 1265 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +88 +88 +88 +88 +88 +78 +08 +70 +ENDCHAR +STARTCHAR uni04F2 +ENCODING 1266 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +28 +50 +88 +88 +88 +88 +78 +08 +08 +70 +00 +00 +ENDCHAR +STARTCHAR uni04F3 +ENCODING 1267 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +28 +50 +88 +88 +88 +88 +88 +78 +08 +70 +ENDCHAR +STARTCHAR uni04F4 +ENCODING 1268 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +88 +88 +88 +88 +78 +08 +08 +08 +00 +00 +ENDCHAR +STARTCHAR uni04F5 +ENCODING 1269 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +88 +88 +88 +78 +08 +08 +00 +00 +ENDCHAR +STARTCHAR uni04F8 +ENCODING 1272 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +88 +88 +C8 +A8 +A8 +A8 +A8 +C8 +00 +00 +ENDCHAR +STARTCHAR uni04F9 +ENCODING 1273 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +88 +88 +C8 +A8 +A8 +C8 +00 +00 +ENDCHAR +STARTCHAR afii57664 +ENCODING 1488 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +50 +20 +50 +90 +88 +88 +00 +00 +ENDCHAR +STARTCHAR afii57665 +ENCODING 1489 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F0 +08 +08 +08 +08 +08 +08 +FC +00 +00 +ENDCHAR +STARTCHAR afii57666 +ENCODING 1490 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +60 +10 +10 +10 +10 +30 +48 +88 +00 +00 +ENDCHAR +STARTCHAR afii57667 +ENCODING 1491 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +FC +08 +08 +08 +08 +08 +08 +08 +00 +00 +ENDCHAR +STARTCHAR afii57668 +ENCODING 1492 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F0 +08 +88 +88 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR afii57669 +ENCODING 1493 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +C0 +20 +20 +20 +20 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR afii57670 +ENCODING 1494 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +78 +10 +20 +20 +20 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR afii57671 +ENCODING 1495 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F0 +88 +88 +88 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR afii57672 +ENCODING 1496 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +90 +98 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR afii57673 +ENCODING 1497 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +C0 +20 +20 +20 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR afii57674 +ENCODING 1498 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F0 +08 +08 +08 +08 +08 +08 +08 +08 +08 +ENDCHAR +STARTCHAR afii57675 +ENCODING 1499 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F0 +08 +08 +08 +08 +08 +08 +F0 +00 +00 +ENDCHAR +STARTCHAR afii57676 +ENCODING 1500 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +80 +80 +F8 +08 +08 +08 +10 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR afii57677 +ENCODING 1501 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F0 +88 +88 +88 +88 +88 +88 +F8 +00 +00 +ENDCHAR +STARTCHAR afii57678 +ENCODING 1502 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +B0 +C8 +88 +88 +88 +88 +88 +98 +00 +00 +ENDCHAR +STARTCHAR afii57679 +ENCODING 1503 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +C0 +20 +20 +20 +20 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR afii57680 +ENCODING 1504 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +60 +10 +10 +10 +10 +10 +10 +70 +00 +00 +ENDCHAR +STARTCHAR afii57681 +ENCODING 1505 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F0 +48 +48 +48 +48 +48 +48 +30 +00 +00 +ENDCHAR +STARTCHAR afii57682 +ENCODING 1506 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +88 +48 +48 +48 +30 +C0 +00 +00 +ENDCHAR +STARTCHAR afii57683 +ENCODING 1507 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F0 +88 +88 +48 +08 +08 +08 +08 +08 +08 +ENDCHAR +STARTCHAR afii57684 +ENCODING 1508 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F0 +88 +88 +48 +08 +08 +08 +F0 +00 +00 +ENDCHAR +STARTCHAR afii57685 +ENCODING 1509 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +48 +50 +20 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR afii57686 +ENCODING 1510 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +48 +50 +20 +20 +10 +F0 +00 +00 +ENDCHAR +STARTCHAR afii57687 +ENCODING 1511 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +08 +88 +88 +90 +A0 +A0 +A0 +80 +80 +ENDCHAR +STARTCHAR afii57688 +ENCODING 1512 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F0 +08 +08 +08 +08 +08 +08 +08 +00 +00 +ENDCHAR +STARTCHAR afii57689 +ENCODING 1513 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +A8 +A8 +A8 +A8 +C8 +88 +88 +F0 +00 +00 +ENDCHAR +STARTCHAR afii57690 +ENCODING 1514 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F0 +48 +48 +48 +48 +48 +48 +88 +00 +00 +ENDCHAR +STARTCHAR uni1E0C +ENCODING 7692 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +E0 +90 +88 +88 +88 +88 +90 +E0 +20 +20 +ENDCHAR +STARTCHAR uni1E0D +ENCODING 7693 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +08 +08 +78 +88 +88 +88 +88 +78 +20 +20 +ENDCHAR +STARTCHAR Klinebelow +ENCODING 7732 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +90 +A0 +C0 +C0 +A0 +90 +88 +00 +70 +ENDCHAR +STARTCHAR klinebelow +ENCODING 7733 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +40 +40 +48 +50 +60 +60 +50 +48 +00 +70 +ENDCHAR +STARTCHAR uni1E36 +ENCODING 7734 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +80 +80 +80 +80 +80 +80 +80 +F8 +20 +20 +ENDCHAR +STARTCHAR uni1E37 +ENCODING 7735 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +60 +20 +20 +20 +20 +20 +20 +70 +20 +20 +ENDCHAR +STARTCHAR uni1E40 +ENCODING 7744 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +88 +D8 +A8 +A8 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR uni1E41 +ENCODING 7745 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +20 +F0 +A8 +A8 +A8 +A8 +A8 +00 +00 +ENDCHAR +STARTCHAR uni1E42 +ENCODING 7746 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +D8 +A8 +A8 +88 +88 +88 +88 +20 +20 +ENDCHAR +STARTCHAR uni1E43 +ENCODING 7747 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F0 +A8 +A8 +A8 +A8 +A8 +10 +10 +ENDCHAR +STARTCHAR uni1E44 +ENCODING 7748 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +88 +88 +C8 +A8 +98 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR uni1E45 +ENCODING 7749 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +20 +F0 +88 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR uni1E46 +ENCODING 7750 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +C8 +A8 +98 +88 +88 +88 +20 +20 +ENDCHAR +STARTCHAR uni1E47 +ENCODING 7751 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F0 +88 +88 +88 +88 +88 +20 +20 +ENDCHAR +STARTCHAR uni1E6C +ENCODING 7788 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +20 +20 +20 +20 +20 +20 +20 +10 +10 +ENDCHAR +STARTCHAR uni1E6D +ENCODING 7789 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +20 +70 +20 +20 +20 +20 +18 +10 +10 +ENDCHAR +STARTCHAR Edotbelow +ENCODING 7864 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +80 +80 +F0 +80 +80 +80 +F8 +20 +20 +ENDCHAR +STARTCHAR edotbelow +ENCODING 7865 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +F8 +80 +80 +78 +20 +20 +ENDCHAR +STARTCHAR Etilde +ENCODING 7868 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +28 +50 +F8 +80 +80 +F0 +80 +80 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR etilde +ENCODING 7869 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +28 +50 +70 +88 +F8 +80 +80 +78 +00 +00 +ENDCHAR +STARTCHAR uni1ECA +ENCODING 7882 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +20 +20 +20 +20 +20 +20 +70 +20 +20 +ENDCHAR +STARTCHAR uni1ECB +ENCODING 7883 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +20 +20 +00 +60 +20 +20 +20 +20 +70 +20 +20 +ENDCHAR +STARTCHAR Odotbelow +ENCODING 7884 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +88 +88 +88 +88 +88 +70 +20 +20 +ENDCHAR +STARTCHAR odotbelow +ENCODING 7885 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +88 +88 +88 +70 +20 +20 +ENDCHAR +STARTCHAR uni1EE4 +ENCODING 7908 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +88 +88 +88 +88 +88 +70 +20 +20 +ENDCHAR +STARTCHAR uni1EE5 +ENCODING 7909 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +88 +88 +88 +78 +20 +20 +ENDCHAR +STARTCHAR Ytilde +ENCODING 7928 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +28 +50 +88 +88 +50 +50 +20 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR ytilde +ENCODING 7929 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +28 +50 +88 +88 +88 +88 +88 +78 +08 +70 +ENDCHAR +STARTCHAR uni2000 +ENCODING 8192 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2001 +ENCODING 8193 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR enspace +ENCODING 8194 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2003 +ENCODING 8195 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2004 +ENCODING 8196 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2005 +ENCODING 8197 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2006 +ENCODING 8198 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2007 +ENCODING 8199 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2008 +ENCODING 8200 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2009 +ENCODING 8201 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni200A +ENCODING 8202 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni200B +ENCODING 8203 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR afii61664 +ENCODING 8204 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR afii301 +ENCODING 8205 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR afii299 +ENCODING 8206 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR afii300 +ENCODING 8207 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR hyphentwo +ENCODING 8208 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +78 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2011 +ENCODING 8209 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +78 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR figuredash +ENCODING 8210 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +F8 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR endash +ENCODING 8211 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +F8 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR emdash +ENCODING 8212 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +F8 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR afii00208 +ENCODING 8213 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +F8 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR dblverticalbar +ENCODING 8214 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +50 +50 +50 +50 +50 +50 +00 +00 +ENDCHAR +STARTCHAR underscoredbl +ENCODING 8215 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +F8 +00 +F8 +ENDCHAR +STARTCHAR quoteleft +ENCODING 8216 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +10 +20 +20 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR quoteright +ENCODING 8217 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +20 +20 +40 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR quotesinglbase +ENCODING 8218 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +20 +20 +40 +00 +ENDCHAR +STARTCHAR quotereversed +ENCODING 8219 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +20 +20 +10 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR quotedblleft +ENCODING 8220 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +28 +50 +50 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR quotedblright +ENCODING 8221 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +28 +28 +50 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR quotedblbase +ENCODING 8222 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +50 +50 +A0 +00 +ENDCHAR +STARTCHAR uni201F +ENCODING 8223 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +A0 +A0 +50 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR dagger +ENCODING 8224 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +70 +20 +20 +20 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR daggerdbl +ENCODING 8225 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +70 +20 +20 +20 +20 +70 +20 +00 +00 +ENDCHAR +STARTCHAR bullet +ENCODING 8226 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +30 +78 +78 +30 +00 +00 +00 +00 +ENDCHAR +STARTCHAR ellipsis +ENCODING 8230 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +A8 +A8 +00 +00 +ENDCHAR +STARTCHAR perthousand +ENCODING 8240 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +44 +A8 +50 +20 +40 +A8 +54 +28 +00 +00 +ENDCHAR +STARTCHAR minute +ENCODING 8242 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +20 +20 +20 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR second +ENCODING 8243 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +50 +50 +50 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR guilsinglleft +ENCODING 8249 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +08 +10 +20 +40 +20 +10 +08 +00 +00 +ENDCHAR +STARTCHAR guilsinglright +ENCODING 8250 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +40 +20 +10 +08 +10 +20 +40 +00 +00 +ENDCHAR +STARTCHAR exclamdbl +ENCODING 8252 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +50 +50 +50 +00 +50 +50 +00 +00 +ENDCHAR +STARTCHAR overline +ENCODING 8254 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +F8 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2070 +ENCODING 8304 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +30 +48 +48 +48 +30 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2071 +ENCODING 8305 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +00 +60 +20 +20 +70 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2074 +ENCODING 8308 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +08 +18 +28 +78 +08 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2075 +ENCODING 8309 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +70 +40 +70 +08 +70 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2076 +ENCODING 8310 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +30 +40 +70 +48 +30 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2077 +ENCODING 8311 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +78 +08 +10 +20 +20 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2078 +ENCODING 8312 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +30 +48 +30 +48 +30 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2079 +ENCODING 8313 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +30 +48 +38 +08 +30 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni207A +ENCODING 8314 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +20 +20 +F8 +20 +20 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni207B +ENCODING 8315 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +78 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni207C +ENCODING 8316 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +78 +00 +78 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni207D +ENCODING 8317 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +10 +20 +20 +20 +10 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni207E +ENCODING 8318 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +20 +10 +10 +10 +20 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR nsuperior +ENCODING 8319 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +70 +48 +48 +48 +48 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2080 +ENCODING 8320 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +30 +48 +48 +48 +30 +00 +00 +ENDCHAR +STARTCHAR uni2081 +ENCODING 8321 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +10 +30 +10 +10 +38 +00 +00 +ENDCHAR +STARTCHAR uni2082 +ENCODING 8322 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +30 +48 +10 +20 +78 +00 +00 +ENDCHAR +STARTCHAR uni2083 +ENCODING 8323 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +70 +08 +30 +08 +70 +00 +00 +ENDCHAR +STARTCHAR uni2084 +ENCODING 8324 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +08 +18 +28 +78 +08 +00 +00 +ENDCHAR +STARTCHAR uni2085 +ENCODING 8325 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +70 +40 +70 +08 +70 +00 +00 +ENDCHAR +STARTCHAR uni2086 +ENCODING 8326 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +30 +40 +70 +48 +30 +00 +00 +ENDCHAR +STARTCHAR uni2087 +ENCODING 8327 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +78 +08 +10 +20 +20 +00 +00 +ENDCHAR +STARTCHAR uni2088 +ENCODING 8328 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +30 +48 +30 +48 +30 +00 +00 +ENDCHAR +STARTCHAR uni2089 +ENCODING 8329 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +30 +48 +38 +08 +30 +00 +00 +ENDCHAR +STARTCHAR uni208A +ENCODING 8330 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +20 +20 +F8 +20 +20 +00 +00 +ENDCHAR +STARTCHAR uni208B +ENCODING 8331 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +78 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni208C +ENCODING 8332 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +78 +00 +78 +00 +00 +00 +ENDCHAR +STARTCHAR uni208D +ENCODING 8333 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +10 +20 +20 +20 +10 +00 +00 +ENDCHAR +STARTCHAR uni208E +ENCODING 8334 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +20 +10 +10 +10 +20 +00 +00 +ENDCHAR +STARTCHAR uni2090 +ENCODING 8336 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +30 +08 +38 +48 +38 +00 +00 +ENDCHAR +STARTCHAR uni2091 +ENCODING 8337 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +30 +48 +78 +40 +38 +00 +00 +ENDCHAR +STARTCHAR uni2092 +ENCODING 8338 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +30 +48 +48 +48 +30 +00 +00 +ENDCHAR +STARTCHAR uni2093 +ENCODING 8339 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +88 +50 +20 +50 +88 +00 +00 +ENDCHAR +STARTCHAR uni2094 +ENCODING 8340 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +70 +08 +78 +48 +30 +00 +00 +ENDCHAR +STARTCHAR uni2095 +ENCODING 8341 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +40 +70 +48 +48 +48 +48 +00 +00 +ENDCHAR +STARTCHAR uni2096 +ENCODING 8342 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +40 +48 +50 +60 +50 +48 +00 +00 +ENDCHAR +STARTCHAR uni2097 +ENCODING 8343 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +60 +20 +20 +20 +20 +70 +00 +00 +ENDCHAR +STARTCHAR uni2098 +ENCODING 8344 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +F0 +A8 +A8 +A8 +A8 +00 +00 +ENDCHAR +STARTCHAR uni209A +ENCODING 8346 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +70 +48 +48 +48 +70 +40 +40 +ENDCHAR +STARTCHAR peseta +ENCODING 8359 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +C0 +A0 +A0 +C8 +9C +88 +88 +84 +00 +00 +ENDCHAR +STARTCHAR afii57636 +ENCODING 8362 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +C8 +A8 +A8 +88 +88 +A8 +A8 +B0 +00 +00 +ENDCHAR +STARTCHAR Euro +ENCODING 8364 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +38 +44 +F0 +40 +F0 +44 +38 +00 +00 +ENDCHAR +STARTCHAR uni20AE +ENCODING 8366 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +20 +30 +60 +30 +60 +20 +20 +00 +00 +ENDCHAR +STARTCHAR uni2102 +ENCODING 8450 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +A8 +A0 +A0 +A0 +A0 +A8 +70 +00 +00 +ENDCHAR +STARTCHAR uni210E +ENCODING 8462 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +80 +80 +F0 +88 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR uni210F +ENCODING 8463 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +40 +F0 +40 +70 +48 +48 +48 +48 +00 +00 +ENDCHAR +STARTCHAR uni2115 +ENCODING 8469 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +C8 +A8 +D8 +A8 +98 +88 +88 +00 +00 +ENDCHAR +STARTCHAR afii61352 +ENCODING 8470 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +94 +94 +D0 +F0 +F0 +B4 +90 +94 +00 +00 +ENDCHAR +STARTCHAR uni211A +ENCODING 8474 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +C8 +A8 +A8 +A8 +A8 +A8 +70 +18 +00 +ENDCHAR +STARTCHAR uni211D +ENCODING 8477 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F0 +A8 +A8 +A8 +B0 +B0 +A8 +E4 +00 +00 +ENDCHAR +STARTCHAR trademark +ENCODING 8482 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F4 +5C +54 +54 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2124 +ENCODING 8484 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +18 +28 +50 +A0 +C0 +80 +F8 +00 +00 +ENDCHAR +STARTCHAR Ohm +ENCODING 8486 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +88 +88 +88 +88 +50 +D8 +00 +00 +ENDCHAR +STARTCHAR aleph +ENCODING 8501 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +48 +48 +24 +68 +90 +90 +88 +48 +00 +00 +ENDCHAR +STARTCHAR arrowleft +ENCODING 8592 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +20 +60 +FC +60 +20 +00 +00 +00 +00 +ENDCHAR +STARTCHAR arrowup +ENCODING 8593 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +70 +F8 +20 +20 +20 +20 +20 +00 +00 +ENDCHAR +STARTCHAR arrowright +ENCODING 8594 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +10 +18 +FC +18 +10 +00 +00 +00 +00 +ENDCHAR +STARTCHAR arrowdown +ENCODING 8595 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +20 +20 +20 +20 +F8 +70 +20 +00 +00 +ENDCHAR +STARTCHAR arrowboth +ENCODING 8596 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +48 +CC +FC +CC +48 +00 +00 +00 +00 +ENDCHAR +STARTCHAR arrowupdn +ENCODING 8597 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +70 +F8 +20 +20 +F8 +70 +20 +00 +00 +ENDCHAR +STARTCHAR uni21A4 +ENCODING 8612 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +24 +64 +FC +64 +24 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni21A6 +ENCODING 8614 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +90 +98 +FC +98 +90 +00 +00 +00 +00 +ENDCHAR +STARTCHAR arrowupdnbse +ENCODING 8616 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +70 +F8 +20 +F8 +70 +20 +F8 +00 +00 +ENDCHAR +STARTCHAR uni21B2 +ENCODING 8626 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +08 +08 +08 +28 +68 +F8 +60 +20 +00 +00 +ENDCHAR +STARTCHAR uni21B3 +ENCODING 8627 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +80 +80 +80 +A0 +B0 +F8 +30 +20 +00 +00 +ENDCHAR +STARTCHAR carriagereturn +ENCODING 8629 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +08 +08 +28 +68 +F8 +60 +20 +00 +00 +ENDCHAR +STARTCHAR uni21BB +ENCODING 8635 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F0 +30 +50 +94 +84 +84 +84 +78 +00 +00 +ENDCHAR +STARTCHAR uni21CB +ENCODING 8651 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +40 +FC +00 +FC +08 +10 +00 +00 +00 +ENDCHAR +STARTCHAR uni21CC +ENCODING 8652 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +08 +FC +00 +FC +40 +20 +00 +00 +00 +ENDCHAR +STARTCHAR arrowdblleft +ENCODING 8656 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +20 +7C +E0 +7C +20 +00 +00 +00 +00 +ENDCHAR +STARTCHAR arrowdblup +ENCODING 8657 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +70 +F8 +50 +50 +50 +50 +50 +00 +00 +ENDCHAR +STARTCHAR arrowdblright +ENCODING 8658 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +10 +F8 +1C +F8 +10 +00 +00 +00 +00 +ENDCHAR +STARTCHAR arrowdbldown +ENCODING 8659 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +50 +50 +50 +F8 +70 +20 +00 +00 +ENDCHAR +STARTCHAR arrowdblboth +ENCODING 8660 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +48 +FC +CC +FC +48 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni21D5 +ENCODING 8661 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +70 +F8 +50 +50 +F8 +70 +20 +00 +00 +ENDCHAR +STARTCHAR universal +ENCODING 8704 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +F8 +50 +50 +50 +20 +20 +00 +00 +ENDCHAR +STARTCHAR existential +ENCODING 8707 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +F8 +08 +08 +F8 +08 +08 +F8 +00 +00 +ENDCHAR +STARTCHAR uni2204 +ENCODING 8708 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +F8 +28 +28 +F8 +48 +48 +F8 +80 +00 +ENDCHAR +STARTCHAR emptyset +ENCODING 8709 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +08 +70 +98 +A8 +A8 +C8 +70 +80 +00 +00 +ENDCHAR +STARTCHAR increment +ENCODING 8710 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +20 +50 +50 +88 +88 +88 +F8 +00 +00 +ENDCHAR +STARTCHAR gradient +ENCODING 8711 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +88 +88 +88 +50 +50 +20 +20 +00 +00 +ENDCHAR +STARTCHAR element +ENCODING 8712 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +38 +40 +80 +F8 +80 +40 +38 +00 +00 +ENDCHAR +STARTCHAR notelement +ENCODING 8713 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +08 +38 +50 +90 +F8 +A0 +60 +78 +40 +00 +ENDCHAR +STARTCHAR uni220A +ENCODING 8714 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +78 +80 +F8 +80 +78 +00 +00 +00 +ENDCHAR +STARTCHAR suchthat +ENCODING 8715 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +E0 +10 +08 +F8 +08 +10 +E0 +00 +00 +ENDCHAR +STARTCHAR uni220C +ENCODING 8716 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +80 +E0 +50 +48 +F8 +28 +30 +F0 +10 +00 +ENDCHAR +STARTCHAR uni220D +ENCODING 8717 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F0 +08 +F8 +08 +F0 +00 +00 +00 +ENDCHAR +STARTCHAR minus +ENCODING 8722 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +F8 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2213 +ENCODING 8723 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +F8 +00 +20 +20 +F8 +20 +20 +00 +00 +ENDCHAR +STARTCHAR uni2214 +ENCODING 8724 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +20 +00 +20 +20 +F8 +20 +20 +00 +00 +ENDCHAR +STARTCHAR uni2215 +ENCODING 8725 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +08 +10 +20 +40 +80 +00 +00 +00 +ENDCHAR +STARTCHAR uni2216 +ENCODING 8726 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +80 +40 +20 +10 +08 +00 +00 +00 +ENDCHAR +STARTCHAR bulletoperator +ENCODING 8729 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +30 +78 +30 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR radical +ENCODING 8730 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +0C +08 +08 +08 +88 +88 +48 +28 +18 +00 +00 +ENDCHAR +STARTCHAR infinity +ENCODING 8734 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +50 +A8 +A8 +A8 +50 +00 +00 +00 +00 +ENDCHAR +STARTCHAR orthogonal +ENCODING 8735 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +80 +80 +80 +80 +F8 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2225 +ENCODING 8741 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +50 +50 +50 +50 +50 +50 +50 +50 +00 +00 +ENDCHAR +STARTCHAR logicaland +ENCODING 8743 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +20 +20 +50 +50 +88 +88 +00 +00 +ENDCHAR +STARTCHAR logicalor +ENCODING 8744 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +50 +50 +20 +20 +00 +00 +ENDCHAR +STARTCHAR intersection +ENCODING 8745 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +70 +88 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR union +ENCODING 8746 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR approxequal +ENCODING 8776 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +68 +B0 +00 +68 +B0 +00 +00 +00 +ENDCHAR +STARTCHAR notequal +ENCODING 8800 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +08 +F8 +20 +40 +F8 +80 +00 +00 +00 +ENDCHAR +STARTCHAR equivalence +ENCODING 8801 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F8 +00 +F8 +00 +F8 +00 +00 +00 +ENDCHAR +STARTCHAR lessequal +ENCODING 8804 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +10 +20 +40 +80 +40 +20 +10 +00 +F8 +00 +00 +ENDCHAR +STARTCHAR greaterequal +ENCODING 8805 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +40 +20 +10 +08 +10 +20 +40 +00 +F8 +00 +00 +ENDCHAR +STARTCHAR uni226A +ENCODING 8810 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +14 +28 +50 +A0 +50 +28 +14 +00 +00 +ENDCHAR +STARTCHAR uni226B +ENCODING 8811 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +A0 +50 +28 +14 +28 +50 +A0 +00 +00 +ENDCHAR +STARTCHAR propersubset +ENCODING 8834 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +78 +80 +80 +80 +80 +78 +00 +00 +00 +ENDCHAR +STARTCHAR propersuperset +ENCODING 8835 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +F0 +08 +08 +08 +08 +F0 +00 +00 +00 +ENDCHAR +STARTCHAR reflexsubset +ENCODING 8838 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +78 +80 +80 +80 +80 +78 +00 +F8 +00 +00 +ENDCHAR +STARTCHAR reflexsuperset +ENCODING 8839 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F0 +08 +08 +08 +08 +F0 +00 +F8 +00 +00 +ENDCHAR +STARTCHAR perpendicular +ENCODING 8869 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +20 +20 +20 +20 +20 +F8 +00 +00 +ENDCHAR +STARTCHAR uni22C2 +ENCODING 8898 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +88 +88 +88 +88 +88 +88 +00 +00 +ENDCHAR +STARTCHAR uni22C3 +ENCODING 8899 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +88 +88 +88 +88 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR uni2300 +ENCODING 8960 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +08 +70 +98 +A8 +A8 +C8 +70 +80 +00 +00 +ENDCHAR +STARTCHAR house +ENCODING 8962 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +20 +50 +88 +88 +88 +F8 +00 +00 +ENDCHAR +STARTCHAR uni2308 +ENCODING 8968 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +40 +40 +40 +40 +40 +40 +40 +00 +00 +ENDCHAR +STARTCHAR uni2309 +ENCODING 8969 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +10 +10 +10 +10 +10 +10 +10 +00 +00 +ENDCHAR +STARTCHAR uni230A +ENCODING 8970 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +40 +40 +40 +40 +40 +40 +40 +70 +00 +00 +ENDCHAR +STARTCHAR uni230B +ENCODING 8971 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +10 +10 +10 +10 +10 +10 +70 +00 +00 +ENDCHAR +STARTCHAR revlogicalnot +ENCODING 8976 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F8 +80 +80 +80 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2319 +ENCODING 8985 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +80 +80 +80 +F8 +00 +00 +00 +00 +ENDCHAR +STARTCHAR integraltp +ENCODING 8992 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +10 +28 +28 +20 +20 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR integralbt +ENCODING 8993 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +20 +20 +A0 +A0 +40 +00 +00 +ENDCHAR +STARTCHAR uni239B +ENCODING 9115 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +10 +10 +20 +20 +20 +40 +40 +40 +40 +40 +40 +ENDCHAR +STARTCHAR uni239C +ENCODING 9116 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +40 +40 +40 +40 +40 +40 +40 +40 +40 +40 +ENDCHAR +STARTCHAR uni239D +ENCODING 9117 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +40 +40 +40 +40 +20 +20 +20 +10 +10 +08 +ENDCHAR +STARTCHAR uni239E +ENCODING 9118 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +20 +20 +10 +10 +10 +08 +08 +08 +08 +08 +08 +ENDCHAR +STARTCHAR uni239F +ENCODING 9119 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +08 +08 +08 +08 +08 +08 +08 +08 +08 +08 +ENDCHAR +STARTCHAR uni23A0 +ENCODING 9120 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +08 +08 +08 +08 +10 +10 +10 +20 +20 +40 +ENDCHAR +STARTCHAR uni23A1 +ENCODING 9121 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +78 +40 +40 +40 +40 +40 +40 +40 +40 +40 +40 +40 +ENDCHAR +STARTCHAR uni23A2 +ENCODING 9122 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +40 +40 +40 +40 +40 +40 +40 +40 +40 +40 +ENDCHAR +STARTCHAR uni23A3 +ENCODING 9123 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +40 +40 +40 +40 +40 +40 +40 +40 +40 +78 +ENDCHAR +STARTCHAR uni23A4 +ENCODING 9124 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +78 +08 +08 +08 +08 +08 +08 +08 +08 +08 +08 +08 +ENDCHAR +STARTCHAR uni23A5 +ENCODING 9125 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +08 +08 +08 +08 +08 +08 +08 +08 +08 +08 +ENDCHAR +STARTCHAR uni23A6 +ENCODING 9126 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +08 +08 +08 +08 +08 +08 +08 +08 +08 +78 +ENDCHAR +STARTCHAR uni23A7 +ENCODING 9127 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +0C +10 +20 +20 +20 +20 +20 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni23A8 +ENCODING 9128 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +C0 +C0 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni23A9 +ENCODING 9129 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +20 +20 +20 +20 +20 +10 +0C +ENDCHAR +STARTCHAR uni23AB +ENCODING 9131 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +C0 +20 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +ENDCHAR +STARTCHAR uni23AC +ENCODING 9132 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +10 +10 +10 +10 +0C +0C +10 +10 +10 +10 +10 +ENDCHAR +STARTCHAR uni23AD +ENCODING 9133 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +20 +C0 +ENDCHAR +STARTCHAR uni23AE +ENCODING 9134 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +20 +20 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni23AF +ENCODING 9135 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +FC +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni23BA +ENCODING 9146 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +FC +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni23BB +ENCODING 9147 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +FC +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni23BC +ENCODING 9148 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +FC +00 +00 +00 +ENDCHAR +STARTCHAR uni23BD +ENCODING 9149 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +FC +ENDCHAR +STARTCHAR uni23D0 +ENCODING 9168 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +20 +20 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni2409 +ENCODING 9225 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +90 +90 +F0 +90 +90 +00 +7C +10 +10 +10 +10 +00 +ENDCHAR +STARTCHAR uni240A +ENCODING 9226 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +80 +80 +80 +80 +F0 +00 +3C +20 +38 +20 +20 +00 +ENDCHAR +STARTCHAR uni240B +ENCODING 9227 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +88 +88 +88 +50 +20 +00 +7C +10 +10 +10 +10 +00 +ENDCHAR +STARTCHAR uni240C +ENCODING 9228 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +F0 +80 +E0 +80 +80 +00 +3C +20 +38 +20 +20 +00 +ENDCHAR +STARTCHAR uni240D +ENCODING 9229 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +60 +90 +80 +90 +60 +00 +38 +24 +38 +28 +24 +00 +ENDCHAR +STARTCHAR uni2424 +ENCODING 9252 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +90 +D0 +B0 +90 +90 +00 +20 +20 +20 +20 +3C +00 +ENDCHAR +STARTCHAR SF100000 +ENCODING 9472 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +FC +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2501 +ENCODING 9473 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +FC +FC +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR SF110000 +ENCODING 9474 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +20 +20 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni2503 +ENCODING 9475 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR uni2508 +ENCODING 9480 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +A8 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2509 +ENCODING 9481 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +A8 +A8 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni250A +ENCODING 9482 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +00 +20 +20 +00 +20 +20 +00 +20 +20 +00 +ENDCHAR +STARTCHAR uni250B +ENCODING 9483 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +00 +30 +30 +00 +30 +30 +00 +30 +30 +00 +ENDCHAR +STARTCHAR SF010000 +ENCODING 9484 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +3C +20 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni250D +ENCODING 9485 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +3C +3C +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni250E +ENCODING 9486 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +3C +30 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR uni250F +ENCODING 9487 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +3C +3C +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR SF030000 +ENCODING 9488 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +E0 +20 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni2511 +ENCODING 9489 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +E0 +E0 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni2512 +ENCODING 9490 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +F0 +30 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR uni2513 +ENCODING 9491 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +F0 +F0 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR SF020000 +ENCODING 9492 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +3C +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2515 +ENCODING 9493 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +3C +3C +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2516 +ENCODING 9494 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +3C +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2517 +ENCODING 9495 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +3C +3C +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR SF040000 +ENCODING 9496 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +E0 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2519 +ENCODING 9497 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +E0 +E0 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni251A +ENCODING 9498 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +F0 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni251B +ENCODING 9499 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +F0 +F0 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR SF080000 +ENCODING 9500 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +3C +20 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni251D +ENCODING 9501 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +3C +3C +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni251E +ENCODING 9502 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +3C +20 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni251F +ENCODING 9503 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +3C +30 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR uni2520 +ENCODING 9504 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +3C +30 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR uni2521 +ENCODING 9505 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +3C +3C +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni2522 +ENCODING 9506 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +3C +3C +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR uni2523 +ENCODING 9507 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +3C +3C +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR SF090000 +ENCODING 9508 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +E0 +20 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni2525 +ENCODING 9509 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +E0 +E0 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni2526 +ENCODING 9510 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +F0 +20 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni2527 +ENCODING 9511 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +F0 +30 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR uni2528 +ENCODING 9512 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +F0 +30 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR uni2529 +ENCODING 9513 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +F0 +F0 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni252A +ENCODING 9514 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +F0 +F0 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR uni252B +ENCODING 9515 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +F0 +F0 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR SF060000 +ENCODING 9516 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +FC +20 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni252D +ENCODING 9517 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +FC +E0 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni252E +ENCODING 9518 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +FC +3C +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni252F +ENCODING 9519 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +FC +FC +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni2530 +ENCODING 9520 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +FC +30 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR uni2531 +ENCODING 9521 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +FC +F0 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR uni2532 +ENCODING 9522 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +FC +3C +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR uni2533 +ENCODING 9523 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +FC +FC +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR SF070000 +ENCODING 9524 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +FC +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2535 +ENCODING 9525 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +FC +E0 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2536 +ENCODING 9526 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +FC +3C +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2537 +ENCODING 9527 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +FC +FC +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2538 +ENCODING 9528 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +FC +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2539 +ENCODING 9529 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +FC +F0 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni253A +ENCODING 9530 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +FC +3C +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni253B +ENCODING 9531 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +FC +FC +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR SF050000 +ENCODING 9532 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +FC +20 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni253D +ENCODING 9533 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +FC +E0 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni253E +ENCODING 9534 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +FC +3C +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni253F +ENCODING 9535 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +FC +FC +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni2540 +ENCODING 9536 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +FC +20 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni2541 +ENCODING 9537 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +FC +30 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR uni2542 +ENCODING 9538 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +FC +30 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR uni2543 +ENCODING 9539 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +FC +E0 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni2544 +ENCODING 9540 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +FC +3C +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni2545 +ENCODING 9541 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +FC +F0 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR uni2546 +ENCODING 9542 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +FC +3C +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR uni2547 +ENCODING 9543 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +FC +FC +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni2548 +ENCODING 9544 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +FC +FC +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR uni2549 +ENCODING 9545 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +FC +F0 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR uni254A +ENCODING 9546 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +FC +3C +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR uni254B +ENCODING 9547 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +FC +FC +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR SF430000 +ENCODING 9552 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +FC +00 +FC +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR SF240000 +ENCODING 9553 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +50 +50 +50 +50 +50 +50 +50 +50 +50 +50 +ENDCHAR +STARTCHAR SF510000 +ENCODING 9554 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +3C +20 +3C +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR SF520000 +ENCODING 9555 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +7C +50 +50 +50 +50 +50 +50 +ENDCHAR +STARTCHAR SF390000 +ENCODING 9556 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +7C +40 +5C +50 +50 +50 +50 +50 +ENDCHAR +STARTCHAR SF220000 +ENCODING 9557 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +E0 +20 +E0 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR SF210000 +ENCODING 9558 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +F0 +50 +50 +50 +50 +50 +50 +ENDCHAR +STARTCHAR SF250000 +ENCODING 9559 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +F0 +10 +D0 +50 +50 +50 +50 +50 +ENDCHAR +STARTCHAR SF500000 +ENCODING 9560 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +3C +20 +3C +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR SF490000 +ENCODING 9561 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +50 +50 +50 +7C +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR SF380000 +ENCODING 9562 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +50 +50 +5C +40 +7C +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR SF280000 +ENCODING 9563 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +E0 +20 +E0 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR SF270000 +ENCODING 9564 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +50 +50 +50 +F0 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR SF260000 +ENCODING 9565 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +50 +50 +D0 +10 +F0 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR SF360000 +ENCODING 9566 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +3C +20 +3C +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR SF370000 +ENCODING 9567 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +50 +50 +50 +5C +50 +50 +50 +50 +50 +50 +ENDCHAR +STARTCHAR SF420000 +ENCODING 9568 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +50 +50 +5C +40 +5C +50 +50 +50 +50 +50 +ENDCHAR +STARTCHAR SF190000 +ENCODING 9569 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +E0 +20 +E0 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR SF200000 +ENCODING 9570 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +50 +50 +50 +D0 +50 +50 +50 +50 +50 +50 +ENDCHAR +STARTCHAR SF230000 +ENCODING 9571 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +50 +50 +D0 +10 +D0 +50 +50 +50 +50 +50 +ENDCHAR +STARTCHAR SF470000 +ENCODING 9572 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +FC +00 +FC +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR SF480000 +ENCODING 9573 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +FC +50 +50 +50 +50 +50 +50 +ENDCHAR +STARTCHAR SF410000 +ENCODING 9574 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +FC +00 +DC +50 +50 +50 +50 +50 +ENDCHAR +STARTCHAR SF450000 +ENCODING 9575 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +FC +00 +FC +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR SF460000 +ENCODING 9576 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +50 +50 +50 +FC +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR SF400000 +ENCODING 9577 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +50 +50 +DC +00 +FC +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR SF540000 +ENCODING 9578 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +FC +20 +FC +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR SF530000 +ENCODING 9579 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +50 +50 +50 +FC +50 +50 +50 +50 +50 +50 +ENDCHAR +STARTCHAR SF440000 +ENCODING 9580 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +50 +50 +50 +50 +DC +00 +DC +50 +50 +50 +50 +50 +ENDCHAR +STARTCHAR uni256D +ENCODING 9581 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +0C +10 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni256E +ENCODING 9582 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +80 +40 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni256F +ENCODING 9583 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +40 +80 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2570 +ENCODING 9584 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +10 +0C +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2571 +ENCODING 9585 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +04 +04 +08 +08 +10 +10 +20 +20 +40 +40 +80 +80 +ENDCHAR +STARTCHAR uni2572 +ENCODING 9586 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +80 +80 +40 +40 +20 +20 +10 +10 +08 +08 +04 +04 +ENDCHAR +STARTCHAR uni2573 +ENCODING 9587 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +84 +84 +48 +48 +30 +30 +30 +30 +48 +48 +84 +84 +ENDCHAR +STARTCHAR uni2574 +ENCODING 9588 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +E0 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2575 +ENCODING 9589 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +20 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2576 +ENCODING 9590 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +3C +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2577 +ENCODING 9591 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +20 +20 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni2578 +ENCODING 9592 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +E0 +E0 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2579 +ENCODING 9593 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +30 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni257A +ENCODING 9594 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +3C +3C +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni257B +ENCODING 9595 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +30 +30 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR uni257C +ENCODING 9596 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +FC +3C +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni257D +ENCODING 9597 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +20 +20 +20 +20 +20 +30 +30 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR uni257E +ENCODING 9598 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +FC +E0 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni257F +ENCODING 9599 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +30 +30 +30 +30 +30 +30 +20 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR upblock +ENCODING 9600 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +FC +FC +FC +FC +FC +FC +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2581 +ENCODING 9601 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +FC +FC +ENDCHAR +STARTCHAR uni2582 +ENCODING 9602 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +FC +FC +FC +ENDCHAR +STARTCHAR uni2583 +ENCODING 9603 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +FC +FC +FC +FC +FC +ENDCHAR +STARTCHAR dnblock +ENCODING 9604 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +FC +FC +FC +FC +FC +FC +ENDCHAR +STARTCHAR uni2585 +ENCODING 9605 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +FC +FC +FC +FC +FC +FC +FC +FC +ENDCHAR +STARTCHAR uni2586 +ENCODING 9606 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +FC +FC +FC +FC +FC +FC +FC +FC +FC +ENDCHAR +STARTCHAR uni2587 +ENCODING 9607 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +FC +FC +FC +FC +FC +FC +FC +FC +FC +FC +FC +ENDCHAR +STARTCHAR block +ENCODING 9608 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +FC +FC +FC +FC +FC +FC +FC +FC +FC +FC +FC +FC +ENDCHAR +STARTCHAR uni2589 +ENCODING 9609 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +F8 +F8 +F8 +F8 +F8 +F8 +F8 +F8 +F8 +F8 +F8 +F8 +ENDCHAR +STARTCHAR uni258A +ENCODING 9610 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +F8 +F8 +F8 +F8 +F8 +F8 +F8 +F8 +F8 +F8 +F8 +F8 +ENDCHAR +STARTCHAR uni258B +ENCODING 9611 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +F0 +F0 +F0 +F0 +F0 +F0 +F0 +F0 +F0 +F0 +F0 +F0 +ENDCHAR +STARTCHAR lfblock +ENCODING 9612 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +E0 +E0 +E0 +E0 +E0 +E0 +E0 +E0 +E0 +E0 +E0 +E0 +ENDCHAR +STARTCHAR uni258D +ENCODING 9613 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +ENDCHAR +STARTCHAR uni258E +ENCODING 9614 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +ENDCHAR +STARTCHAR uni258F +ENCODING 9615 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +80 +80 +80 +80 +80 +80 +80 +80 +80 +80 +80 +80 +ENDCHAR +STARTCHAR rtblock +ENCODING 9616 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +1C +1C +1C +1C +1C +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR ltshade +ENCODING 9617 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +90 +24 +90 +24 +90 +24 +90 +24 +90 +24 +90 +24 +ENDCHAR +STARTCHAR shade +ENCODING 9618 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +A8 +54 +A8 +54 +A8 +54 +A8 +54 +A8 +54 +A8 +54 +ENDCHAR +STARTCHAR dkshade +ENCODING 9619 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +D8 +B4 +D8 +B4 +D8 +B4 +D8 +B4 +D8 +B4 +D8 +B4 +ENDCHAR +STARTCHAR uni2596 +ENCODING 9622 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +E0 +E0 +E0 +E0 +E0 +E0 +ENDCHAR +STARTCHAR uni2597 +ENCODING 9623 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR uni2598 +ENCODING 9624 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +E0 +E0 +E0 +E0 +E0 +E0 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2599 +ENCODING 9625 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +E0 +E0 +E0 +E0 +E0 +E0 +FC +FC +FC +FC +FC +FC +ENDCHAR +STARTCHAR uni259A +ENCODING 9626 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +E0 +E0 +E0 +E0 +E0 +E0 +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR uni259B +ENCODING 9627 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +FC +FC +FC +FC +FC +FC +E0 +E0 +E0 +E0 +E0 +E0 +ENDCHAR +STARTCHAR uni259C +ENCODING 9628 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +FC +FC +FC +FC +FC +FC +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR uni259D +ENCODING 9629 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +1C +1C +1C +1C +1C +1C +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni259E +ENCODING 9630 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +1C +1C +1C +1C +1C +1C +E0 +E0 +E0 +E0 +E0 +E0 +ENDCHAR +STARTCHAR uni259F +ENCODING 9631 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +1C +1C +1C +1C +1C +1C +FC +FC +FC +FC +FC +FC +ENDCHAR +STARTCHAR filledbox +ENCODING 9632 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +78 +78 +78 +78 +78 +00 +00 +00 +00 +ENDCHAR +STARTCHAR filledrect +ENCODING 9644 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +F8 +F8 +F8 +00 +00 +ENDCHAR +STARTCHAR uni25AE +ENCODING 9646 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +F8 +F8 +F8 +F8 +F8 +F8 +F8 +00 +00 +ENDCHAR +STARTCHAR triagup +ENCODING 9650 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +30 +30 +78 +78 +FC +FC +00 +00 +00 +ENDCHAR +STARTCHAR uni25B6 +ENCODING 9654 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +C0 +F0 +FC +FC +F0 +C0 +00 +00 +00 +ENDCHAR +STARTCHAR triagrt +ENCODING 9658 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +C0 +F0 +FC +FC +F0 +C0 +00 +00 +00 +ENDCHAR +STARTCHAR triagdn +ENCODING 9660 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +FC +FC +78 +78 +30 +30 +00 +00 +00 +ENDCHAR +STARTCHAR uni25C0 +ENCODING 9664 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +0C +3C +FC +FC +3C +0C +00 +00 +00 +ENDCHAR +STARTCHAR triaglf +ENCODING 9668 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +0C +3C +FC +FC +3C +0C +00 +00 +00 +ENDCHAR +STARTCHAR blackdiamond +ENCODING 9670 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +20 +70 +F8 +70 +20 +00 +00 +00 +00 +ENDCHAR +STARTCHAR lozenge +ENCODING 9674 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +20 +50 +88 +50 +20 +00 +00 +00 +00 +ENDCHAR +STARTCHAR circle +ENCODING 9675 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +30 +48 +48 +30 +00 +00 +00 +00 +ENDCHAR +STARTCHAR H18533 +ENCODING 9679 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +30 +78 +78 +30 +00 +00 +00 +00 +ENDCHAR +STARTCHAR invbullet +ENCODING 9688 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +FC +FC +FC +FC +CC +84 +84 +CC +FC +FC +FC +FC +ENDCHAR +STARTCHAR invcircle +ENCODING 9689 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +FC +FC +FC +FC +CC +B4 +B4 +CC +FC +FC +FC +FC +ENDCHAR +STARTCHAR smileface +ENCODING 9786 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +78 +84 +CC +84 +B4 +B4 +84 +78 +00 +00 +ENDCHAR +STARTCHAR invsmileface +ENCODING 9787 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +78 +FC +B4 +FC +84 +CC +FC +78 +00 +00 +ENDCHAR +STARTCHAR sun +ENCODING 9788 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +20 +A8 +70 +D8 +70 +A8 +20 +00 +00 +ENDCHAR +STARTCHAR female +ENCODING 9792 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +70 +88 +88 +88 +70 +20 +F8 +20 +00 +00 +ENDCHAR +STARTCHAR male +ENCODING 9794 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +3C +0C +14 +70 +88 +88 +88 +70 +00 +00 +ENDCHAR +STARTCHAR spade +ENCODING 9824 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +20 +70 +F8 +F8 +70 +20 +70 +00 +00 +ENDCHAR +STARTCHAR club +ENCODING 9827 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +20 +70 +20 +A8 +F8 +A8 +20 +70 +00 +00 +ENDCHAR +STARTCHAR heart +ENCODING 9829 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +50 +F8 +F8 +F8 +70 +70 +20 +00 +00 +ENDCHAR +STARTCHAR diamond +ENCODING 9830 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +20 +70 +F8 +70 +20 +00 +00 +00 +00 +ENDCHAR +STARTCHAR musicalnote +ENCODING 9834 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +78 +48 +78 +40 +40 +40 +40 +80 +00 +00 +ENDCHAR +STARTCHAR musicalnotedbl +ENCODING 9835 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +78 +48 +78 +48 +48 +48 +48 +50 +80 +00 +ENDCHAR +STARTCHAR uni2713 +ENCODING 10003 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +04 +04 +08 +88 +90 +50 +20 +20 +00 +00 +ENDCHAR +STARTCHAR uni2714 +ENCODING 10004 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +0C +0C +18 +D8 +F0 +70 +60 +60 +00 +00 +ENDCHAR +STARTCHAR uni2717 +ENCODING 10007 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +48 +28 +10 +10 +28 +24 +40 +40 +00 +00 +ENDCHAR +STARTCHAR uni2718 +ENCODING 10008 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +18 +D8 +70 +30 +78 +6C +C0 +C0 +00 +00 +ENDCHAR +STARTCHAR uni27E8 +ENCODING 10216 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +10 +10 +20 +20 +40 +20 +20 +10 +10 +00 +00 +ENDCHAR +STARTCHAR uni27E9 +ENCODING 10217 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +40 +40 +20 +20 +10 +20 +20 +40 +40 +00 +00 +ENDCHAR +STARTCHAR uni27EA +ENCODING 10218 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +28 +28 +50 +50 +A0 +50 +50 +28 +28 +00 +00 +ENDCHAR +STARTCHAR uni27EB +ENCODING 10219 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +A0 +A0 +50 +50 +28 +50 +50 +A0 +A0 +00 +00 +ENDCHAR +STARTCHAR uni2800 +ENCODING 10240 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2801 +ENCODING 10241 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2802 +ENCODING 10242 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +40 +40 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2803 +ENCODING 10243 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +40 +40 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2804 +ENCODING 10244 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +40 +40 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2805 +ENCODING 10245 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +00 +00 +00 +40 +40 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2806 +ENCODING 10246 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +40 +40 +00 +40 +40 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2807 +ENCODING 10247 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +40 +40 +00 +40 +40 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2808 +ENCODING 10248 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2809 +ENCODING 10249 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni280A +ENCODING 10250 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +40 +40 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni280B +ENCODING 10251 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +40 +40 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni280C +ENCODING 10252 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +00 +00 +00 +40 +40 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni280D +ENCODING 10253 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +00 +00 +00 +40 +40 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni280E +ENCODING 10254 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +40 +40 +00 +40 +40 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni280F +ENCODING 10255 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +40 +40 +00 +40 +40 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2810 +ENCODING 10256 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +08 +08 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2811 +ENCODING 10257 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +08 +08 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2812 +ENCODING 10258 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +48 +48 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2813 +ENCODING 10259 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +48 +48 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2814 +ENCODING 10260 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +08 +08 +00 +40 +40 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2815 +ENCODING 10261 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +08 +08 +00 +40 +40 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2816 +ENCODING 10262 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +48 +48 +00 +40 +40 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2817 +ENCODING 10263 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +48 +48 +00 +40 +40 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2818 +ENCODING 10264 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +08 +08 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2819 +ENCODING 10265 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +08 +08 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni281A +ENCODING 10266 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +48 +48 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni281B +ENCODING 10267 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +48 +48 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni281C +ENCODING 10268 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +08 +08 +00 +40 +40 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni281D +ENCODING 10269 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +08 +08 +00 +40 +40 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni281E +ENCODING 10270 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +48 +48 +00 +40 +40 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni281F +ENCODING 10271 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +48 +48 +00 +40 +40 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2820 +ENCODING 10272 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +08 +08 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2821 +ENCODING 10273 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +00 +00 +00 +08 +08 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2822 +ENCODING 10274 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +40 +40 +00 +08 +08 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2823 +ENCODING 10275 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +40 +40 +00 +08 +08 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2824 +ENCODING 10276 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +48 +48 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2825 +ENCODING 10277 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +00 +00 +00 +48 +48 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2826 +ENCODING 10278 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +40 +40 +00 +48 +48 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2827 +ENCODING 10279 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +40 +40 +00 +48 +48 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2828 +ENCODING 10280 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +00 +00 +00 +08 +08 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2829 +ENCODING 10281 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +00 +00 +00 +08 +08 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni282A +ENCODING 10282 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +40 +40 +00 +08 +08 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni282B +ENCODING 10283 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +40 +40 +00 +08 +08 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni282C +ENCODING 10284 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +00 +00 +00 +48 +48 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni282D +ENCODING 10285 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +00 +00 +00 +48 +48 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni282E +ENCODING 10286 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +40 +40 +00 +48 +48 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni282F +ENCODING 10287 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +40 +40 +00 +48 +48 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2830 +ENCODING 10288 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +08 +08 +00 +08 +08 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2831 +ENCODING 10289 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +08 +08 +00 +08 +08 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2832 +ENCODING 10290 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +48 +48 +00 +08 +08 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2833 +ENCODING 10291 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +48 +48 +00 +08 +08 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2834 +ENCODING 10292 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +08 +08 +00 +48 +48 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2835 +ENCODING 10293 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +08 +08 +00 +48 +48 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2836 +ENCODING 10294 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +48 +48 +00 +48 +48 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2837 +ENCODING 10295 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +48 +48 +00 +48 +48 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2838 +ENCODING 10296 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +08 +08 +00 +08 +08 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2839 +ENCODING 10297 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +08 +08 +00 +08 +08 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni283A +ENCODING 10298 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +48 +48 +00 +08 +08 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni283B +ENCODING 10299 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +48 +48 +00 +08 +08 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni283C +ENCODING 10300 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +08 +08 +00 +48 +48 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni283D +ENCODING 10301 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +08 +08 +00 +48 +48 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni283E +ENCODING 10302 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +48 +48 +00 +48 +48 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni283F +ENCODING 10303 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +48 +48 +00 +48 +48 +00 +00 +00 +00 +ENDCHAR +STARTCHAR uni2840 +ENCODING 10304 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2841 +ENCODING 10305 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +00 +00 +00 +00 +00 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2842 +ENCODING 10306 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +40 +40 +00 +00 +00 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2843 +ENCODING 10307 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +40 +40 +00 +00 +00 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2844 +ENCODING 10308 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +40 +40 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2845 +ENCODING 10309 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +00 +00 +00 +40 +40 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2846 +ENCODING 10310 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +40 +40 +00 +40 +40 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2847 +ENCODING 10311 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +40 +40 +00 +40 +40 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2848 +ENCODING 10312 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +00 +00 +00 +00 +00 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2849 +ENCODING 10313 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +00 +00 +00 +00 +00 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni284A +ENCODING 10314 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +40 +40 +00 +00 +00 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni284B +ENCODING 10315 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +40 +40 +00 +00 +00 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni284C +ENCODING 10316 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +00 +00 +00 +40 +40 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni284D +ENCODING 10317 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +00 +00 +00 +40 +40 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni284E +ENCODING 10318 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +40 +40 +00 +40 +40 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni284F +ENCODING 10319 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +40 +40 +00 +40 +40 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2850 +ENCODING 10320 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +08 +08 +00 +00 +00 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2851 +ENCODING 10321 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +08 +08 +00 +00 +00 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2852 +ENCODING 10322 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +48 +48 +00 +00 +00 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2853 +ENCODING 10323 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +48 +48 +00 +00 +00 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2854 +ENCODING 10324 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +08 +08 +00 +40 +40 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2855 +ENCODING 10325 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +08 +08 +00 +40 +40 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2856 +ENCODING 10326 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +48 +48 +00 +40 +40 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2857 +ENCODING 10327 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +48 +48 +00 +40 +40 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2858 +ENCODING 10328 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +08 +08 +00 +00 +00 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2859 +ENCODING 10329 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +08 +08 +00 +00 +00 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni285A +ENCODING 10330 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +48 +48 +00 +00 +00 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni285B +ENCODING 10331 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +48 +48 +00 +00 +00 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni285C +ENCODING 10332 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +08 +08 +00 +40 +40 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni285D +ENCODING 10333 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +08 +08 +00 +40 +40 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni285E +ENCODING 10334 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +48 +48 +00 +40 +40 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni285F +ENCODING 10335 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +48 +48 +00 +40 +40 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2860 +ENCODING 10336 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +08 +08 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2861 +ENCODING 10337 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +00 +00 +00 +08 +08 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2862 +ENCODING 10338 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +40 +40 +00 +08 +08 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2863 +ENCODING 10339 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +40 +40 +00 +08 +08 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2864 +ENCODING 10340 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +48 +48 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2865 +ENCODING 10341 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +00 +00 +00 +48 +48 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2866 +ENCODING 10342 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +40 +40 +00 +48 +48 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2867 +ENCODING 10343 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +40 +40 +00 +48 +48 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2868 +ENCODING 10344 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +00 +00 +00 +08 +08 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2869 +ENCODING 10345 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +00 +00 +00 +08 +08 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni286A +ENCODING 10346 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +40 +40 +00 +08 +08 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni286B +ENCODING 10347 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +40 +40 +00 +08 +08 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni286C +ENCODING 10348 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +00 +00 +00 +48 +48 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni286D +ENCODING 10349 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +00 +00 +00 +48 +48 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni286E +ENCODING 10350 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +40 +40 +00 +48 +48 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni286F +ENCODING 10351 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +40 +40 +00 +48 +48 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2870 +ENCODING 10352 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +08 +08 +00 +08 +08 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2871 +ENCODING 10353 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +08 +08 +00 +08 +08 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2872 +ENCODING 10354 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +48 +48 +00 +08 +08 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2873 +ENCODING 10355 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +48 +48 +00 +08 +08 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2874 +ENCODING 10356 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +08 +08 +00 +48 +48 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2875 +ENCODING 10357 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +08 +08 +00 +48 +48 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2876 +ENCODING 10358 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +48 +48 +00 +48 +48 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2877 +ENCODING 10359 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +48 +48 +00 +48 +48 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2878 +ENCODING 10360 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +08 +08 +00 +08 +08 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2879 +ENCODING 10361 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +08 +08 +00 +08 +08 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni287A +ENCODING 10362 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +48 +48 +00 +08 +08 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni287B +ENCODING 10363 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +48 +48 +00 +08 +08 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni287C +ENCODING 10364 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +08 +08 +00 +48 +48 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni287D +ENCODING 10365 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +08 +08 +00 +48 +48 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni287E +ENCODING 10366 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +48 +48 +00 +48 +48 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni287F +ENCODING 10367 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +48 +48 +00 +48 +48 +00 +40 +40 +00 +ENDCHAR +STARTCHAR uni2880 +ENCODING 10368 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni2881 +ENCODING 10369 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +00 +00 +00 +00 +00 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni2882 +ENCODING 10370 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +40 +40 +00 +00 +00 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni2883 +ENCODING 10371 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +40 +40 +00 +00 +00 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni2884 +ENCODING 10372 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +40 +40 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni2885 +ENCODING 10373 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +00 +00 +00 +40 +40 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni2886 +ENCODING 10374 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +40 +40 +00 +40 +40 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni2887 +ENCODING 10375 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +40 +40 +00 +40 +40 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni2888 +ENCODING 10376 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +00 +00 +00 +00 +00 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni2889 +ENCODING 10377 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +00 +00 +00 +00 +00 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni288A +ENCODING 10378 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +40 +40 +00 +00 +00 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni288B +ENCODING 10379 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +40 +40 +00 +00 +00 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni288C +ENCODING 10380 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +00 +00 +00 +40 +40 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni288D +ENCODING 10381 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +00 +00 +00 +40 +40 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni288E +ENCODING 10382 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +40 +40 +00 +40 +40 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni288F +ENCODING 10383 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +40 +40 +00 +40 +40 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni2890 +ENCODING 10384 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +08 +08 +00 +00 +00 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni2891 +ENCODING 10385 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +08 +08 +00 +00 +00 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni2892 +ENCODING 10386 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +48 +48 +00 +00 +00 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni2893 +ENCODING 10387 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +48 +48 +00 +00 +00 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni2894 +ENCODING 10388 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +08 +08 +00 +40 +40 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni2895 +ENCODING 10389 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +08 +08 +00 +40 +40 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni2896 +ENCODING 10390 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +48 +48 +00 +40 +40 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni2897 +ENCODING 10391 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +48 +48 +00 +40 +40 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni2898 +ENCODING 10392 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +08 +08 +00 +00 +00 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni2899 +ENCODING 10393 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +08 +08 +00 +00 +00 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni289A +ENCODING 10394 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +48 +48 +00 +00 +00 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni289B +ENCODING 10395 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +48 +48 +00 +00 +00 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni289C +ENCODING 10396 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +08 +08 +00 +40 +40 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni289D +ENCODING 10397 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +08 +08 +00 +40 +40 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni289E +ENCODING 10398 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +48 +48 +00 +40 +40 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni289F +ENCODING 10399 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +48 +48 +00 +40 +40 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28A0 +ENCODING 10400 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +08 +08 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28A1 +ENCODING 10401 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +00 +00 +00 +08 +08 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28A2 +ENCODING 10402 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +40 +40 +00 +08 +08 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28A3 +ENCODING 10403 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +40 +40 +00 +08 +08 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28A4 +ENCODING 10404 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +48 +48 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28A5 +ENCODING 10405 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +00 +00 +00 +48 +48 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28A6 +ENCODING 10406 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +40 +40 +00 +48 +48 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28A7 +ENCODING 10407 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +40 +40 +00 +48 +48 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28A8 +ENCODING 10408 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +00 +00 +00 +08 +08 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28A9 +ENCODING 10409 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +00 +00 +00 +08 +08 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28AA +ENCODING 10410 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +40 +40 +00 +08 +08 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28AB +ENCODING 10411 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +40 +40 +00 +08 +08 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28AC +ENCODING 10412 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +00 +00 +00 +48 +48 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28AD +ENCODING 10413 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +00 +00 +00 +48 +48 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28AE +ENCODING 10414 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +40 +40 +00 +48 +48 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28AF +ENCODING 10415 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +40 +40 +00 +48 +48 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28B0 +ENCODING 10416 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +08 +08 +00 +08 +08 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28B1 +ENCODING 10417 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +08 +08 +00 +08 +08 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28B2 +ENCODING 10418 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +48 +48 +00 +08 +08 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28B3 +ENCODING 10419 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +48 +48 +00 +08 +08 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28B4 +ENCODING 10420 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +08 +08 +00 +48 +48 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28B5 +ENCODING 10421 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +08 +08 +00 +48 +48 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28B6 +ENCODING 10422 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +48 +48 +00 +48 +48 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28B7 +ENCODING 10423 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +48 +48 +00 +48 +48 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28B8 +ENCODING 10424 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +08 +08 +00 +08 +08 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28B9 +ENCODING 10425 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +08 +08 +00 +08 +08 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28BA +ENCODING 10426 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +48 +48 +00 +08 +08 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28BB +ENCODING 10427 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +48 +48 +00 +08 +08 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28BC +ENCODING 10428 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +08 +08 +00 +48 +48 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28BD +ENCODING 10429 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +08 +08 +00 +48 +48 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28BE +ENCODING 10430 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +48 +48 +00 +48 +48 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28BF +ENCODING 10431 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +48 +48 +00 +48 +48 +00 +08 +08 +00 +ENDCHAR +STARTCHAR uni28C0 +ENCODING 10432 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28C1 +ENCODING 10433 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +00 +00 +00 +00 +00 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28C2 +ENCODING 10434 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +40 +40 +00 +00 +00 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28C3 +ENCODING 10435 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +40 +40 +00 +00 +00 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28C4 +ENCODING 10436 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +40 +40 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28C5 +ENCODING 10437 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +00 +00 +00 +40 +40 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28C6 +ENCODING 10438 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +40 +40 +00 +40 +40 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28C7 +ENCODING 10439 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +40 +40 +00 +40 +40 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28C8 +ENCODING 10440 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +00 +00 +00 +00 +00 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28C9 +ENCODING 10441 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +00 +00 +00 +00 +00 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28CA +ENCODING 10442 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +40 +40 +00 +00 +00 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28CB +ENCODING 10443 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +40 +40 +00 +00 +00 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28CC +ENCODING 10444 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +00 +00 +00 +40 +40 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28CD +ENCODING 10445 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +00 +00 +00 +40 +40 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28CE +ENCODING 10446 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +40 +40 +00 +40 +40 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28CF +ENCODING 10447 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +40 +40 +00 +40 +40 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28D0 +ENCODING 10448 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +08 +08 +00 +00 +00 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28D1 +ENCODING 10449 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +08 +08 +00 +00 +00 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28D2 +ENCODING 10450 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +48 +48 +00 +00 +00 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28D3 +ENCODING 10451 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +48 +48 +00 +00 +00 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28D4 +ENCODING 10452 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +08 +08 +00 +40 +40 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28D5 +ENCODING 10453 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +08 +08 +00 +40 +40 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28D6 +ENCODING 10454 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +48 +48 +00 +40 +40 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28D7 +ENCODING 10455 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +48 +48 +00 +40 +40 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28D8 +ENCODING 10456 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +08 +08 +00 +00 +00 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28D9 +ENCODING 10457 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +08 +08 +00 +00 +00 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28DA +ENCODING 10458 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +48 +48 +00 +00 +00 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28DB +ENCODING 10459 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +48 +48 +00 +00 +00 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28DC +ENCODING 10460 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +08 +08 +00 +40 +40 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28DD +ENCODING 10461 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +08 +08 +00 +40 +40 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28DE +ENCODING 10462 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +48 +48 +00 +40 +40 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28DF +ENCODING 10463 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +48 +48 +00 +40 +40 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28E0 +ENCODING 10464 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +08 +08 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28E1 +ENCODING 10465 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +00 +00 +00 +08 +08 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28E2 +ENCODING 10466 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +40 +40 +00 +08 +08 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28E3 +ENCODING 10467 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +40 +40 +00 +08 +08 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28E4 +ENCODING 10468 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +00 +00 +48 +48 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28E5 +ENCODING 10469 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +00 +00 +00 +48 +48 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28E6 +ENCODING 10470 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +40 +40 +00 +48 +48 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28E7 +ENCODING 10471 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +40 +40 +00 +48 +48 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28E8 +ENCODING 10472 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +00 +00 +00 +08 +08 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28E9 +ENCODING 10473 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +00 +00 +00 +08 +08 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28EA +ENCODING 10474 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +40 +40 +00 +08 +08 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28EB +ENCODING 10475 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +40 +40 +00 +08 +08 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28EC +ENCODING 10476 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +00 +00 +00 +48 +48 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28ED +ENCODING 10477 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +00 +00 +00 +48 +48 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28EE +ENCODING 10478 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +40 +40 +00 +48 +48 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28EF +ENCODING 10479 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +40 +40 +00 +48 +48 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28F0 +ENCODING 10480 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +08 +08 +00 +08 +08 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28F1 +ENCODING 10481 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +08 +08 +00 +08 +08 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28F2 +ENCODING 10482 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +48 +48 +00 +08 +08 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28F3 +ENCODING 10483 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +48 +48 +00 +08 +08 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28F4 +ENCODING 10484 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +08 +08 +00 +48 +48 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28F5 +ENCODING 10485 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +08 +08 +00 +48 +48 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28F6 +ENCODING 10486 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +48 +48 +00 +48 +48 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28F7 +ENCODING 10487 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +40 +40 +00 +48 +48 +00 +48 +48 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28F8 +ENCODING 10488 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +08 +08 +00 +08 +08 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28F9 +ENCODING 10489 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +08 +08 +00 +08 +08 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28FA +ENCODING 10490 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +48 +48 +00 +08 +08 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28FB +ENCODING 10491 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +48 +48 +00 +08 +08 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28FC +ENCODING 10492 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +08 +08 +00 +48 +48 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28FD +ENCODING 10493 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +08 +08 +00 +48 +48 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28FE +ENCODING 10494 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +08 +08 +00 +48 +48 +00 +48 +48 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni28FF +ENCODING 10495 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +48 +48 +00 +48 +48 +00 +48 +48 +00 +48 +48 +00 +ENDCHAR +STARTCHAR uni2E2C +ENCODING 11820 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +88 +88 +00 +00 +88 +88 +00 +00 +ENDCHAR +STARTCHAR uniE0A0 +ENCODING 57504 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +80 +90 +B8 +90 +90 +90 +20 +40 +80 +80 +80 +80 +ENDCHAR +STARTCHAR uniE0A1 +ENCODING 57505 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +80 +80 +80 +80 +F0 +00 +24 +34 +2C +24 +24 +00 +ENDCHAR +STARTCHAR uniE0A2 +ENCODING 57506 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +30 +48 +48 +48 +FC +FC +CC +CC +FC +FC +00 +ENDCHAR +STARTCHAR uniE0B0 +ENCODING 57520 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +80 +C0 +E0 +F0 +F8 +FC +FC +F8 +F0 +E0 +C0 +80 +ENDCHAR +STARTCHAR uniE0B1 +ENCODING 57521 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +80 +40 +20 +10 +08 +04 +04 +08 +10 +20 +40 +80 +ENDCHAR +STARTCHAR uniE0B2 +ENCODING 57522 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +04 +0C +1C +3C +7C +FC +FC +7C +3C +1C +0C +04 +ENDCHAR +STARTCHAR uniE0B3 +ENCODING 57523 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +04 +08 +10 +20 +40 +80 +80 +40 +20 +10 +08 +04 +ENDCHAR +STARTCHAR uniF6BE +ENCODING 63166 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +00 +00 +18 +08 +08 +08 +08 +08 +48 +30 +ENDCHAR +STARTCHAR uniFFFD +ENCODING 65533 +SWIDTH 500 0 +DWIDTH 6 0 +BBX 6 12 0 -2 +BITMAP +00 +00 +F8 +88 +88 +88 +88 +88 +88 +F8 +00 +00 +ENDCHAR +ENDFONT diff --git a/src/resources/system/images/.gitignore b/src/resources/system/images/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/src/resources/system/mochimochi-kun.png b/src/resources/system/mochimochi-kun.png new file mode 100644 index 0000000..a3dc7ab Binary files /dev/null and b/src/resources/system/mochimochi-kun.png differ diff --git a/src/services/build.rs b/src/services/build.rs new file mode 100644 index 0000000..9711c71 --- /dev/null +++ b/src/services/build.rs @@ -0,0 +1,76 @@ +use std::fs; +use std::path::Path; + +/// index.tomlを解析してサービス情報を取得 +pub fn parse_service_index(index_path: &Path) -> Result { + let content = fs::read_to_string(index_path) + .map_err(|e| format!("Failed to read index.toml: {}", e))?; + + let value: toml::Value = toml::from_str(&content) + .map_err(|e| format!("Failed to parse index.toml: {}", e))?; + + let mut index = ServiceIndex { + services: Vec::new(), + }; + + // サービスを解析 + if let Some(core) = value.get("core") { + if let Some(service_table) = core.get("service") { + if let Some(table) = service_table.as_table() { + for (name, value) in table { + if let Some(service_data) = value.as_table() { + let dir = service_data.get("dir") + .and_then(|v| v.as_str()) + .ok_or(format!("Missing 'dir' for service {}", name))?; + + let fs_type = service_data.get("fs") + .and_then(|v| v.as_str()) + .ok_or(format!("Missing 'fs' for service {}", name))?; + + let description = service_data.get("description") + .and_then(|v| v.as_str()) + .unwrap_or(""); + + index.services.push(ServiceEntry { + name: name.to_string(), + dir: dir.to_string(), + fs_type: fs_type.to_string(), + description: description.to_string(), + }); + } + } + } + } + } + + Ok(index) +} + +#[derive(Debug)] +pub struct ServiceIndex { + pub services: Vec, +} + +#[derive(Debug, Clone)] +pub struct ServiceEntry { + pub name: String, + pub dir: String, + pub fs_type: String, + pub description: String, +} + +impl ServiceIndex { + /// initfsに含めるサービスのリストを取得 + pub fn get_initfs_services(&self) -> Vec<&ServiceEntry> { + self.services.iter() + .filter(|s| s.fs_type == "initfs") + .collect() + } + + /// ext2に含めるサービスのリストを取得 + pub fn get_ext2_services(&self) -> Vec<&ServiceEntry> { + self.services.iter() + .filter(|s| s.fs_type == "ext2") + .collect() + } +} diff --git a/src/services/core/.cargo/config.toml b/src/services/core/.cargo/config.toml new file mode 100644 index 0000000..c9eb2f1 --- /dev/null +++ b/src/services/core/.cargo/config.toml @@ -0,0 +1,12 @@ +[build] +target = "../../x86_64-mochios.json" + +[unstable] +build-std = ["std", "panic_abort"] +json-target-spec = true + +[target.x86_64-mochios] +rustflags = [ + "-C", "link-arg=-nostdlib", + "-C", "panic=abort", +] diff --git a/src/services/core/.gitignore b/src/services/core/.gitignore new file mode 100644 index 0000000..1c159c8 --- /dev/null +++ b/src/services/core/.gitignore @@ -0,0 +1,4 @@ +/target +*.fossil +*.fslckout +*.log \ No newline at end of file diff --git a/src/services/core/Cargo.toml b/src/services/core/Cargo.toml new file mode 100644 index 0000000..f32339f --- /dev/null +++ b/src/services/core/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "core" +version = "0.1.0" +edition = "2021" + +[dependencies] +swiftlib = { path = "../../user", features = ["std-support"] } + +[features] +default = ["run_tests"] +run_tests = [] + +[profile.release] +panic = "abort" +lto = true + +[profile.dev] +panic = "abort" diff --git a/src/services/core/build.rs b/src/services/core/build.rs new file mode 100644 index 0000000..b545dca --- /dev/null +++ b/src/services/core/build.rs @@ -0,0 +1,48 @@ +use std::env; +use std::path::Path; + +fn main() { + // Skip mochiOS-specific linker flags when building the host PoC + if std::env::var("MOCHI_HOST_POC").is_ok() { + println!("cargo:warning=MOCHI_HOST_POC set; skipping mochiOS linker flags in services/core/build.rs"); + return; + } + + let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"); + + let root_dir = Path::new(&manifest_dir) + .ancestors() + .nth(3) + .expect("failed to determine project root"); + + // リンカスクリプトを指定 + let linker_script = Path::new(&manifest_dir).join("linker.ld"); + if linker_script.exists() { + println!("cargo:rustc-link-arg=-T{}", linker_script.display()); + println!("cargo:rerun-if-changed={}", linker_script.display()); + } + + let libs_dir = root_dir.join("ramfs").join("lib"); + + if libs_dir.exists() { + println!("cargo:rustc-link-search=native={}", libs_dir.display()); + println!("cargo:rustc-link-arg={}/crt0.o", libs_dir.display()); + println!("cargo:rustc-link-arg=-static"); + println!("cargo:rustc-link-arg=-no-pie"); + println!("cargo:rustc-link-arg=--allow-multiple-definition"); + + println!("cargo:rustc-link-lib=static=c"); + println!("cargo:rustc-link-lib=static=g"); + let libgcc_s = libs_dir.join("libgcc_s.a"); + let libg = libs_dir.join("libg.a"); + if !libgcc_s.exists() && libg.exists() { + let _ = std::fs::copy(&libg, &libgcc_s); + } + println!("cargo:rustc-link-lib=static=gcc_s"); + println!("cargo:rustc-link-lib=static=m"); + println!("cargo:rustc-link-lib=static=nosys"); + } + + println!("cargo:rerun-if-changed=../../../ramfs/lib/libc.a"); +} + diff --git a/src/services/core/linker.ld b/src/services/core/linker.ld new file mode 100644 index 0000000..bac0c56 --- /dev/null +++ b/src/services/core/linker.ld @@ -0,0 +1,31 @@ +OUTPUT_FORMAT("elf64-x86-64") +OUTPUT_ARCH(i386:x86-64) +ENTRY(_start) + +SECTIONS +{ + . = 0x800000; + + .text : { + *(.text._start) + *(.text .text.*) + } + + .rodata : ALIGN(4K) { + *(.rodata .rodata.*) + } + + .data : ALIGN(4K) { + *(.data .data.*) + } + + .bss : ALIGN(4K) { + *(COMMON) + *(.bss .bss.*) + } + + /DISCARD/ : { + *(.eh_frame) + *(.comment) + } +} diff --git a/src/services/core/src/main.rs b/src/services/core/src/main.rs new file mode 100644 index 0000000..3533058 --- /dev/null +++ b/src/services/core/src/main.rs @@ -0,0 +1,214 @@ +use swiftlib::ipc; +use swiftlib::process; +use swiftlib::task; +use swiftlib::time; + +/// READY通知OPコード +const OP_NOTIFY_READY: u64 = 0xFF; + +/// サービス定義 +struct ServiceDef { + name: &'static str, + path: &'static str, +} + +const CRITICAL_SERVICES: &[ServiceDef] = &[]; + +const BACKGROUND_SERVICES: &[ServiceDef] = &[ + ServiceDef { name: "driver.service", path: "/system/services/driver.service" }, +]; + +#[cfg(feature = "run_tests")] +const TEST_PATH: &str = "tests"; + +fn start_service(service: &ServiceDef) -> Option { + println!("[CORE] Starting service: {}", service.name); + match process::exec(service.path) { + Ok(pid) => { + println!("[CORE] {} started (PID={})", service.name, pid); + Some(pid) + } + Err(_) => { + println!("[CORE] Failed to start {}", service.name); + None + } + } +} + +fn start_background_service(service: &ServiceDef) -> Option { + println!("[CORE] Starting background service: {}", service.name); + match exec_file_via_fs_service(service.path) { + Ok(pid) => { + println!("[CORE] {} started (PID={})", service.name, pid); + Some(pid) + } + Err(errno) => { + println!("[CORE] exec failed for {}: errno={}, falling back", service.name, errno); + start_service(service) + } + } +} + +fn wait_for_ready(expected_pids: &[u64]) -> bool { + let mut pending: Vec = expected_pids.iter().copied().filter(|pid| *pid != 0).collect(); + + if pending.is_empty() { + println!("[CORE] WARNING: no critical services to wait for"); + return true; + } + + let total = pending.len(); + let mut recv_buf = [0u8; 64]; + let timeout = std::time::Duration::from_secs(20); + let start = std::time::Instant::now(); + + println!("[CORE] Waiting for {} critical service(s) to be ready...", total); + + while !pending.is_empty() { + if start.elapsed() >= timeout { + println!("[CORE] ERROR: timed out waiting for critical services"); + return false; + } + let (sender, len) = ipc::ipc_recv(&mut recv_buf); + if sender == 0 && len == 0 { + time::sleep_ms(0); + continue; + } + + if sender != 0 && (len as usize) >= 8 { + // OP コードだけ読む + let op = u64::from_le_bytes(recv_buf[..8].try_into().unwrap_or([0; 8])); + if op == OP_NOTIFY_READY { + if let Some(pos) = pending.iter().position(|pid| *pid == sender) { + pending.swap_remove(pos); + let ready_count = total - pending.len(); + println!( + "[CORE] Critical service ready (PID={}, {}/{})", + sender, ready_count, total + ); + if pending.is_empty() { + return true; + } + } + } + } + } + + true +} + +fn exec_file_via_fs_service(path: &str) -> Result { + match process::exec(path) { + Ok(pid) => Ok(pid), + Err(_) => { + let fallback = path.rsplit('/').next().unwrap_or(path); + process::exec(fallback).map_err(|_| -2) + } + } +} + +fn service_name_from_path(path: &str) -> &str { + path.rsplit('/').next().unwrap_or(path) +} + +fn is_allowed_service_path(path: &str) -> bool { + if path.is_empty() || path.contains("..") { + return false; + } + path.starts_with("/system/services/") + || path.starts_with("/bin/") + || path.starts_with("system/services/") + || path.starts_with("bin/") +} + +fn service_already_running(path: &str) -> bool { + let name = service_name_from_path(path); + task::find_process_by_name(path).is_some() + || task::find_process_by_name(name).is_some() + || task::find_process_by_name(&format!("/system/services/{}", name)).is_some() +} + +fn fs_open_read_lines(path: &str) -> Result, i64> { + match std::fs::read_to_string(path) { + Ok(text) => { + let mut lines = Vec::new(); + for line in text.lines() { + let line = line.trim(); + if line.is_empty() || line.starts_with('#') { + continue; + } + lines.push(line.to_string()); + } + Ok(lines) + } + Err(_) => Err(-2), + } +} + +fn main() { + println!("[CORE] Service Manager Started"); + + let mut critical_pids = [0u64; CRITICAL_SERVICES.len()]; + for (idx, service) in CRITICAL_SERVICES.iter().enumerate() { + let Some(pid) = start_service(service) else { + println!( + "[CORE] ERROR: failed to start critical service {}, aborting startup", + service.name + ); + return; + }; + critical_pids[idx] = pid; + } + + if !wait_for_ready(&critical_pids) { + println!("[CORE] Critical services readiness failed; aborting startup"); + return; + } + + // Try to read /config/services.list and start listed services from rootfs. + match fs_open_read_lines("/config/services.list") { + Ok(lines) => { + println!("[CORE] Found services.list with {} entries", lines.len()); + for p in lines { + if !is_allowed_service_path(&p) { + println!("[CORE] Skipping disallowed service path: {}", p); + continue; + } + if service_already_running(&p) { + println!( + "[CORE] Skipping {} ({} already running)", + p, + service_name_from_path(&p) + ); + continue; + } + println!("[CORE] Requesting exec for {}", p); + match exec_file_via_fs_service(&p) { + Ok(pid) => println!("[CORE] {} started (PID={})", p, pid), + Err(errno) => println!("[CORE] Failed to exec {}: errno={}", p, errno), + } + } + } + Err(errno) => { + println!("[CORE] No services.list (errno={}), falling back to background list", errno); + for service in BACKGROUND_SERVICES { + let _ = start_background_service(service); + } + } + } + + #[cfg(feature = "run_tests")] + { + println!("[CORE] Starting test application..."); + match process::exec(TEST_PATH) { + Ok(pid) => println!("[CORE] tests started (PID={})", pid), + Err(_) => println!("[CORE] Failed to start tests"), + } + time::sleep_ms(100); + } + + println!("[CORE] Entering monitoring loop..."); + loop { + time::sleep_ms(1000); + } +} diff --git a/src/services/driver/.cargo/config.toml b/src/services/driver/.cargo/config.toml new file mode 100644 index 0000000..a177f26 --- /dev/null +++ b/src/services/driver/.cargo/config.toml @@ -0,0 +1,11 @@ +[build] +target = "../../x86_64-mochios.json" + +[unstable] +build-std = ["std", "panic_abort"] +json-target-spec = true + +[target.x86_64-mochios] +rustflags = [ + "-C", "link-arg=-nostdlib", +] diff --git a/src/services/driver/Cargo.toml b/src/services/driver/Cargo.toml new file mode 100644 index 0000000..f25856f --- /dev/null +++ b/src/services/driver/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "driver" +version = "0.1.0" +edition = "2021" + +[dependencies] +swiftlib = { path = "../../user", features = ["std-support"] } + +[profile.release] +panic = "abort" +lto = true + +[profile.dev] +panic = "abort" diff --git a/src/services/driver/build.rs b/src/services/driver/build.rs new file mode 100644 index 0000000..fa4348b --- /dev/null +++ b/src/services/driver/build.rs @@ -0,0 +1,88 @@ +use std::env; +use std::path::{Path, PathBuf}; + +fn find_project_root(manifest_dir: &Path) -> PathBuf { + if let Ok(workspace_dir) = env::var("CARGO_WORKSPACE_DIR") { + return PathBuf::from(workspace_dir); + } + + for ancestor in manifest_dir.ancestors() { + if ancestor.join("ramfs").join("lib").exists() { + return ancestor.to_path_buf(); + } + } + + for ancestor in manifest_dir.ancestors() { + if ancestor.join("Cargo.toml").exists() { + return ancestor.to_path_buf(); + } + } + + manifest_dir.to_path_buf() +} + +fn main() { + // When building the host PoC, skip emitting mochiOS-specific linker flags. + if std::env::var("MOCHI_HOST_POC").is_ok() { + println!("cargo:warning=MOCHI_HOST_POC set; skipping mochiOS linker flags in services/driver/build.rs"); + return; + } + + let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"); + let manifest_path = Path::new(&manifest_dir); + let project_root = find_project_root(manifest_path); + + let libs_dir = project_root.join("ramfs").join("lib"); + + // ライブラリ検索パスを追加 + println!("cargo:rustc-link-search=native={}", libs_dir.display()); + + // crt0.o をリンク + println!("cargo:rustc-link-arg={}/crt0.o", libs_dir.display()); + + // 静的リンクを指定し、PIEを無効化する + println!("cargo:rustc-link-arg=-static"); + println!("cargo:rustc-link-arg=-no-pie"); + + // カスタムリンカースクリプトを使用してロードアドレスを0x800000に設定 + println!("cargo:rustc-link-arg=-T{}/linker.ld", manifest_dir); + println!("cargo:rerun-if-changed={}", manifest_path.join("linker.ld").display()); + + // 重複シンボルを許可(最初に見つかったものを使用) + println!("cargo:rustc-link-arg=--allow-multiple-definition"); + + // ライブラリをリンク + println!("cargo:rustc-link-lib=static=c"); // libc.a + println!("cargo:rustc-link-lib=static=g"); // libg.a + println!("cargo:rustc-link-lib=static=m"); // libm.a + + // std の unwind クレートが libgcc_s を要求するため libg.a を libgcc_s.a として提供 + let libgcc_s = libs_dir.join("libgcc_s.a"); + let libg = libs_dir.join("libg.a"); + if !libgcc_s.exists() && libg.exists() { + let tmp = libs_dir.join("libgcc_s.a.tmp"); + if let Err(err) = std::fs::copy(&libg, &tmp) { + panic!( + "failed to copy {} to {} for static gcc_s linking: {}", + libg.display(), + tmp.display(), + err + ); + } + if let Err(err) = std::fs::rename(&tmp, &libgcc_s) { + let _ = std::fs::remove_file(&tmp); + if !libgcc_s.exists() { + panic!( + "failed to rename {} to {} for static gcc_s linking: {}", + tmp.display(), + libgcc_s.display(), + err + ); + } + } + } + println!("cargo:rustc-link-lib=static=gcc_s"); + + println!("cargo:rerun-if-changed={}", libs_dir.join("libc.a").display()); +} + diff --git a/src/services/driver/linker.ld b/src/services/driver/linker.ld new file mode 100644 index 0000000..c8595ab --- /dev/null +++ b/src/services/driver/linker.ld @@ -0,0 +1,31 @@ +OUTPUT_FORMAT("elf64-x86-64") +OUTPUT_ARCH(i386:x86-64) +ENTRY(_start) + +SECTIONS +{ + . = 0x800000; + + .text : ALIGN(4K) { + *(.text._start) + *(.text .text.*) + } + + .rodata : ALIGN(4K) { + *(.rodata .rodata.*) + } + + .data : ALIGN(4K) { + *(.data .data.*) + } + + .bss : ALIGN(4K) { + *(COMMON) + *(.bss .bss.*) + } + + /DISCARD/ : { + *(.eh_frame) + *(.comment) + } +} diff --git a/src/services/driver/src/main.rs b/src/services/driver/src/main.rs new file mode 100644 index 0000000..cb330ea --- /dev/null +++ b/src/services/driver/src/main.rs @@ -0,0 +1,88 @@ +use std::vec::Vec; + +use swiftlib::process; +use swiftlib::time; +use swiftlib::task; +use swiftlib::ipc; +use swiftlib::io; + +const OP_NOTIFY_READY: u64 = 0xFF; +const DRIVER_CONFIG_PATH: &str = "/config/drivers.list"; +const DEFAULT_DRIVERS: &[&str] = &["/bin/drivers/usb.elf"]; + +fn load_driver_list() -> Vec { + let mut drivers = Vec::new(); + + match std::fs::read_to_string(DRIVER_CONFIG_PATH) { + Ok(text) => { + for line in text.lines() { + let line = line.trim(); + if line.is_empty() || line.starts_with('#') { + continue; + } + drivers.push(line.to_string()); + } + } + Err(_) => { + println!( + "[DRIVER] Failed to open {} (using defaults)", + DRIVER_CONFIG_PATH + ); + } + } + + if drivers.is_empty() { + for path in DEFAULT_DRIVERS { + drivers.push((*path).to_string()); + } + } + + drivers +} + +fn start_driver(path: &str) { + let probe_fd = io::open(path, io::O_RDONLY); + if probe_fd < 0 { + println!("[DRIVER] Skipping missing driver binary {}", path); + return; + } + let _ = io::close(probe_fd as u64); + println!("[DRIVER] Starting {}", path); + match process::exec(path) { + Ok(pid) => println!("[DRIVER] Started {} (PID={})", path, pid), + Err(_) => println!("[DRIVER] Failed to start {}", path), + } +} + +fn notify_ready_to_core() { + let core_pid = match task::find_process_by_name("core.service") { + Some(pid) => pid, + None => { + println!("[DRIVER] WARNING: core.service not found, skipping READY notify"); + return; + } + }; + + let op_bytes = OP_NOTIFY_READY.to_le_bytes(); + if ipc::ipc_send(core_pid, &op_bytes) == 0 { + println!("[DRIVER] Sent READY to core.service (PID={})", core_pid); + } else { + println!("[DRIVER] Failed to send READY to core.service"); + } +} + +fn main() { + println!("[DRIVER] Driver service started"); + + let drivers = load_driver_list(); + for path in &drivers { + start_driver(path); + } + + notify_ready_to_core(); + + println!("[DRIVER] Entering monitoring loop..."); + loop { + time::sleep_ms(1000); + } +} diff --git a/src/services/index.toml b/src/services/index.toml new file mode 100644 index 0000000..38ce8e7 --- /dev/null +++ b/src/services/index.toml @@ -0,0 +1,30 @@ +# サービスの定義 +# +# 形式: +# [service.サービス名] +# description = サービスの説明 +# dir = サービスのディレクトリ +# fs = サービスをどのファイルシステムに配置するか(initfs または ata) +# autostart = カーネル起動時に自動起動するか(initfsに含まれている奴は勝手に起動するやつだけにしようね) +# order = 起動順序(数値が小さいほど先に起動、core.serviceが他を起動) + +[core.service] +description = "Core service / system manager" +dir = "core" +fs = "initfs" +autostart = false +order = 0 + +[core.service.driver] +description = "Driver manager - launches hardware drivers" +dir = "driver" +fs = "ata" +autostart = true +order = 5 + +[core.service.shell] +description = "Draw video output and shell interface" +dir = "shell" +fs = "ata" +autostart = true +order = 10 diff --git a/src/services/shell/.cargo/config.toml b/src/services/shell/.cargo/config.toml new file mode 100644 index 0000000..c9eb2f1 --- /dev/null +++ b/src/services/shell/.cargo/config.toml @@ -0,0 +1,12 @@ +[build] +target = "../../x86_64-mochios.json" + +[unstable] +build-std = ["std", "panic_abort"] +json-target-spec = true + +[target.x86_64-mochios] +rustflags = [ + "-C", "link-arg=-nostdlib", + "-C", "panic=abort", +] diff --git a/src/services/shell/.gitignore b/src/services/shell/.gitignore new file mode 100644 index 0000000..1c159c8 --- /dev/null +++ b/src/services/shell/.gitignore @@ -0,0 +1,4 @@ +/target +*.fossil +*.fslckout +*.log \ No newline at end of file diff --git a/src/services/shell/Cargo.toml b/src/services/shell/Cargo.toml new file mode 100644 index 0000000..7f0a98a --- /dev/null +++ b/src/services/shell/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "shell" +version = "0.1.0" +edition = "2021" + +[dependencies] +swiftlib = { path = "../../user", features = ["std-support"] } + +[profile.release] +panic = "abort" +lto = true + +[profile.dev] +panic = "abort" diff --git a/src/services/shell/build.rs b/src/services/shell/build.rs new file mode 100644 index 0000000..a711928 --- /dev/null +++ b/src/services/shell/build.rs @@ -0,0 +1,48 @@ +use std::env; +use std::path::Path; + +fn main() { + // Skip when building host PoC + if std::env::var("MOCHI_HOST_POC").is_ok() { + println!("cargo:warning=MOCHI_HOST_POC set; skipping mochiOS linker flags in services/shell/build.rs"); + return; + } + + let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"); + + let root_dir = Path::new(&manifest_dir) + .ancestors() + .nth(3) + .expect("failed to determine project root"); + + // リンカスクリプトを指定 + let linker_script = Path::new(&manifest_dir).join("linker.ld"); + if linker_script.exists() { + println!("cargo:rustc-link-arg=-T{}", linker_script.display()); + println!("cargo:rerun-if-changed={}", linker_script.display()); + } + + let libs_dir = root_dir.join("ramfs").join("lib"); + + if libs_dir.exists() { + println!("cargo:rustc-link-search=native={}", libs_dir.display()); + println!("cargo:rustc-link-arg={}/crt0.o", libs_dir.display()); + println!("cargo:rustc-link-arg=-static"); + println!("cargo:rustc-link-arg=-no-pie"); + println!("cargo:rustc-link-arg=--allow-multiple-definition"); + + println!("cargo:rustc-link-lib=static=c"); + println!("cargo:rustc-link-lib=static=g"); + let libgcc_s = libs_dir.join("libgcc_s.a"); + let libg = libs_dir.join("libg.a"); + if !libgcc_s.exists() && libg.exists() { + let _ = std::fs::copy(&libg, &libgcc_s); + } + println!("cargo:rustc-link-lib=static=gcc_s"); + println!("cargo:rustc-link-lib=static=m"); + println!("cargo:rustc-link-lib=static=nosys"); + } + + println!("cargo:rerun-if-changed=../../../ramfs/lib/libc.a"); +} + diff --git a/src/services/shell/linker.ld b/src/services/shell/linker.ld new file mode 100644 index 0000000..bac0c56 --- /dev/null +++ b/src/services/shell/linker.ld @@ -0,0 +1,31 @@ +OUTPUT_FORMAT("elf64-x86-64") +OUTPUT_ARCH(i386:x86-64) +ENTRY(_start) + +SECTIONS +{ + . = 0x800000; + + .text : { + *(.text._start) + *(.text .text.*) + } + + .rodata : ALIGN(4K) { + *(.rodata .rodata.*) + } + + .data : ALIGN(4K) { + *(.data .data.*) + } + + .bss : ALIGN(4K) { + *(COMMON) + *(.bss .bss.*) + } + + /DISCARD/ : { + *(.eh_frame) + *(.comment) + } +} diff --git a/src/services/shell/src/char.rs b/src/services/shell/src/char.rs new file mode 100644 index 0000000..66c2aa9 --- /dev/null +++ b/src/services/shell/src/char.rs @@ -0,0 +1,2355 @@ +use swiftlib::{fs, io, ipc, process, task, vga}; + +// 色の編集がだるっちいったらありゃしないのでgeminiに作ってもらったエディタを使ってください。 +// https://gemini.google.com/share/02481dc7584f + +const FONT_WIDTH: usize = 6; +const FONT_HEIGHT: usize = 12; +const ASCII_START: usize = 32; +const ASCII_END: usize = 127; +const GLYPH_COUNT: usize = ASCII_END - ASCII_START; +const DEFAULT_FG: u32 = 0x00FF_FFFF; +const DEFAULT_BG: u32 = 0x0000_0000; +const ANSI_MAX_SEQ_LEN: usize = 32; +const ANSI_COLOR_NORMAL: [u32; 8] = [ + 0x0000_0000, // black + 0x00EE_0000, // red + 0x0000_AA00, // green + 0x00AA_AA00, // yellow + 0x0000_99FF, // blue + 0x00AA_00AA, // magenta + 0x0000_AAAA, // cyan + 0x00AA_AAAA, // white +]; +const ANSI_COLOR_BRIGHT: [u32; 8] = [ + 0x0055_5555, // bright black (gray) + 0x00FF_5555, // bright red + 0x0055_FF55, // bright green + 0x00FF_FF55, // bright yellow + 0x0055_55FF, // bright blue + 0x00FF_55FF, // bright magenta + 0x0055_FFFF, // bright cyan + 0x00FF_FFFF, // bright white +]; +const FONT_BIN_PATH: &str = "/system/fonts/ter-u12b.bin"; +const FONT_BDF_PATH: &str = "/system/fonts/ter-u12b.bdf"; +const ENV_FILE_PATH: &str = "/config/env.txt"; +const FONT_BIN_SIZE: usize = GLYPH_COUNT * FONT_HEIGHT; +const FONT_BDF_MAX_SIZE: usize = 512 * 1024; +const ENV_FILE_MAX_SIZE: usize = 4096; +const FONT_READ_CHUNK: usize = 512; +const FS_PATH_MAX: usize = 128; +const FS_REQ_TIMEOUT_MS: u64 = 2000; +const IPC_MSG_MAX: usize = 4128; +const PENDING_IPC_CAPACITY: usize = 32; + +#[repr(C)] +#[derive(Clone, Copy)] +struct FsRequest { + op: u64, + arg1: u64, + arg2: u64, + path: [u8; FS_PATH_MAX], +} + +impl FsRequest { + const OP_OPEN: u64 = 1; + const OP_READ: u64 = 2; + const OP_CLOSE: u64 = 4; + const OP_EXEC: u64 = 5; + const OP_STAT: u64 = 6; + const OP_READDIR: u64 = 8; +} + +/// FS service の STAT/FSTAT が返す mode のファイルタイプビット +const S_IFMT: u64 = 0o170000; +const S_IFDIR: u64 = 0o040000; +const S_IFREG: u64 = 0o100000; + +#[repr(C)] +#[derive(Clone, Copy)] +struct FsResponse { + status: i64, + len: u64, + data: [u8; swiftlib::fs_consts::FS_DATA_MAX], +} + +#[derive(Clone, Copy)] +struct PendingIpcMessage { + used: bool, + sender: u64, + len: usize, + data: [u8; IPC_MSG_MAX], +} + +impl PendingIpcMessage { + const fn new() -> Self { + Self { + used: false, + sender: 0, + len: 0, + data: [0; IPC_MSG_MAX], + } + } +} + +static mut PENDING_IPC_MESSAGES: [PendingIpcMessage; PENDING_IPC_CAPACITY] = + [PendingIpcMessage::new(); PENDING_IPC_CAPACITY]; + +fn enqueue_pending_message(sender: u64, data: &[u8], len: usize) -> bool { + let copy_len = core::cmp::min(len, core::cmp::min(data.len(), IPC_MSG_MAX)); + unsafe { + for slot in &mut PENDING_IPC_MESSAGES { + if !slot.used { + slot.used = true; + slot.sender = sender; + slot.len = copy_len; + if copy_len > 0 { + slot.data[..copy_len].copy_from_slice(&data[..copy_len]); + } + return true; + } + } + } + false +} + +fn take_pending_message(buf: &mut [u8]) -> Option<(u64, usize)> { + unsafe { + for slot in &mut PENDING_IPC_MESSAGES { + if slot.used { + let copy_len = core::cmp::min(slot.len, buf.len()); + if copy_len > 0 { + buf[..copy_len].copy_from_slice(&slot.data[..copy_len]); + } + let sender = slot.sender; + slot.used = false; + slot.sender = 0; + slot.len = 0; + return Some((sender, copy_len)); + } + } + } + None +} + +fn read_file(path: &str, max_size: usize) -> Option> { + if max_size == 0 { + return None; + } + + let fd = io::open(path, io::O_RDONLY); + if fd < 0 { + return None; + } + + let mut out = Vec::new(); + let mut chunk = [0u8; FONT_READ_CHUNK]; + while out.len() < max_size { + let read_len = core::cmp::min(chunk.len(), max_size - out.len()); + let n = io::read(fd as u64, &mut chunk[..read_len]); + if (n as i64) < 0 { + let _ = io::close(fd as u64); + return None; + } + let n = n as usize; + if n == 0 { + break; + } + out.extend_from_slice(&chunk[..n]); + } + + let _ = io::close(fd as u64); + if out.is_empty() { + None + } else { + Some(out) + } +} + +fn read_file_from_fs(path: &str, max_size: usize) -> Option> { + read_file(path, max_size) +} + +fn encode_exec_path_and_args(path: &str, args: &[&str]) -> Option<[u8; FS_PATH_MAX]> { + let mut out = [0u8; FS_PATH_MAX]; + let path_bytes = path.as_bytes(); + if path_bytes.is_empty() || path_bytes.len() + 1 > FS_PATH_MAX { + return None; + } + out[..path_bytes.len()].copy_from_slice(path_bytes); + let mut pos = path_bytes.len() + 1; // path の終端 NUL + + for arg in args { + let b = arg.as_bytes(); + if b.is_empty() { + continue; + } + if pos + b.len() + 1 > FS_PATH_MAX { + return None; + } + out[pos..pos + b.len()].copy_from_slice(b); + pos += b.len(); + out[pos] = 0; + pos += 1; + } + Some(out) +} + +fn exec_via_fs_service(path: &str, args: &[&str]) -> Result { + process::exec_with_args(path, args).map_err(|_| -2) +} + +/// OP_STAT 経由でファイルの (mode, size) を取得 +fn stat_via_fs_service(path: &str) -> Option<(u64, u64)> { + let fd = io::open(path, io::O_RDONLY); + if fd < 0 { + return None; + } + + let mut dirbuf = [0u8; 4096]; + let n = fs::readdir(fd as u64, &mut dirbuf); + if (n as i64) > 0 { + let _ = io::close(fd as u64); + return Some((0x4000 | 0o755, 0)); + } + + let mut size = 0u64; + let mut buf = [0u8; 4096]; + loop { + let n = io::read(fd as u64, &mut buf); + if (n as i64) < 0 { + let _ = io::close(fd as u64); + return None; + } + if n == 0 { + break; + } + size = size.saturating_add(n); + } + let _ = io::close(fd as u64); + Some((0x8000 | 0o755, size)) +} + +/// OP_READDIR をページネーションしながら全エントリ名を取得 +fn readdir_all_via_fs_service(path: &str) -> Option> { + let fd = io::open(path, io::O_RDONLY); + if fd < 0 { + return None; + } + let mut entries: Vec = Vec::new(); + let mut buf = [0u8; 4096]; + let n = fs::readdir(fd as u64, &mut buf); + let _ = io::close(fd as u64); + if (n as i64) <= 0 { + return None; + } + for chunk in buf[..n as usize].split(|&b| b == b'\n') { + if chunk.is_empty() { + continue; + } + if let Ok(s) = core::str::from_utf8(chunk) { + entries.push(s.to_string()); + } + } + Some(entries) +} + +/// CWD を基準にパスを絶対化する。 "." "/foo" "../bar" などを解決。 +fn resolve_path(arg: &str) -> String { + let abs = if arg.starts_with('/') { + arg.to_string() + } else { + let mut cwd_buf = [0u8; 256]; + let cwd = fs::getcwd(&mut cwd_buf).unwrap_or("/"); + if arg.is_empty() || arg == "." { + return cwd.to_string(); + } + if cwd == "/" { + format!("/{}", arg) + } else { + format!("{}/{}", cwd, arg) + } + }; + + // "." と ".." を正規化 + let mut stack: Vec<&str> = Vec::new(); + for comp in abs.split('/') { + match comp { + "" | "." => {} + ".." => { + stack.pop(); + } + other => stack.push(other), + } + } + if stack.is_empty() { + "/".to_string() + } else { + let mut out = String::new(); + for c in &stack { + out.push('/'); + out.push_str(c); + } + out + } +} + +/// ASCII 文字ごとの 12 行ビットマップ (インデックス = codepoint - 32) +pub struct Font { + glyphs: [[u8; FONT_HEIGHT]; GLYPH_COUNT], +} + +impl Font { + fn fallback() -> Self { + let mut glyphs = [[0u8; FONT_HEIGHT]; GLYPH_COUNT]; + for (i, glyph) in glyphs.iter_mut().enumerate() { + let ch = (ASCII_START + i) as u8; + if ch == b' ' { + continue; + } + glyph[0] = 0xFC; + glyph[FONT_HEIGHT - 1] = 0xFC; + for row in glyph.iter_mut().take(FONT_HEIGHT - 1).skip(1) { + *row = 0x84; + } + } + Font { glyphs } + } + + fn load_from_binary() -> Option { + match read_file_from_fs(FONT_BIN_PATH, FONT_BIN_SIZE) { + Some(data) => { + if data.len() < FONT_BIN_SIZE { + println!("[SHELL] Font binary too small: {} bytes", data.len()); + return None; + } + let mut glyphs = [[0u8; FONT_HEIGHT]; GLYPH_COUNT]; + for (i, glyph) in glyphs.iter_mut().enumerate() { + let start = i * FONT_HEIGHT; + glyph.copy_from_slice(&data[start..start + FONT_HEIGHT]); + } + Some(Font { glyphs }) + } + None => { + println!("[SHELL] Font binary not found or read failed: {}", FONT_BIN_PATH); + None + } + } + } + + fn load_from_bdf() -> Option { + match read_file_from_fs(FONT_BDF_PATH, FONT_BDF_MAX_SIZE) { + Some(data) => { + let mut glyphs = [[0u8; FONT_HEIGHT]; GLYPH_COUNT]; + parse_bdf(&data, &mut glyphs); + Some(Font { glyphs }) + } + None => { + println!("[SHELL] BDF font not found or read failed: {}", FONT_BDF_PATH); + None + } + } + } + + /// `system/fonts/ter-u12b.bin` を優先し、失敗時はBDFを解析する + pub fn load() -> Option { + if let Some(font) = Self::load_from_binary() { + return Some(font); + } + if let Some(font) = Self::load_from_bdf() { + return Some(font); + } + println!("[SHELL] Using fallback font"); + Some(Self::fallback()) + } + + fn glyph(&self, ch: u8) -> &[u8; FONT_HEIGHT] { + let idx = if ch >= ASCII_START as u8 && ch < ASCII_END as u8 { + (ch as usize) - ASCII_START + } else { + ('?' as usize) - ASCII_START + }; + &self.glyphs[idx] + } +} + +/// BDF データから ASCII グリフを解析して `glyphs` に書き込む +fn parse_bdf(data: &[u8], glyphs: &mut [[u8; FONT_HEIGHT]; GLYPH_COUNT]) { + let text = core::str::from_utf8(data).unwrap_or(""); + let mut lines = text.lines(); + let mut encoding: Option = None; + let mut in_bitmap = false; + let mut row = 0usize; + + loop { + let line = match lines.next() { + Some(l) => l.trim(), + None => break, + }; + if line.starts_with("ENCODING ") { + encoding = line[9..].trim().parse::().ok(); + in_bitmap = false; + row = 0; + } else if line == "BITMAP" { + in_bitmap = true; + row = 0; + } else if line == "ENDCHAR" { + in_bitmap = false; + encoding = None; + row = 0; + } else if in_bitmap { + if let Some(enc) = encoding { + if enc >= ASCII_START && enc < ASCII_END { + let idx = enc - ASCII_START; + if row < FONT_HEIGHT { + if let Ok(byte) = u8::from_str_radix(line, 16) { + glyphs[idx][row] = byte; + } + row += 1; + } + } + } + } + } +} + +/// フレームバッファへの書き込みを管理するターミナル +pub struct Terminal { + fb_ptr: *mut u32, + width: u32, + height: u32, + stride: u32, + col: u32, + row: u32, + max_cols: u32, + max_rows: u32, + font: Font, + pub fg: u32, + bg: u32, + pub input_buf: [u8; 256], + pub input_len: usize, + env: Vec<(String, String)>, + ansi_esc_pending: bool, + ansi_csi_mode: bool, + ansi_osc_mode: bool, + ansi_dcs_mode: bool, + ansi_osc_esc_pending: bool, + ansi_dcs_esc_pending: bool, + ansi_seq: [u8; ANSI_MAX_SEQ_LEN], + ansi_seq_len: usize, + ansi_saved_col: u32, + ansi_saved_row: u32, + scroll_top: u32, + scroll_bottom: u32, + insert_mode: bool, + cursor_visible: bool, + cursor_drawn: bool, + cursor_draw_col: u32, + cursor_draw_row: u32, + alt_screen: Option, + last_printable: u8, + cells: Vec, + // コマンドパスキャッシュ(最大16エントリ) + cmd_cache: Vec<(String, String)>, // (cmd_name, full_path) +} + +struct AltScreenState { + cells: Vec, + col: u32, + row: u32, + fg: u32, + bg: u32, + scroll_top: u32, + scroll_bottom: u32, + insert_mode: bool, +} + +#[derive(Clone, Copy)] +struct Cell { + ch: u8, + fg: u32, + bg: u32, +} + +impl Cell { + const fn blank() -> Self { + Self { + ch: b' ', + fg: DEFAULT_FG, + bg: DEFAULT_BG, + } + } +} + +#[allow(unused)] +impl Terminal { + fn drain_pending_ipc_messages(&mut self, buf: &mut [u8]) -> bool { + let mut wrote = false; + while let Some((_, len)) = take_pending_message(buf) { + if len == 0 || len > buf.len() { + continue; + } + self.write_bytes(&buf[..len]); + wrote = true; + } + wrote + } + + fn load_env_file(&mut self) { + let data = match read_file(ENV_FILE_PATH, ENV_FILE_MAX_SIZE) + { + Some(d) => d, + None => return, + }; + let text = match core::str::from_utf8(&data) { + Ok(t) => t, + Err(_) => return, + }; + + for raw_line in text.lines() { + let line = raw_line.trim(); + if line.is_empty() || line.starts_with('#') { + continue; + } + if let Some(eq) = line.find('=') { + let key = line[..eq].trim(); + let val = line[eq + 1..].trim(); + if !key.is_empty() { + self.set_env(key, val); + } + } + } + } + + fn command_exists(&self, path: &str) -> bool { + // stat syscall 未実装のため open/close で存在確認 + // 1回のopen試行で十分 + let fd = swiftlib::io::open(path, io::O_RDONLY); + if fd >= 0 { + swiftlib::io::close(fd as u64); + return true; + } + false + } + + fn should_try_busybox_alias(cmd: &str) -> bool { + matches!(cmd, "ls" | "cat") + } + + fn busybox_fallback_in_path(&mut self) -> Option { + // busybox専用キャッシュキー + let cache_key = "__busybox__"; + if let Some(cached) = self.cmd_cache.iter().find(|(c, _)| c == cache_key) { + return Some(cached.1.clone()); + } + + let path_val = self.get_env("PATH").unwrap_or_default(); + for dir in path_val.split(':') { + let dir = dir.trim(); + if dir.is_empty() { + continue; + } + let candidate = format!("{}/busybox.elf", dir); + if self.command_exists(&candidate) { + // キャッシュに追加 + if self.cmd_cache.len() >= 16 { + self.cmd_cache.remove(0); + } + self.cmd_cache.push((cache_key.to_string(), candidate.clone())); + return Some(candidate); + } + } + None + } + + pub fn new(fb_ptr: *mut u32, info: vga::FbInfo, font: Font) -> Self { + let max_cols = info.width / FONT_WIDTH as u32; + let max_rows = info.height / FONT_HEIGHT as u32; + let mut env = Vec::new(); + env.push(("PATH".to_string(), "/bin:/applications".to_string())); + env.push(("TERM".to_string(), "xterm-256color".to_string())); + let mut term = Terminal { + fb_ptr, + width: info.width, + height: info.height, + stride: info.stride, + col: 0, + row: 0, + max_cols, + max_rows, + font, + fg: DEFAULT_FG, + bg: DEFAULT_BG, + input_buf: [0u8; 256], + input_len: 0, + env, + ansi_esc_pending: false, + ansi_csi_mode: false, + ansi_osc_mode: false, + ansi_dcs_mode: false, + ansi_osc_esc_pending: false, + ansi_dcs_esc_pending: false, + ansi_seq: [0; ANSI_MAX_SEQ_LEN], + ansi_seq_len: 0, + ansi_saved_col: 0, + ansi_saved_row: 0, + scroll_top: 0, + scroll_bottom: max_rows.saturating_sub(1), + insert_mode: false, + cursor_visible: true, + cursor_drawn: false, + cursor_draw_col: 0, + cursor_draw_row: 0, + alt_screen: None, + last_printable: b' ', + cells: vec![Cell::blank(); (max_cols as usize).saturating_mul(max_rows as usize)], + cmd_cache: Vec::new(), + }; + term.load_env_file(); + term + } + + fn get_env(&self, key: &str) -> Option { + self.env.iter().rev().find(|(k, _)| k == key).map(|(_, v)| v.clone()) + } + + fn set_env(&mut self, key: &str, val: &str) { + if let Some(entry) = self.env.iter_mut().find(|(k, _)| k == key) { + entry.1 = val.to_string(); + } else { + self.env.push((key.to_string(), val.to_string())); + } + } + + /// PATH の各ディレクトリでコマンドを探す + /// `cmd` が `.elf` で終わる場合はそのまま、そうでなければ `.elf` を付けて検索する + fn find_in_path(&mut self, cmd: &str) -> Option { + // キャッシュを確認 + if let Some(cached) = self.cmd_cache.iter().find(|(c, _)| c == cmd) { + return Some(cached.1.clone()); + } + + let path_val = self.get_env("PATH").unwrap_or_default(); + let filename = if cmd.ends_with(".elf") { + cmd.to_string() + } else { + format!("{}.elf", cmd) + }; + for dir in path_val.split(':') { + let dir = dir.trim(); + if dir.is_empty() { + continue; + } + let candidate = format!("{}/{}", dir, filename); + // stat syscall 未実装のため open/close で存在確認 + if self.command_exists(&candidate) { + // キャッシュに追加(最大16エントリ) + if self.cmd_cache.len() >= 16 { + self.cmd_cache.remove(0); + } + self.cmd_cache.push((cmd.to_string(), candidate.clone())); + return Some(candidate); + } + if !cmd.ends_with(".elf") { + let app_candidate = format!("{}/{}.app/entry.elf", dir, cmd); + if self.command_exists(&app_candidate) { + if self.cmd_cache.len() >= 16 { + self.cmd_cache.remove(0); + } + self.cmd_cache + .push((cmd.to_string(), app_candidate.clone())); + return Some(app_candidate); + } + } + } + None + } + + fn put_pixel(&self, x: u32, y: u32, color: u32) { + if x >= self.width || y >= self.height { + return; + } + let offset = (y * self.stride + x) as usize; + // GOP 実装によっては上位8bitをアルファとして扱うため、常に不透明で書き込む。 + let opaque = color | 0xFF00_0000; + unsafe { + self.fb_ptr.add(offset).write_volatile(opaque); + } + } + + fn cell_index(&self, col: u32, row: u32) -> Option { + if col >= self.max_cols || row >= self.max_rows { + return None; + } + Some((row as usize) * (self.max_cols as usize) + (col as usize)) + } + + fn draw_char_pixels(&self, ch: u8, col: u32, row: u32, fg: u32, bg: u32) { + let glyph = *self.font.glyph(ch); + let x0 = col * FONT_WIDTH as u32; + let y0 = row * FONT_HEIGHT as u32; + for (r, &bits) in glyph.iter().enumerate() { + let y = y0 + r as u32; + if y >= self.height { break; } + if x0 + FONT_WIDTH as u32 > self.width { break; } + for c in 0..FONT_WIDTH { + let on = (bits >> (7 - c)) & 1 != 0; + let color = if on { fg } else { bg }; + self.put_pixel(x0 + c as u32, y, color); + } + } + } + + fn set_cell(&mut self, col: u32, row: u32, cell: Cell) { + if let Some(idx) = self.cell_index(col, row) { + self.cells[idx] = cell; + self.draw_char_pixels(cell.ch, col, row, cell.fg, cell.bg); + } + } + + fn get_cell(&self, col: u32, row: u32) -> Cell { + match self.cell_index(col, row) { + Some(idx) => self.cells[idx], + None => Cell::blank(), + } + } + + fn redraw_from_cells(&mut self) { + for row in 0..self.max_rows { + for col in 0..self.max_cols { + let c = self.get_cell(col, row); + self.draw_char_pixels(c.ch, col, row, c.fg, c.bg); + } + } + } + + fn hide_cursor_overlay(&mut self) { + if !self.cursor_drawn { + return; + } + let col = self.cursor_draw_col; + let row = self.cursor_draw_row; + self.cursor_drawn = false; + if col >= self.max_cols || row >= self.max_rows { + return; + } + let cell = self.get_cell(col, row); + self.draw_char_pixels(cell.ch, col, row, cell.fg, cell.bg); + } + + fn show_cursor_overlay(&mut self) { + if !self.cursor_visible || self.max_cols == 0 || self.max_rows == 0 { + self.cursor_drawn = false; + return; + } + let col = core::cmp::min(self.col, self.max_cols.saturating_sub(1)); + let row = core::cmp::min(self.row, self.max_rows.saturating_sub(1)); + let cell = self.get_cell(col, row); + self.draw_char_pixels(cell.ch, col, row, cell.bg, cell.fg); + self.cursor_drawn = true; + self.cursor_draw_col = col; + self.cursor_draw_row = row; + } + + fn refresh_cursor_overlay(&mut self) { + self.hide_cursor_overlay(); + self.show_cursor_overlay(); + } + + pub fn clear_screen(&mut self) { + self.hide_cursor_overlay(); + let total = (self.height * self.stride) as usize; + for i in 0..total { + unsafe { + self.fb_ptr.add(i).write_volatile(0); + } + } + for cell in &mut self.cells { + *cell = Cell::blank(); + } + self.col = 0; + self.row = 0; + self.scroll_top = 0; + self.scroll_bottom = self.max_rows.saturating_sub(1); + self.insert_mode = false; + self.show_cursor_overlay(); + } + + fn normalize_scroll_region(&mut self) { + if self.max_rows == 0 { + self.scroll_top = 0; + self.scroll_bottom = 0; + return; + } + if self.scroll_top >= self.max_rows { + self.scroll_top = self.max_rows - 1; + } + if self.scroll_bottom >= self.max_rows { + self.scroll_bottom = self.max_rows - 1; + } + if self.scroll_top > self.scroll_bottom { + self.scroll_top = 0; + self.scroll_bottom = self.max_rows - 1; + } + } + + fn scroll_region_up(&mut self, top: u32, bottom: u32, count: u32) { + if self.max_rows == 0 || self.max_cols == 0 || top >= bottom { + return; + } + let n = core::cmp::min(count.max(1), bottom - top + 1); + for row in top..=bottom.saturating_sub(n) { + for col in 0..self.max_cols { + let src = self.get_cell(col, row + n); + if let Some(idx) = self.cell_index(col, row) { self.cells[idx] = src; } + } + } + for row in bottom.saturating_sub(n).saturating_add(1)..=bottom { + for col in 0..self.max_cols { + if let Some(idx) = self.cell_index(col, row) { self.cells[idx] = Cell::blank(); } + } + } + self.redraw_from_cells(); + } + + fn scroll_region_down(&mut self, top: u32, bottom: u32, count: u32) { + if self.max_rows == 0 || self.max_cols == 0 || top >= bottom { + return; + } + let n = core::cmp::min(count.max(1), bottom - top + 1); + let mut row = bottom; + while row >= top + n { + for col in 0..self.max_cols { + let src = self.get_cell(col, row - n); + if let Some(idx) = self.cell_index(col, row) { self.cells[idx] = src; } + } + if row == 0 { break; } + row -= 1; + } + for row in top..top + n { + for col in 0..self.max_cols { + if let Some(idx) = self.cell_index(col, row) { self.cells[idx] = Cell::blank(); } + } + } + self.redraw_from_cells(); + } + + fn scroll_up(&mut self) { + if self.max_rows == 0 { + return; + } + self.scroll_region_up(self.scroll_top, self.scroll_bottom, 1); + } + + /// 互換性のために残す(シャドウバッファ廃止により no-op) + pub fn flush(&mut self) { + self.refresh_cursor_overlay(); + } + + fn index(&mut self) { + self.normalize_scroll_region(); + if self.max_rows == 0 { + return; + } + if self.row == self.scroll_bottom { + self.scroll_region_up(self.scroll_top, self.scroll_bottom, 1); + } else if self.row + 1 < self.max_rows { + self.row += 1; + } + } + + fn reverse_index(&mut self) { + self.normalize_scroll_region(); + if self.max_rows == 0 { + return; + } + if self.row == self.scroll_top { + self.scroll_region_down(self.scroll_top, self.scroll_bottom, 1); + } else { + self.row = self.row.saturating_sub(1); + } + } + + fn new_line(&mut self) { + self.col = 0; + self.index(); + } + + pub fn erase_previous_cell(&mut self) { + if self.max_cols == 0 || self.max_rows == 0 { + return; + } + if self.col > 0 { + self.col -= 1; + self.set_cell( + self.col, + self.row, + Cell { + ch: b' ', + fg: self.fg, + bg: self.bg, + }, + ); + } + } + + fn set_scroll_region(&mut self, top: u32, bottom: u32) { + if self.max_rows == 0 { + self.scroll_top = 0; + self.scroll_bottom = 0; + return; + } + if top >= self.max_rows || bottom >= self.max_rows || top >= bottom { + self.scroll_top = 0; + self.scroll_bottom = self.max_rows - 1; + } else { + self.scroll_top = top; + self.scroll_bottom = bottom; + } + self.col = 0; + self.row = self.scroll_top; + } + + fn enter_alt_screen(&mut self) { + if self.alt_screen.is_some() { + return; + } + let saved = AltScreenState { + cells: self.cells.clone(), + col: self.col, + row: self.row, + fg: self.fg, + bg: self.bg, + scroll_top: self.scroll_top, + scroll_bottom: self.scroll_bottom, + insert_mode: self.insert_mode, + }; + self.alt_screen = Some(saved); + for cell in &mut self.cells { + *cell = Cell::blank(); + } + self.col = 0; + self.row = 0; + self.scroll_top = 0; + self.scroll_bottom = self.max_rows.saturating_sub(1); + self.insert_mode = false; + self.redraw_from_cells(); + } + + fn leave_alt_screen(&mut self) { + if let Some(saved) = self.alt_screen.take() { + self.cells = saved.cells; + self.col = core::cmp::min(saved.col, self.max_cols.saturating_sub(1)); + self.row = core::cmp::min(saved.row, self.max_rows.saturating_sub(1)); + self.fg = saved.fg; + self.bg = saved.bg; + self.scroll_top = saved.scroll_top; + self.scroll_bottom = saved.scroll_bottom; + self.insert_mode = saved.insert_mode; + self.normalize_scroll_region(); + self.redraw_from_cells(); + } + } + + pub fn write_byte(&mut self, byte: u8) { + self.hide_cursor_overlay(); + match byte { + b'\n' => self.new_line(), + b'\r' => { self.col = 0; } + b'\t' => { + let next_tab = ((self.col / 8) + 1) * 8; + while self.col < core::cmp::min(next_tab, self.max_cols) { + self.set_cell( + self.col, + self.row, + Cell { + ch: b' ', + fg: self.fg, + bg: self.bg, + }, + ); + self.col += 1; + } + } + 0x08 => { // Backspace + if self.col > 0 { + self.col -= 1; + } + } + _ => { + if !(0x20..=0x7E).contains(&byte) { + return; + } + if self.col >= self.max_cols { + self.new_line(); + } + if self.insert_mode { + self.insert_blank_chars(1); + } + self.set_cell( + self.col, + self.row, + Cell { + ch: byte, + fg: self.fg, + bg: self.bg, + }, + ); + self.col += 1; + self.last_printable = byte; + } + } + self.show_cursor_overlay(); + } + + fn ansi_color(index: u16, bright: bool) -> Option { + let i = index as usize; + if i >= ANSI_COLOR_NORMAL.len() { + return None; + } + if bright { + Some(ANSI_COLOR_BRIGHT[i]) + } else { + Some(ANSI_COLOR_NORMAL[i]) + } + } + + fn apply_sgr_code(&mut self, code: u16) { + match code { + 0 => { + self.fg = DEFAULT_FG; + self.bg = DEFAULT_BG; + } + 30..=37 => { + if let Some(color) = Self::ansi_color(code - 30, false) { + self.fg = color; + } + } + 90..=97 => { + if let Some(color) = Self::ansi_color(code - 90, true) { + self.fg = color; + } + } + 39 => self.fg = DEFAULT_FG, + 40..=47 => { + if let Some(color) = Self::ansi_color(code - 40, false) { + self.bg = color; + } + } + 100..=107 => { + if let Some(color) = Self::ansi_color(code - 100, true) { + self.bg = color; + } + } + 49 => self.bg = DEFAULT_BG, + _ => {} + } + } + + fn parse_ascii_u16(bytes: &[u8]) -> Option { + let mut value = 0u16; + for &b in bytes { + if !b.is_ascii_digit() { + return None; + } + value = value.saturating_mul(10).saturating_add((b - b'0') as u16); + } + Some(value) + } + + fn apply_sgr_sequence(&mut self) { + let params = self.csi_params_without_prefix(); + if params.is_empty() { + self.apply_sgr_code(0); + return; + } + for code in params { + self.apply_sgr_code(code); + } + } + + fn reset_ansi_parser(&mut self) { + self.ansi_esc_pending = false; + self.ansi_csi_mode = false; + self.ansi_osc_mode = false; + self.ansi_dcs_mode = false; + self.ansi_osc_esc_pending = false; + self.ansi_dcs_esc_pending = false; + self.ansi_seq_len = 0; + } + + fn csi_private_prefix(&self) -> Option { + if self.ansi_seq_len == 0 { + return None; + } + match self.ansi_seq[0] { + b'?' | b'>' | b'!' => Some(self.ansi_seq[0]), + _ => None, + } + } + + fn csi_params_from(&self, start_at: usize) -> Vec { + let mut params = Vec::new(); + if start_at > self.ansi_seq_len { + return params; + } + let mut start = start_at; + let mut i = start_at; + while i <= self.ansi_seq_len { + if i == self.ansi_seq_len || self.ansi_seq[i] == b';' { + if i == start { + params.push(0); + } else if let Some(v) = Self::parse_ascii_u16(&self.ansi_seq[start..i]) { + params.push(v); + } + start = i + 1; + } + i += 1; + } + params + } + + fn csi_params_without_prefix(&self) -> Vec { + let start = if self.csi_private_prefix().is_some() { 1 } else { 0 }; + self.csi_params_from(start) + } + + fn erase_cell(&mut self, col: u32, row: u32) { + self.set_cell( + col, + row, + Cell { + ch: b' ', + fg: self.fg, + bg: self.bg, + }, + ); + } + + fn erase_line_range(&mut self, row: u32, start_col: u32, end_col: u32) { + if row >= self.max_rows { + return; + } + let end = core::cmp::min(end_col, self.max_cols); + let mut col = core::cmp::min(start_col, end); + while col < end { + self.erase_cell(col, row); + col += 1; + } + } + + fn erase_screen_range(&mut self, start_row: u32, start_col: u32, end_row: u32, end_col: u32) { + if self.max_rows == 0 || self.max_cols == 0 { + return; + } + let mut row = start_row; + while row < end_row && row < self.max_rows { + let col_begin = if row == start_row { start_col } else { 0 }; + let col_end = if row + 1 == end_row { + core::cmp::min(end_col, self.max_cols) + } else { + self.max_cols + }; + self.erase_line_range(row, col_begin, col_end); + row += 1; + } + } + + fn insert_blank_chars(&mut self, mut count: u32) { + if self.row >= self.max_rows || self.col >= self.max_cols || count == 0 { + return; + } + count = core::cmp::min(count, self.max_cols - self.col); + let row = self.row; + let start = self.col; + let end = self.max_cols; + let mut c = end; + while c > start + count { + let src = c - count - 1; + let dst = c - 1; + let moved = self.get_cell(src, row); + self.set_cell(dst, row, moved); + c -= 1; + } + self.erase_line_range(row, start, start + count); + } + + fn delete_chars(&mut self, mut count: u32) { + if self.row >= self.max_rows || self.col >= self.max_cols || count == 0 { + return; + } + count = core::cmp::min(count, self.max_cols - self.col); + let row = self.row; + let start = self.col; + let end = self.max_cols; + let mut c = start; + while c + count < end { + let src_cell = self.get_cell(c + count, row); + self.set_cell(c, row, src_cell); + c += 1; + } + self.erase_line_range(row, end - count, end); + } + + fn insert_blank_lines(&mut self, mut count: u32) { + self.normalize_scroll_region(); + if self.row >= self.max_rows || count == 0 || self.row < self.scroll_top || self.row > self.scroll_bottom { + return; + } + let start = self.row; + let end = self.scroll_bottom + 1; + count = core::cmp::min(count, end - start); + let mut r = end; + while r > start + count { + let src = r - count - 1; + let dst = r - 1; + for col in 0..self.max_cols { + let c = self.get_cell(col, src); + self.set_cell(col, dst, c); + } + r -= 1; + } + for rr in start..start + count { + self.erase_line_range(rr, 0, self.max_cols); + } + } + + fn delete_lines(&mut self, mut count: u32) { + self.normalize_scroll_region(); + if self.row >= self.max_rows || count == 0 || self.row < self.scroll_top || self.row > self.scroll_bottom { + return; + } + count = core::cmp::min(count, self.scroll_bottom - self.row + 1); + let start = self.row; + let end = self.scroll_bottom + 1; + let mut r = start; + while r + count < end { + let src = r + count; + for col in 0..self.max_cols { + let c = self.get_cell(col, src); + self.set_cell(col, r, c); + } + r += 1; + } + for rr in end - count..end { + self.erase_line_range(rr, 0, self.max_cols); + } + } + + fn erase_chars(&mut self, mut count: u32) { + if self.row >= self.max_rows || self.col >= self.max_cols || count == 0 { + return; + } + count = core::cmp::min(count, self.max_cols - self.col); + self.erase_line_range(self.row, self.col, self.col + count); + } + + fn read_cell(&self, col: u32, row: u32) -> u8 { + self.get_cell(col, row).ch + } + + fn handle_csi_sequence(&mut self, final_byte: u8) { + let private_prefix = self.csi_private_prefix(); + let params = self.csi_params_without_prefix(); + let p = |idx: usize, default: u16| -> u16 { + let v = params.get(idx).copied().unwrap_or(default); + if v == 0 { + default + } else { + v + } + }; + + let mut apply_mode = |mode: u16, enabled: bool| { + match (private_prefix, mode) { + (Some(b'?'), 25) => { + self.cursor_visible = enabled; + } + (Some(b'?'), 47) | (Some(b'?'), 1047) | (Some(b'?'), 1049) => { + if enabled { + self.enter_alt_screen(); + } else { + self.leave_alt_screen(); + } + } + (None, 4) => { + self.insert_mode = enabled; + } + _ => {} + } + }; + + match final_byte { + b'A' => { + let n = p(0, 1) as u32; + self.row = self.row.saturating_sub(n); + } + b'B' => { + let n = p(0, 1) as u32; + self.row = core::cmp::min(self.row.saturating_add(n), self.max_rows.saturating_sub(1)); + } + b'C' => { + let n = p(0, 1) as u32; + self.col = core::cmp::min(self.col.saturating_add(n), self.max_cols.saturating_sub(1)); + } + b'D' => { + let n = p(0, 1) as u32; + self.col = self.col.saturating_sub(n); + } + b'E' => { + let n = p(0, 1) as u32; + self.row = core::cmp::min( + self.row.saturating_add(n), + self.max_rows.saturating_sub(1), + ); + self.col = 0; + } + b'F' => { + let n = p(0, 1) as u32; + self.row = self.row.saturating_sub(n); + self.col = 0; + } + b'H' | b'f' => { + let row = p(0, 1).saturating_sub(1) as u32; + let col = p(1, 1).saturating_sub(1) as u32; + self.row = core::cmp::min(row, self.max_rows.saturating_sub(1)); + self.col = core::cmp::min(col, self.max_cols.saturating_sub(1)); + } + b'G' => { + let col = p(0, 1).saturating_sub(1) as u32; + self.col = core::cmp::min(col, self.max_cols.saturating_sub(1)); + } + b'd' => { + let row = p(0, 1).saturating_sub(1) as u32; + self.row = core::cmp::min(row, self.max_rows.saturating_sub(1)); + } + b'J' => { + let mode = params.get(0).copied().unwrap_or(0); + match mode { + 0 => { + self.erase_screen_range( + self.row, + self.col, + self.max_rows, + self.max_cols, + ); + } + 1 => { + self.erase_screen_range(0, 0, self.row + 1, self.col + 1); + } + 2 => { + self.erase_screen_range(0, 0, self.max_rows, self.max_cols); + self.col = 0; + self.row = 0; + } + _ => {} + } + } + b'K' => { + let mode = params.get(0).copied().unwrap_or(0); + match mode { + 0 => self.erase_line_range(self.row, self.col, self.max_cols), + 1 => self.erase_line_range(self.row, 0, self.col + 1), + 2 => self.erase_line_range(self.row, 0, self.max_cols), + _ => {} + } + } + b's' => { + self.ansi_saved_col = self.col; + self.ansi_saved_row = self.row; + } + b'u' => { + self.col = core::cmp::min(self.ansi_saved_col, self.max_cols.saturating_sub(1)); + self.row = core::cmp::min(self.ansi_saved_row, self.max_rows.saturating_sub(1)); + } + b'@' => { + let n = p(0, 1) as u32; + self.insert_blank_chars(n); + } + b'P' => { + let n = p(0, 1) as u32; + self.delete_chars(n); + } + b'L' => { + let n = p(0, 1) as u32; + self.insert_blank_lines(n); + } + b'M' => { + let n = p(0, 1) as u32; + self.delete_lines(n); + } + b'X' => { + let n = p(0, 1) as u32; + self.erase_chars(n); + } + b'r' => { + let top = p(0, 1).saturating_sub(1) as u32; + let bottom = p(1, self.max_rows as u16).saturating_sub(1) as u32; + self.set_scroll_region(top, bottom); + } + b'S' => { + let n = p(0, 1) as u32; + self.scroll_region_up(self.scroll_top, self.scroll_bottom, n); + } + b'T' => { + let n = p(0, 1) as u32; + self.scroll_region_down(self.scroll_top, self.scroll_bottom, n); + } + b'b' => { + let n = p(0, 1) as usize; + for _ in 0..n { + self.write_byte(self.last_printable); + } + } + b'h' => { + if params.is_empty() { + apply_mode(0, true); + } else { + for &mode in ¶ms { + apply_mode(mode, true); + } + } + } + b'l' => { + if params.is_empty() { + apply_mode(0, false); + } else { + for &mode in ¶ms { + apply_mode(mode, false); + } + } + } + _ => {} + } + } + + fn write_output_byte(&mut self, byte: u8) { + if self.ansi_dcs_mode { + if byte == 0x07 { + self.reset_ansi_parser(); + return; + } + if self.ansi_dcs_esc_pending { + self.ansi_dcs_esc_pending = false; + if byte == b'\\' { + self.reset_ansi_parser(); + } + return; + } + if byte == 0x1B { + self.ansi_dcs_esc_pending = true; + } + return; + } + + if self.ansi_osc_mode { + if byte == 0x07 { + self.reset_ansi_parser(); + return; + } + if self.ansi_osc_esc_pending { + self.ansi_osc_esc_pending = false; + if byte == b'\\' { + self.reset_ansi_parser(); + } + return; + } + if byte == 0x1B { + self.ansi_osc_esc_pending = true; + } + return; + } + + if self.ansi_esc_pending { + self.ansi_esc_pending = false; + if byte == b'[' { + self.ansi_csi_mode = true; + self.ansi_seq_len = 0; + } else if byte == b']' { + self.ansi_osc_mode = true; + self.ansi_osc_esc_pending = false; + } else if byte == b'P' { + self.ansi_dcs_mode = true; + self.ansi_dcs_esc_pending = false; + } else if byte == b'7' { + self.ansi_saved_col = self.col; + self.ansi_saved_row = self.row; + } else if byte == b'8' { + self.col = core::cmp::min(self.ansi_saved_col, self.max_cols.saturating_sub(1)); + self.row = core::cmp::min(self.ansi_saved_row, self.max_rows.saturating_sub(1)); + } else if byte == b'D' { + self.index(); + } else if byte == b'E' { + self.new_line(); + self.col = 0; + } else if byte == b'M' { + self.reverse_index(); + } else if byte == b'c' { + self.clear_screen(); + self.fg = DEFAULT_FG; + self.bg = DEFAULT_BG; + self.cursor_visible = true; + self.alt_screen = None; + self.last_printable = b' '; + } else if byte == 0x1B { + self.ansi_esc_pending = true; + } + return; + } + + if self.ansi_csi_mode { + if byte == b'm' { + self.apply_sgr_sequence(); + self.reset_ansi_parser(); + return; + } + + if (0x20..=0x3F).contains(&byte) { + if self.ansi_seq_len < self.ansi_seq.len() { + self.ansi_seq[self.ansi_seq_len] = byte; + self.ansi_seq_len += 1; + } else { + self.reset_ansi_parser(); + } + return; + } + + if (0x40..=0x7E).contains(&byte) { + self.handle_csi_sequence(byte); + self.reset_ansi_parser(); + return; + } + + self.reset_ansi_parser(); + return; + } + + if byte == 0x1B { + self.ansi_esc_pending = true; + return; + } + + self.write_byte(byte); + } + + pub fn write_str(&mut self, s: &str) { + self.hide_cursor_overlay(); + for b in s.bytes() { + self.write_output_byte(b); + } + self.show_cursor_overlay(); + } + + fn write_bytes(&mut self, bytes: &[u8]) { + self.hide_cursor_overlay(); + for &b in bytes { + self.write_output_byte(b); + } + self.show_cursor_overlay(); + } + + fn write_num(&mut self, mut n: u64) { + if n == 0 { + self.write_byte(b'0'); + return; + } + let mut buf = [0u8; 20]; + let mut i = 20; + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + for &b in &buf[i..] { + self.write_byte(b); + } + } + + pub fn prompt(&mut self) { + let mut cwd_buf = [0u8; 256]; + let cwd = fs::getcwd(&mut cwd_buf).unwrap_or("/"); + self.fg = 0x00FF_88FF; // 紫 + self.write_str(cwd); + self.write_str(" mochi> "); + self.fg = 0x00FF_FFFF; // シアン + } + + pub fn size_chars(&self) -> (u16, u16) { + (self.max_cols as u16, self.max_rows as u16) + } + + /// 子プロセスのIPC出力を受け取りながら終了を待つ + fn drain_child_output(&mut self, pid: u64) { + let mut buf = Box::new([0u8; IPC_MSG_MAX]); + loop { + let mut wrote = false; + if self.drain_pending_ipc_messages(&mut *buf) { + wrote = true; + } + loop { + let (_, len2) = ipc::ipc_recv(&mut *buf); + if len2 == 0 || len2 as usize > buf.len() { + break; + } + self.write_bytes(&buf[..len2 as usize]); + wrote = true; + } + if wrote { + self.flush(); + } + let child_finished = match task::wait_nonblocking_status(pid as i64) { + task::WaitNonblockingStatus::Exited(_) => true, + task::WaitNonblockingStatus::Running => false, + task::WaitNonblockingStatus::NoChild => true, + task::WaitNonblockingStatus::Error(_) => true, + }; + if child_finished { + break; + } + + // メッセージが届くまでスリープして待機(ビジーウェイトしない) + let (_, len) = ipc::ipc_recv_wait(&mut *buf); + if len > 0 && len as usize <= buf.len() { + self.write_bytes(&buf[..len as usize]); + // 続きのメッセージをノンブロッキングで掃き出す + loop { + let (_, len2) = ipc::ipc_recv(&mut *buf); + if len2 == 0 || len2 as usize > buf.len() { + break; + } + self.write_bytes(&buf[..len2 as usize]); + } + // バッチ分まとめてフラッシュ + self.flush(); + } + // 子プロセスが終了していれば抜ける(exit 通知で起床した場合もここで検知) + let child_finished = match task::wait_nonblocking_status(pid as i64) { + task::WaitNonblockingStatus::Exited(_) => true, + task::WaitNonblockingStatus::Running => false, + task::WaitNonblockingStatus::NoChild => true, + task::WaitNonblockingStatus::Error(_) => true, + }; + if child_finished { + break; + } + } + // 終了後に残ったメッセージを念のため掃き出す + let mut wrote = false; + if self.drain_pending_ipc_messages(&mut *buf) { + wrote = true; + } + loop { + let (_, len) = ipc::ipc_recv(&mut *buf); + if len == 0 || len as usize > buf.len() { + break; + } + self.write_bytes(&buf[..len as usize]); + wrote = true; + } + if wrote { + self.flush(); + } + } + + // ================================================================ + // ネイティブビルトインコマンド群 + // BusyBox を介さずシェル内で直接実装し、ELF ロード/IPC コマンド実行の + // オーバーヘッドを回避する。 + // ================================================================ + + /// 共通: ファイル内容を取得 + fn load_file_bytes(&self, path: &str, limit: usize) -> Option> { + let abs = resolve_path(path); + read_file_from_fs(&abs, limit) + } + + /// 共通: ファイル内容をテキストとして書き出す + fn write_file_text(&mut self, data: &[u8]) { + if let Ok(text) = core::str::from_utf8(data) { + self.write_str(text); + if !text.ends_with('\n') { + self.write_byte(b'\n'); + } + } else { + // バイナリは可読文字のみ表示 + for &b in data { + if b == b'\n' || b == b'\t' || (0x20..0x7F).contains(&b) { + self.write_byte(b); + } else { + self.write_byte(b'.'); + } + } + self.write_byte(b'\n'); + } + } + + /// ls [-l] [path...] + fn builtin_ls(&mut self, args: &[String]) { + let mut long = false; + let mut targets: Vec<&str> = Vec::new(); + for a in args { + if a == "-l" || a == "-la" || a == "-al" { + long = true; + } else if a.starts_with('-') { + // 未知オプションは無視 + } else { + targets.push(a.as_str()); + } + } + if targets.is_empty() { + targets.push("."); + } + + let multi = targets.len() > 1; + for (i, t) in targets.iter().enumerate() { + if multi { + if i > 0 { + self.write_byte(b'\n'); + } + self.write_str(t); + self.write_str(":\n"); + } + let abs = resolve_path(t); + + // まず stat してファイル/ディレクトリ判定 + match stat_via_fs_service(&abs) { + Some((mode, size)) => { + let is_dir = (mode & S_IFMT) == S_IFDIR; + if !is_dir { + // 単一ファイル指定 + if long { + self.ls_print_long_line(t, mode, size); + } else { + self.write_str(t); + self.write_byte(b'\n'); + } + continue; + } + } + None => { + self.write_str("ls: cannot access '"); + self.write_str(t); + self.write_str("': No such file or directory\n"); + continue; + } + } + + // ディレクトリを列挙 + let entries = match readdir_all_via_fs_service(&abs) { + Some(e) => e, + None => { + self.write_str("ls: cannot read directory '"); + self.write_str(t); + self.write_str("'\n"); + continue; + } + }; + + // ソート(名前順) + let mut sorted = entries; + sorted.sort(); + + for name in &sorted { + let child_path = if abs == "/" { + format!("/{}", name) + } else { + format!("{}/{}", abs, name) + }; + if long { + match stat_via_fs_service(&child_path) { + Some((mode, size)) => { + self.ls_print_long_line(name, mode, size); + } + None => { + self.write_str("? "); + self.write_str(name); + self.write_byte(b'\n'); + } + } + } else { + // 色分け: ディレクトリは青、その他は白 + let mode = stat_via_fs_service(&child_path).map(|(m, _)| m).unwrap_or(0); + let is_dir = (mode & S_IFMT) == S_IFDIR; + if is_dir { + self.fg = 0x005599FF; + } + self.write_str(name); + if is_dir { + self.write_byte(b'/'); + self.fg = DEFAULT_FG; + } + self.write_byte(b'\n'); + } + } + } + } + + fn ls_print_long_line(&mut self, name: &str, mode: u64, size: u64) { + let is_dir = (mode & S_IFMT) == S_IFDIR; + // type bit + self.write_byte(if is_dir { b'd' } else { b'-' }); + // rwx for user/group/other + let perm = mode & 0o777; + let bits = [ + (perm >> 8) & 1, (perm >> 7) & 1, (perm >> 6) & 1, + (perm >> 5) & 1, (perm >> 4) & 1, (perm >> 3) & 1, + (perm >> 2) & 1, (perm >> 1) & 1, perm & 1, + ]; + let chars = [b'r', b'w', b'x', b'r', b'w', b'x', b'r', b'w', b'x']; + for i in 0..9 { + self.write_byte(if bits[i] != 0 { chars[i] } else { b'-' }); + } + self.write_byte(b' '); + // size(右詰め 10 桁) + let size_str = format!("{}", size); + let pad = 10usize.saturating_sub(size_str.len()); + for _ in 0..pad { + self.write_byte(b' '); + } + self.write_str(&size_str); + self.write_byte(b' '); + // name (ディレクトリは色付け) + if is_dir { + self.fg = 0x005599FF; + } + self.write_str(name); + if is_dir { + self.write_byte(b'/'); + self.fg = DEFAULT_FG; + } + self.write_byte(b'\n'); + } + + /// cat file... + fn builtin_cat(&mut self, args: &[String]) { + if args.is_empty() { + self.write_str("usage: cat ...\n"); + return; + } + for arg in args { + match self.load_file_bytes(arg, 1024 * 1024) { + Some(data) => self.write_file_text(&data), + None => { + self.write_str("cat: "); + self.write_str(arg); + self.write_str(": No such file\n"); + } + } + } + } + + /// echo [-n] args... + fn builtin_echo(&mut self, args: &[String]) { + let mut trailing_newline = true; + let mut start = 0; + if let Some(first) = args.first() { + if first == "-n" { + trailing_newline = false; + start = 1; + } + } + for (i, a) in args[start..].iter().enumerate() { + if i > 0 { + self.write_byte(b' '); + } + self.write_str(a); + } + if trailing_newline { + self.write_byte(b'\n'); + } + } + + /// pwd + fn builtin_pwd(&mut self) { + let mut buf = [0u8; 256]; + let cwd = fs::getcwd(&mut buf).unwrap_or("/"); + self.write_str(cwd); + self.write_byte(b'\n'); + } + + /// stat file... + fn builtin_stat(&mut self, args: &[String]) { + if args.is_empty() { + self.write_str("usage: stat ...\n"); + return; + } + for arg in args { + let abs = resolve_path(arg); + match stat_via_fs_service(&abs) { + Some((mode, size)) => { + let is_dir = (mode & S_IFMT) == S_IFDIR; + self.write_str(" File: "); + self.write_str(arg); + self.write_byte(b'\n'); + self.write_str(" Type: "); + self.write_str(if is_dir { "directory" } else { "regular file" }); + self.write_byte(b'\n'); + self.write_str(" Size: "); + self.write_str(&format!("{}", size)); + self.write_byte(b'\n'); + self.write_str(" Mode: "); + self.write_str(&format!("{:o}", mode & 0o7777)); + self.write_byte(b'\n'); + } + None => { + self.write_str("stat: cannot stat '"); + self.write_str(arg); + self.write_str("': No such file or directory\n"); + } + } + } + } + + /// head [-n N] file... + fn builtin_head(&mut self, args: &[String]) { + let (n, files) = Self::parse_nflag(args, 10); + if files.is_empty() { + self.write_str("usage: head [-n N] ...\n"); + return; + } + let multi = files.len() > 1; + for (i, arg) in files.iter().enumerate() { + if multi { + if i > 0 { + self.write_byte(b'\n'); + } + self.write_str("==> "); + self.write_str(arg); + self.write_str(" <==\n"); + } + match self.load_file_bytes(arg, 1024 * 1024) { + Some(data) => { + if let Ok(text) = core::str::from_utf8(&data) { + for (idx, line) in text.lines().enumerate() { + if idx >= n { + break; + } + self.write_str(line); + self.write_byte(b'\n'); + } + } else { + self.write_str("head: binary file\n"); + } + } + None => { + self.write_str("head: "); + self.write_str(arg); + self.write_str(": No such file\n"); + } + } + } + } + + /// tail [-n N] file... + fn builtin_tail(&mut self, args: &[String]) { + let (n, files) = Self::parse_nflag(args, 10); + if files.is_empty() { + self.write_str("usage: tail [-n N] ...\n"); + return; + } + let multi = files.len() > 1; + for (i, arg) in files.iter().enumerate() { + if multi { + if i > 0 { + self.write_byte(b'\n'); + } + self.write_str("==> "); + self.write_str(arg); + self.write_str(" <==\n"); + } + match self.load_file_bytes(arg, 1024 * 1024) { + Some(data) => { + if let Ok(text) = core::str::from_utf8(&data) { + let lines: Vec<&str> = text.lines().collect(); + let start = lines.len().saturating_sub(n); + for line in &lines[start..] { + self.write_str(line); + self.write_byte(b'\n'); + } + } else { + self.write_str("tail: binary file\n"); + } + } + None => { + self.write_str("tail: "); + self.write_str(arg); + self.write_str(": No such file\n"); + } + } + } + } + + /// -n フラグを処理して (count, files) を返す + fn parse_nflag(args: &[String], default: usize) -> (usize, Vec) { + let mut n = default; + let mut files: Vec = Vec::new(); + let mut i = 0; + while i < args.len() { + let a = &args[i]; + if a == "-n" { + if i + 1 < args.len() { + if let Ok(v) = args[i + 1].parse::() { + n = v; + } + i += 2; + continue; + } + } else if let Some(rest) = a.strip_prefix("-n") { + if let Ok(v) = rest.parse::() { + n = v; + i += 1; + continue; + } + } + files.push(a.clone()); + i += 1; + } + (n, files) + } + + /// wc [-lwc] file... + fn builtin_wc(&mut self, args: &[String]) { + let mut show_lines = false; + let mut show_words = false; + let mut show_bytes = false; + let mut files: Vec<&str> = Vec::new(); + for a in args { + if let Some(flags) = a.strip_prefix('-') { + for ch in flags.chars() { + match ch { + 'l' => show_lines = true, + 'w' => show_words = true, + 'c' => show_bytes = true, + _ => {} + } + } + } else { + files.push(a.as_str()); + } + } + if !show_lines && !show_words && !show_bytes { + show_lines = true; + show_words = true; + show_bytes = true; + } + if files.is_empty() { + self.write_str("usage: wc [-lwc] ...\n"); + return; + } + for arg in &files { + match self.load_file_bytes(arg, 1024 * 1024) { + Some(data) => { + let bytes = data.len(); + let text = core::str::from_utf8(&data).unwrap_or(""); + let lines = text.lines().count(); + let words = text.split_whitespace().count(); + let mut first = true; + if show_lines { + self.write_str(&format!("{:>7}", lines)); + first = false; + } + if show_words { + if !first { self.write_byte(b' '); } + self.write_str(&format!("{:>7}", words)); + first = false; + } + if show_bytes { + if !first { self.write_byte(b' '); } + self.write_str(&format!("{:>7}", bytes)); + } + self.write_byte(b' '); + self.write_str(arg); + self.write_byte(b'\n'); + } + None => { + self.write_str("wc: "); + self.write_str(arg); + self.write_str(": No such file\n"); + } + } + } + } + + /// grep pattern file... + fn builtin_grep(&mut self, args: &[String]) { + if args.len() < 2 { + self.write_str("usage: grep ...\n"); + return; + } + let pattern = args[0].as_str(); + let files = &args[1..]; + let multi = files.len() > 1; + for arg in files { + match self.load_file_bytes(arg, 1024 * 1024) { + Some(data) => { + if let Ok(text) = core::str::from_utf8(&data) { + for line in text.lines() { + if line.contains(pattern) { + if multi { + self.write_str(arg); + self.write_byte(b':'); + } + self.write_str(line); + self.write_byte(b'\n'); + } + } + } + } + None => { + self.write_str("grep: "); + self.write_str(arg); + self.write_str(": No such file\n"); + } + } + } + } + + /// which cmd... + fn builtin_which(&mut self, args: &[String]) { + if args.is_empty() { + self.write_str("usage: which ...\n"); + return; + } + for arg in args { + let name = arg.clone(); + if let Some(path) = self.find_in_path(&name) { + self.write_str(&path); + self.write_byte(b'\n'); + } else { + self.write_str(arg); + self.write_str(": not found\n"); + } + } + } + + /// about + fn builtin_about(&mut self, args: &[String]) { + if args.len() != 1 { + self.write_str("usage: about \n"); + return; + } + let raw_name = args[0].trim(); + if raw_name.is_empty() { + self.write_str("usage: about \n"); + return; + } + let app_name = raw_name.strip_suffix(".app").unwrap_or(raw_name); + let about_path = format!("/applications/{}.app/about.toml", app_name); + match self.load_file_bytes(&about_path, 16 * 1024) { + Some(data) => { + if let Ok(text) = core::str::from_utf8(&data) { + self.write_str(text); + if !text.ends_with('\n') { + self.write_byte(b'\n'); + } + } else { + self.write_str("about: about.toml is not valid UTF-8\n"); + } + } + None => { + self.write_str("about: app not found: "); + self.write_str(app_name); + self.write_byte(b'\n'); + } + } + } + + /// env + fn builtin_env(&mut self) { + for (k, v) in self.env.clone().iter() { + self.write_str(k); + self.write_byte(b'='); + self.write_str(v); + self.write_byte(b'\n'); + } + } + + /// basename path + fn builtin_basename(&mut self, args: &[String]) { + if args.is_empty() { + self.write_str("usage: basename \n"); + return; + } + let p = args[0].trim_end_matches('/'); + let base = match p.rfind('/') { + Some(i) => &p[i + 1..], + None => p, + }; + let base = if base.is_empty() { "/" } else { base }; + self.write_str(base); + self.write_byte(b'\n'); + } + + /// dirname path + fn builtin_dirname(&mut self, args: &[String]) { + if args.is_empty() { + self.write_str("usage: dirname \n"); + return; + } + let p = args[0].trim_end_matches('/'); + let dir = match p.rfind('/') { + Some(0) => "/", + Some(i) => &p[..i], + None => ".", + }; + self.write_str(dir); + self.write_byte(b'\n'); + } + + fn parse_command_line(line: &str) -> Vec { + let mut tokens = Vec::new(); + let mut current = String::new(); + let mut quote: Option = None; + + for b in line.bytes() { + match quote { + Some(q) => { + if b == q { + quote = None; + } else { + current.push(b as char); + } + } + None => match b { + b'"' | b'\'' => quote = Some(b), + b' ' | b'\t' => { + if !current.is_empty() { + tokens.push(current); + current = String::new(); + } + } + _ => current.push(b as char), + }, + } + } + + if !current.is_empty() { + tokens.push(current); + } + + tokens + } + + pub fn handle_line(&mut self) { + // バッファから文字列をコピーして借用を解放 + let mut tmp = [0u8; 256]; + let len = self.input_len; + tmp[..len].copy_from_slice(&self.input_buf[..len]); + let cmd_str: &str = core::str::from_utf8(&tmp[..len]).unwrap_or("").trim(); + + let mut cmd_buf = [0u8; 256]; + let cmd_bytes = cmd_str.as_bytes(); + cmd_buf[..cmd_bytes.len()].copy_from_slice(cmd_bytes); + let cmd_len = cmd_bytes.len(); + + self.write_byte(b'\n'); + self.input_len = 0; + + let cmd = core::str::from_utf8(&cmd_buf[..cmd_len]).unwrap_or(""); + if cmd.is_empty() { + return; + } + + let tokens = Self::parse_command_line(cmd); + if tokens.is_empty() { + return; + } + + let cmd_name = tokens[0].as_str(); + let args = &tokens[1..]; + let joined_args = if args.is_empty() { + String::new() + } else { + args.join(" ") + }; + + match cmd_name { + "help" => { + self.write_str("mochiOS shell builtins:\n"); + self.write_str(" ls [-l] [path] cat ... echo [-n] args\n"); + self.write_str(" pwd cd stat \n"); + self.write_str(" head [-n N] tail [-n N] wc [-lwc] \n"); + self.write_str(" grep which env\n"); + self.write_str(" basename

dirname

export K=V\n"); + self.write_str(" clear version about \n"); + self.write_str(" true / false\n"); + self.write_str("External binaries in $PATH are executed directly (no BusyBox).\n"); + } + "clear" => { + self.clear_screen(); + } + "version" => { + if let Some(data) = read_file_from_fs("/system/about.txt", 4096) { + if let Ok(text) = core::str::from_utf8(&data) { + self.write_str(text); + if !text.ends_with('\n') { + self.write_byte(b'\n'); + } + } else { + self.write_str("Error: /system/about.txt is not valid UTF-8\n"); + } + } else { + self.write_str("mochiOS (about.txt not found)\n"); + } + } + "about" => self.builtin_about(args), + "cd" => { + let target = args.first().map(|s| s.as_str()).unwrap_or("/"); + let ret = fs::chdir(target); + if ret != 0 { + self.write_str("cd: no such directory: "); + self.write_str(target); + self.write_byte(b'\n'); + } + } + "export" => { + if let Some(eq) = joined_args.find('=') { + let key = joined_args[..eq].trim().to_string(); + let val = joined_args[eq + 1..].trim().to_string(); + self.set_env(&key, &val); + } else { + self.write_str("usage: export VAR=VALUE\n"); + } + } + "ls" => self.builtin_ls(args), + "cat" => self.builtin_cat(args), + "echo" => self.builtin_echo(args), + "pwd" => self.builtin_pwd(), + "stat" => self.builtin_stat(args), + "head" => self.builtin_head(args), + "tail" => self.builtin_tail(args), + "wc" => self.builtin_wc(args), + "grep" => self.builtin_grep(args), + "which" => self.builtin_which(args), + "env" => self.builtin_env(), + "basename" => self.builtin_basename(args), + "dirname" => self.builtin_dirname(args), + "true" => {} + "false" => {} + _ => { + // PATH から直接実行(BusyBox フォールバック廃止) + match self.find_in_path(cmd_name).map(|s| s.to_string()) { + Some(bin_path) => { + let arg_parts: Vec<&str> = args.iter().map(|s| s.as_str()).collect(); + match exec_via_fs_service(&bin_path, &arg_parts) { + Ok(pid) => { + self.drain_child_output(pid); + } + Err(_) => { + self.write_str("exec failed: "); + self.write_str(&bin_path); + self.write_byte(b'\n'); + } + } + } + None => { + self.write_str("command not found: "); + self.write_str(cmd_name); + self.write_byte(b'\n'); + } + } + } + } + } +} diff --git a/src/services/shell/src/keyboard.rs b/src/services/shell/src/keyboard.rs new file mode 100644 index 0000000..b4be9e2 --- /dev/null +++ b/src/services/shell/src/keyboard.rs @@ -0,0 +1,103 @@ +//! PS/2 キーボードドライバ (ユーザー空間) +//! +//! カーネルから rawスキャンコード (セット1) を受け取り +//! ASCII 文字に変換する。Shift / CapsLock の状態を保持する。 + +use swiftlib::keyboard; + +/// スキャンコードセット1 → ASCII(通常) +#[rustfmt::skip] +const MAP_NORMAL: [u8; 128] = [ + 0, 0x1B, b'1', b'2', b'3', b'4', b'5', b'6', // 0x00–0x07 + b'7', b'8', b'9', b'0', b'-', b'=', 0x08, b'\t', // 0x08–0x0F + b'q', b'w', b'e', b'r', b't', b'y', b'u', b'i', // 0x10–0x17 + b'o', b'p', b'[', b']', b'\n', 0, b'a', b's', // 0x18–0x1F + b'd', b'f', b'g', b'h', b'j', b'k', b'l', b';', // 0x20–0x27 + b':', b'`', 0, b'\\',b'z', b'x', b'c', b'v', // 0x28–0x2F + b'b', b'n', b'm', b',', b'.', b'/', 0, b'*', // 0x30–0x37 + 0, b' ', 0, 0, 0, 0, 0, 0, // 0x38–0x3F + 0, 0, 0, 0, 0, 0, 0, b'7', // 0x40–0x47 + b'8', b'9', b'-', b'4', b'5', b'6', b'+', b'1', // 0x48–0x4F + b'2', b'3', b'0', b'.', 0, 0, 0, 0, // 0x50–0x57 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x58–0x5F + 0, 0, 0, 0, 0, 0, 0, 0, // 0x60–0x67 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x68–0x6F + 0, 0, 0, 0, 0, 0, 0, 0, // 0x70–0x77 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x78–0x7F +]; + +/// スキャンコードセット1 → ASCII(Shift押下時) +#[rustfmt::skip] +const MAP_SHIFT: [u8; 128] = [ + 0, 0x1B, b'!', b'@', b'#', b'$', b'%', b'^', // 0x00–0x07 + b'&', b'*', b'(', b')', b'_', b'+', 0x08, b'\t', // 0x08–0x0F + b'Q', b'W', b'E', b'R', b'T', b'Y', b'U', b'I', // 0x10–0x17 + b'O', b'P', b'{', b'}', b'\n', 0, b'A', b'S', // 0x18–0x1F + b'D', b'F', b'G', b'H', b'J', b'K', b'L', b':', // 0x20–0x27 + b'*', b'~', 0, b'|', b'Z', b'X', b'C', b'V', // 0x28–0x2F + b'B', b'N', b'M', b'<', b'>', b'?', 0, b'*', // 0x30–0x37 + 0, b' ', 0, 0, 0, 0, 0, 0, // 0x38–0x3F + 0, 0, 0, 0, 0, 0, 0, b'7', // 0x40–0x47 + b'8', b'9', b'-', b'4', b'5', b'6', b'+', b'1', // 0x48–0x4F + b'2', b'3', b'0', b'.', 0, 0, 0, 0, // 0x50–0x57 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x58–0x5F + 0, 0, 0, 0, 0, 0, 0, 0, // 0x60–0x67 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x68–0x6F + 0, 0, 0, 0, 0, 0, 0, 0, // 0x70–0x77 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x78–0x7F +]; + +// スキャンコード定数 +const SC_LSHIFT: u8 = 0x2A; +const SC_RSHIFT: u8 = 0x36; +const SC_CAPSLOCK: u8 = 0x3A; +const SC_RELEASE: u8 = 0x80; // リリースフラグ (bit 7) + +/// PS/2 キーボードドライバ +pub struct Ps2Keyboard { + shift: bool, + caps: bool, +} + +impl Ps2Keyboard { + pub const fn new() -> Self { + Ps2Keyboard { shift: false, caps: false } + } + + /// カーネルバッファからスキャンコードを取得して ASCII に変換する。 + /// キー入力がなければ None を返す。 + pub fn read(&mut self) -> Option { + loop { + let sc = keyboard::read_scancode()?; + + // キーリリース + if sc & SC_RELEASE != 0 { + let make = sc & !SC_RELEASE; + if make == SC_LSHIFT || make == SC_RSHIFT { + self.shift = false; + } + continue; // リリースイベントは文字を生成しない + } + + // 修飾キー + match sc { + SC_LSHIFT | SC_RSHIFT => { self.shift = true; continue; } + SC_CAPSLOCK => { self.caps = !self.caps; continue; } + _ => {} + } + + let idx = sc as usize; + if idx >= 128 { + continue; + } + + // Shift と CapsLock を組み合わせて文字を決定 + let use_shift = self.shift ^ (self.caps && MAP_NORMAL[idx].is_ascii_alphabetic()); + let ch = if use_shift { MAP_SHIFT[idx] } else { MAP_NORMAL[idx] }; + + if ch != 0 { + return Some(ch); + } + } + } +} diff --git a/src/services/shell/src/main.rs b/src/services/shell/src/main.rs new file mode 100644 index 0000000..c2ce614 --- /dev/null +++ b/src/services/shell/src/main.rs @@ -0,0 +1,104 @@ +mod char; +mod keyboard; + +use char::{Font, Terminal}; +use keyboard::Ps2Keyboard; +use swiftlib::{time, vga}; + +#[repr(C)] +struct WinSize { + ws_row: u16, + ws_col: u16, + ws_xpixel: u16, + ws_ypixel: u16, +} + +const TIOCSWINSZ: u64 = 0x5414; + +fn main() { + println!("[SHELL] Service Started."); + + let info = match vga::get_info() { + Some(i) => i, + None => { + println!("[SHELL] Failed to get framebuffer info"); + loop { + time::sleep_ms(1000); + } + } + }; + let fb_ptr = match vga::map_framebuffer() { + Some(p) => p, + None => { + println!("[SHELL] Failed to map framebuffer"); + loop { + time::sleep_ms(1000); + } + } + }; + println!( + "[SHELL] fb info: width={} height={} stride={} fb_ptr={:p}", + info.width, info.height, info.stride, fb_ptr + ); + + let font = match Font::load() { + Some(f) => f, + None => { + println!("[SHELL] Failed to load font"); + loop { + time::sleep_ms(1000); + } + } + }; + + let mut term = Terminal::new(fb_ptr, info, font); + let mut kbd = Ps2Keyboard::new(); + let (cols, rows) = term.size_chars(); + let ws = WinSize { + ws_row: rows, + ws_col: cols, + ws_xpixel: 0, + ws_ypixel: 0, + }; + unsafe { + let _ = swiftlib::posix_stubs::ioctl(0, TIOCSWINSZ, (&ws as *const WinSize) as u64); + } + + term.clear_screen(); // clear_screen 内で flush 済み + term.fg = 0x00FF_FF00; // 黄色 + term.write_str("mochiOS Shell\n"); + term.fg = 0x00FF_FFFF; + term.prompt(); + term.flush(); + println!("[SHELL] Ready. Input is on the QEMU VGA window."); + + loop { + time::sleep_ms(10); + + while let Some(ch) = kbd.read() { + match ch { + b'\n' | b'\r' => { + term.handle_line(); + term.prompt(); + term.flush(); + } + 0x08 | 0x7F => { // Backspace / Delete + if term.input_len > 0 { + term.input_len -= 1; + term.erase_previous_cell(); + term.flush(); + } + } + 0x20..=0x7E => { + if term.input_len < term.input_buf.len() - 1 { + term.input_buf[term.input_len] = ch; + term.input_len += 1; + term.write_byte(ch); + term.flush(); + } + } + _ => {} + } + } + } +} diff --git a/src/task/mod.rs b/src/task/mod.rs deleted file mode 100644 index a044ca6..0000000 --- a/src/task/mod.rs +++ /dev/null @@ -1,1040 +0,0 @@ -//! タスク管理モジュール -//! -//! マルチタスク機能を提供(プロセスとスレッドの管理) - -use crate::interrupt::spinlock::SpinLock; -use core::sync::atomic::{AtomicU64, Ordering}; - -/// プロセスID生成用カウンタ -static NEXT_PROCESS_ID: AtomicU64 = AtomicU64::new(1); - -/// スレッドID生成用カウンタ -static NEXT_THREAD_ID: AtomicU64 = AtomicU64::new(1); - -/// プロセスID -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ProcessId(u64); - -impl ProcessId { - /// 新しいプロセスIDを生成 - pub fn new() -> Self { - Self(NEXT_PROCESS_ID.fetch_add(1, Ordering::Relaxed)) - } - - /// プロセスIDの値を取得 - pub fn as_u64(&self) -> u64 { - self.0 - } -} - -/// スレッドID -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ThreadId(u64); - -impl ThreadId { - /// 新しいスレッドIDを生成 - pub fn new() -> Self { - Self(NEXT_THREAD_ID.fetch_add(1, Ordering::Relaxed)) - } - - /// スレッドIDの値を取得 - pub fn as_u64(&self) -> u64 { - self.0 - } -} - -/// スレッドの状態 -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ThreadState { - /// 実行可能(スケジューラ待ち) - Ready, - /// 実行中 - Running, - /// ブロック中(I/O待ちなど) - Blocked, - /// スリープ中 - Sleeping, - /// 終了済み - Terminated, -} - -/// プロセスの状態 -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ProcessState { - /// 実行中(少なくとも1つのスレッドがRunning/Ready) - Running, - /// スリープ中(すべてのスレッドがSleeping) - Sleeping, - /// ゾンビ(終了したが親に回収されていない) - Zombie, - /// 終了済み - Terminated, -} - -/// タスクが保有する権限レベル。ServiceとUserは区別のためであり、両方ともRing3で動作する。 -/// -/// - Core: カーネルモード(Ring0)で動作するタスク。システムの中核機能を担当。 -/// - Service: ユーザーモード(Ring3)で動作するが、システムサービスやドライバを担当。 -/// - User: ユーザーモード(Ring3)で動作。一般的なアプリケーションを担当。 -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PrivilegeLevel { - /// コアレベルタスク(Ring0) - Core, - /// サービスレベルタスク(Ring3) - Service, - /// ユーザーレベルタスク(Ring3) - User, -} - -/// CPUコンテキスト(レジスタ保存用) -#[derive(Debug, Clone, Copy)] -#[repr(C)] -pub struct Context { - /// スタックポインタ - pub rsp: u64, - /// ベースポインタ - pub rbp: u64, - /// Callee-saved レジスタ - pub rbx: u64, - pub r12: u64, - pub r13: u64, - pub r14: u64, - pub r15: u64, - /// 命令ポインタ(戻り先アドレス) - pub rip: u64, - /// RFLAGSレジスタ - pub rflags: u64, -} - -impl Context { - /// 新しいコンテキストを作成 - pub const fn new() -> Self { - Self { - rsp: 0, - rbp: 0, - rbx: 0, - r12: 0, - r13: 0, - r14: 0, - r15: 0, - rip: 0, - rflags: 0, - } - } -} - -/// プロセス構造体 -/// -/// メモリ空間とリソースを管理する実行単位。 -/// 1つ以上のスレッドを持つ。 -pub struct Process { - /// プロセスID - id: ProcessId, - /// プロセス名 - name: &'static str, - /// プロセスの状態 - state: ProcessState, - /// 権限レベル - privilege: PrivilegeLevel, - /// 親プロセスID(存在する場合) - parent_id: Option, - /// ページテーブルのアドレス(メモリ空間)。Noneの場合はカーネル空間を共有。 - page_table: Option, - /// 優先度(0が最高、値が大きいほど低い) - priority: u8, -} - -impl Process { - /// 新しいプロセスを作成 - /// - /// # Arguments - /// * `name` - プロセス名 - /// * `privilege` - 権限レベル - /// * `parent_id` - 親プロセスID - /// * `priority` - プロセスの優先度 - pub fn new( - name: &'static str, - privilege: PrivilegeLevel, - parent_id: Option, - priority: u8, - ) -> Self { - Self { - id: ProcessId::new(), - name, - state: ProcessState::Running, - privilege, - parent_id, - page_table: None, // TODO: ページテーブル実装後に設定 - priority, - } - } - - /// プロセスIDを取得 - pub fn id(&self) -> ProcessId { - self.id - } - - /// プロセス名を取得 - pub fn name(&self) -> &'static str { - self.name - } - - /// プロセスの状態を取得 - pub fn state(&self) -> ProcessState { - self.state - } - - /// プロセスの状態を設定 - pub fn set_state(&mut self, state: ProcessState) { - self.state = state; - } - - /// 権限レベルを取得 - pub fn privilege(&self) -> PrivilegeLevel { - self.privilege - } - - /// 親プロセスIDを取得 - pub fn parent_id(&self) -> Option { - self.parent_id - } - - /// 優先度を取得 - pub fn priority(&self) -> u8 { - self.priority - } - - /// ページテーブルアドレスを取得 - pub fn page_table(&self) -> Option { - self.page_table - } - - /// ページテーブルアドレスを設定 - pub fn set_page_table(&mut self, page_table: u64) { - self.page_table = Some(page_table); - } -} - -impl core::fmt::Debug for Process { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let mut debug_struct = f.debug_struct("Process"); - debug_struct - .field("id", &self.id) - .field("name", &self.name) - .field("state", &self.state) - .field("privilege", &self.privilege) - .field("parent_id", &self.parent_id) - .field("priority", &self.priority); - - if let Some(pt) = self.page_table { - debug_struct.field("page_table", &format_args!("{:#x}", pt)); - } else { - debug_struct.field("page_table", &None::); - } - - debug_struct.finish() - } -} - -/// スレッド構造体 -/// -/// プロセス内で実行される軽量な実行単位。 -/// 同じプロセス内のスレッドはメモリ空間を共有する。 -pub struct Thread { - /// スレッドID - id: ThreadId, - /// 所属するプロセスID - process_id: ProcessId, - /// スレッド名 - name: &'static str, - /// 現在の状態 - state: ThreadState, - /// CPUコンテキスト - context: Context, - /// カーネルスタックの開始アドレス - kernel_stack: u64, - /// カーネルスタックのサイズ - kernel_stack_size: usize, -} - -impl Thread { - /// 新しいスレッドを作成 - /// - /// # Arguments - /// * `process_id` - 所属するプロセスID - /// * `name` - スレッド名 - /// * `entry_point` - スレッドのエントリーポイント関数 - /// * `kernel_stack` - カーネルスタックのアドレス - /// * `kernel_stack_size` - カーネルスタックのサイズ - pub fn new( - process_id: ProcessId, - name: &'static str, - entry_point: fn() -> !, - kernel_stack: u64, - kernel_stack_size: usize, - ) -> Self { - let mut context = Context::new(); - - // スタックポインタをスタックの最後に設定(スタックは下に伸びる) - context.rsp = kernel_stack + kernel_stack_size as u64; - context.rbp = context.rsp; - - // エントリーポイントをripに設定 - context.rip = entry_point as u64; - - // RFLAGSの初期値(割り込み有効) - context.rflags = 0x202; // IF (Interrupt Flag) = 1 - - Self { - id: ThreadId::new(), - process_id, - name, - state: ThreadState::Ready, - context, - kernel_stack, - kernel_stack_size, - } - } - - /// スレッドIDを取得 - pub fn id(&self) -> ThreadId { - self.id - } - - /// 所属するプロセスIDを取得 - pub fn process_id(&self) -> ProcessId { - self.process_id - } - - /// スレッド名を取得 - pub fn name(&self) -> &'static str { - self.name - } - - /// スレッドの状態を取得 - pub fn state(&self) -> ThreadState { - self.state - } - - /// スレッドの状態を設定 - pub fn set_state(&mut self, state: ThreadState) { - self.state = state; - } - - /// コンテキストへの可変参照を取得 - pub fn context_mut(&mut self) -> &mut Context { - &mut self.context - } - - /// コンテキストへの参照を取得 - pub fn context(&self) -> &Context { - &self.context - } -} - -impl core::fmt::Debug for Thread { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("Thread") - .field("id", &self.id) - .field("process_id", &self.process_id) - .field("name", &self.name) - .field("state", &self.state) - .field("kernel_stack", &format_args!("{:#x}", self.kernel_stack)) - .field("kernel_stack_size", &self.kernel_stack_size) - .finish() - } -} - -/// プロセステーブル -/// -/// システム内のすべてのプロセスを管理する -pub struct ProcessTable { - /// プロセスの配列(最大容量) - processes: [Option; Self::MAX_PROCESSES], - /// 現在のプロセス数 - count: usize, -} - -impl ProcessTable { - /// プロセステーブルの最大容量 - pub const MAX_PROCESSES: usize = 256; - - /// 新しいプロセステーブルを作成 - pub const fn new() -> Self { - const INIT: Option = None; - Self { - processes: [INIT; Self::MAX_PROCESSES], - count: 0, - } - } - - /// プロセスを追加 - /// - /// # Returns - /// 成功時はプロセスIDを返す。テーブルが満杯の場合はNone - pub fn add(&mut self, process: Process) -> Option { - if self.count >= Self::MAX_PROCESSES { - return None; - } - - let id = process.id(); - - // 空きスロットを探す - for slot in &mut self.processes { - if slot.is_none() { - *slot = Some(process); - self.count += 1; - return Some(id); - } - } - - None - } - - /// プロセスIDでプロセスを取得 - pub fn get(&self, id: ProcessId) -> Option<&Process> { - self.processes - .iter() - .find_map(|slot| slot.as_ref().filter(|p| p.id() == id)) - } - - /// プロセスIDでプロセスの可変参照を取得 - pub fn get_mut(&mut self, id: ProcessId) -> Option<&mut Process> { - self.processes - .iter_mut() - .find_map(|slot| slot.as_mut().filter(|p| p.id() == id)) - } - - /// プロセスを削除 - /// - /// # Returns - /// 削除されたプロセスを返す。存在しない場合はNone - pub fn remove(&mut self, id: ProcessId) -> Option { - for slot in &mut self.processes { - if let Some(ref process) = slot { - if process.id() == id { - self.count -= 1; - return slot.take(); - } - } - } - None - } - - /// すべてのプロセスを反復処理 - pub fn iter(&self) -> impl Iterator { - self.processes.iter().filter_map(|slot| slot.as_ref()) - } - - /// すべてのプロセスを可変反復処理 - pub fn iter_mut(&mut self) -> impl Iterator { - self.processes.iter_mut().filter_map(|slot| slot.as_mut()) - } - - /// 現在のプロセス数を取得 - pub fn count(&self) -> usize { - self.count - } - - /// プロセステーブルが満杯かどうか - pub fn is_full(&self) -> bool { - self.count >= Self::MAX_PROCESSES - } - - /// プロセステーブルが空かどうか - pub fn is_empty(&self) -> bool { - self.count == 0 - } -} - -/// グローバルプロセステーブル -static PROCESS_TABLE: SpinLock = SpinLock::new(ProcessTable::new()); - -/// プロセステーブルにプロセスを追加 -pub fn add_process(process: Process) -> Option { - PROCESS_TABLE.lock().add(process) -} - -/// プロセスIDでプロセス情報を取得(読み取り専用操作) -pub fn with_process(id: ProcessId, f: F) -> Option -where - F: FnOnce(&Process) -> R, -{ - let table = PROCESS_TABLE.lock(); - table.get(id).map(f) -} - -/// プロセスIDでプロセス情報を可変操作 -pub fn with_process_mut(id: ProcessId, f: F) -> Option -where - F: FnOnce(&mut Process) -> R, -{ - let mut table = PROCESS_TABLE.lock(); - table.get_mut(id).map(f) -} - -/// プロセスを削除 -pub fn remove_process(id: ProcessId) -> Option { - PROCESS_TABLE.lock().remove(id) -} - -/// すべてのプロセスに対して操作を実行 -pub fn for_each_process(mut f: F) -where - F: FnMut(&Process), -{ - let table = PROCESS_TABLE.lock(); - for process in table.iter() { - f(process); - } -} - -/// 現在のプロセス数を取得 -pub fn process_count() -> usize { - PROCESS_TABLE.lock().count() -} - -/// スレッドキュー -/// -/// 実行可能なスレッドを管理するキュー -pub struct ThreadQueue { - /// スレッドの配列(最大容量) - threads: [Option; Self::MAX_THREADS], - /// 現在のスレッド数 - count: usize, -} - -impl ThreadQueue { - /// スレッドキューの最大容量 - pub const MAX_THREADS: usize = 1024; - - /// 新しいスレッドキューを作成 - pub const fn new() -> Self { - const INIT: Option = None; - Self { - threads: [INIT; Self::MAX_THREADS], - count: 0, - } - } - - /// スレッドを追加 - /// - /// # Returns - /// 成功時はスレッドIDを返す。キューが満杯の場合はNone - pub fn push(&mut self, thread: Thread) -> Option { - if self.count >= Self::MAX_THREADS { - return None; - } - - let id = thread.id(); - - // 空きスロットを探す - for slot in &mut self.threads { - if slot.is_none() { - *slot = Some(thread); - self.count += 1; - return Some(id); - } - } - - None - } - - /// スレッドIDでスレッドを取得 - pub fn get(&self, id: ThreadId) -> Option<&Thread> { - self.threads - .iter() - .find_map(|slot| slot.as_ref().filter(|t| t.id() == id)) - } - - /// スレッドIDでスレッドの可変参照を取得 - pub fn get_mut(&mut self, id: ThreadId) -> Option<&mut Thread> { - self.threads - .iter_mut() - .find_map(|slot| slot.as_mut().filter(|t| t.id() == id)) - } - - /// スレッドを削除 - /// - /// # Returns - /// 削除されたスレッドを返す。存在しない場合はNone - pub fn remove(&mut self, id: ThreadId) -> Option { - for slot in &mut self.threads { - if let Some(ref thread) = slot { - if thread.id() == id { - self.count -= 1; - return slot.take(); - } - } - } - None - } - - /// 次に実行すべきスレッドを取得(削除せずに参照を返す) - /// - /// Ready状態のスレッドを優先して返す - pub fn peek_next(&self) -> Option<&Thread> { - // Ready状態のスレッドを探す - self.threads - .iter() - .filter_map(|slot| slot.as_ref()) - .find(|t| t.state() == ThreadState::Ready) - } - - /// 次に実行すべきスレッドを取得(可変参照) - pub fn peek_next_mut(&mut self) -> Option<&mut Thread> { - // Ready状態のスレッドを探す - self.threads - .iter_mut() - .filter_map(|slot| slot.as_mut()) - .find(|t| t.state() == ThreadState::Ready) - } - - /// 指定された状態のスレッド数をカウント - pub fn count_by_state(&self, state: ThreadState) -> usize { - self.threads - .iter() - .filter_map(|slot| slot.as_ref()) - .filter(|t| t.state() == state) - .count() - } - - /// 指定されたプロセスに属するスレッドを反復処理 - pub fn iter_by_process(&self, process_id: ProcessId) -> impl Iterator { - self.threads - .iter() - .filter_map(|slot| slot.as_ref()) - .filter(move |t| t.process_id() == process_id) - } - - /// すべてのスレッドを反復処理 - pub fn iter(&self) -> impl Iterator { - self.threads.iter().filter_map(|slot| slot.as_ref()) - } - - /// すべてのスレッドを可変反復処理 - pub fn iter_mut(&mut self) -> impl Iterator { - self.threads.iter_mut().filter_map(|slot| slot.as_mut()) - } - - /// 現在のスレッド数を取得 - pub fn count(&self) -> usize { - self.count - } - - /// スレッドキューが満杯かどうか - pub fn is_full(&self) -> bool { - self.count >= Self::MAX_THREADS - } - - /// スレッドキューが空かどうか - pub fn is_empty(&self) -> bool { - self.count == 0 - } -} - -/// グローバルスレッドキュー -static THREAD_QUEUE: SpinLock = SpinLock::new(ThreadQueue::new()); - -/// 現在実行中のスレッドID -static CURRENT_THREAD: SpinLock> = SpinLock::new(None); - -/// スレッドキューにスレッドを追加 -pub fn add_thread(thread: Thread) -> Option { - THREAD_QUEUE.lock().push(thread) -} - -/// スレッドIDでスレッド情報を取得(読み取り専用操作) -pub fn with_thread(id: ThreadId, f: F) -> Option -where - F: FnOnce(&Thread) -> R, -{ - let queue = THREAD_QUEUE.lock(); - queue.get(id).map(f) -} - -/// スレッドIDでスレッド情報を可変操作 -pub fn with_thread_mut(id: ThreadId, f: F) -> Option -where - F: FnOnce(&mut Thread) -> R, -{ - let mut queue = THREAD_QUEUE.lock(); - queue.get_mut(id).map(f) -} - -/// スレッドを削除 -pub fn remove_thread(id: ThreadId) -> Option { - THREAD_QUEUE.lock().remove(id) -} - -/// 次に実行すべきスレッドIDを取得 -pub fn peek_next_thread() -> Option { - THREAD_QUEUE.lock().peek_next().map(|t| t.id()) -} - -/// 指定された状態のスレッド数を取得 -pub fn count_threads_by_state(state: ThreadState) -> usize { - THREAD_QUEUE.lock().count_by_state(state) -} - -/// すべてのスレッドに対して操作を実行 -pub fn for_each_thread(mut f: F) -where - F: FnMut(&Thread), -{ - let queue = THREAD_QUEUE.lock(); - for thread in queue.iter() { - f(thread); - } -} - -/// 現在のスレッド数を取得 -pub fn thread_count() -> usize { - THREAD_QUEUE.lock().count() -} - -/// 現在実行中のスレッドIDを取得 -pub fn current_thread_id() -> Option { - *CURRENT_THREAD.lock() -} - -/// 現在実行中のスレッドIDを設定 -pub fn set_current_thread(id: Option) { - *CURRENT_THREAD.lock() = id; -} - -/// スケジューラ -/// -/// スレッドのスケジューリングを管理 -pub struct Scheduler { - /// スケジューラが有効かどうか - enabled: bool, - /// タイムスライス(タイマー割り込み回数) - time_slice: u64, - /// 現在のタイムスライスカウンタ - current_slice: u64, -} - -impl Scheduler { - /// デフォルトのタイムスライス(10ms × 10 = 100ms) - pub const DEFAULT_TIME_SLICE: u64 = 10; - - /// 新しいスケジューラを作成 - pub const fn new() -> Self { - Self { - enabled: false, - time_slice: Self::DEFAULT_TIME_SLICE, - current_slice: 0, - } - } - - /// スケジューラを有効化 - pub fn enable(&mut self) { - self.enabled = true; - } - - /// スケジューラを無効化 - pub fn disable(&mut self) { - self.enabled = false; - } - - /// スケジューラが有効かどうか - pub fn is_enabled(&self) -> bool { - self.enabled - } - - /// タイムスライスを設定 - pub fn set_time_slice(&mut self, slice: u64) { - self.time_slice = slice; - } - - /// タイマー割り込み時に呼ばれる - /// - /// タイムスライスをカウントし、期限が来たらスケジューリングを実行 - pub fn tick(&mut self) -> bool { - if !self.enabled { - return false; - } - - self.current_slice += 1; - if self.current_slice >= self.time_slice { - self.current_slice = 0; - true // スケジューリングが必要 - } else { - false - } - } - - /// タイムスライスをリセット - pub fn reset_slice(&mut self) { - self.current_slice = 0; - } -} - -/// グローバルスケジューラ -static SCHEDULER: SpinLock = SpinLock::new(Scheduler::new()); - -/// スケジューラを初期化 -pub fn init_scheduler() { - let mut scheduler = SCHEDULER.lock(); - scheduler.enable(); -} - -/// スケジューラを有効化 -pub fn enable_scheduler() { - SCHEDULER.lock().enable(); -} - -/// スケジューラを無効化 -pub fn disable_scheduler() { - SCHEDULER.lock().disable(); -} - -/// スケジューラが有効かどうか -pub fn is_scheduler_enabled() -> bool { - SCHEDULER.lock().is_enabled() -} - -/// タイマー割り込み時に呼ばれる(タイマー割り込みハンドラから呼び出す) -/// -/// # Returns -/// スケジューリングが必要な場合はtrue -pub fn scheduler_tick() -> bool { - SCHEDULER.lock().tick() -} - -/// 次に実行すべきスレッドを選択 -/// -/// ラウンドロビンスケジューリング:Ready状態のスレッドを順に選択 -/// -/// # Returns -/// 次に実行すべきスレッドID。実行可能なスレッドがない場合はNone -pub fn schedule() -> Option { - let mut queue = THREAD_QUEUE.lock(); - - // 現在のスレッドを取得 - let current = *CURRENT_THREAD.lock(); - - // 現在のスレッドがあれば、状態をReadyに戻す(Running -> Ready) - if let Some(current_id) = current { - if let Some(thread) = queue.get_mut(current_id) { - if thread.state() == ThreadState::Running { - thread.set_state(ThreadState::Ready); - } - } - } - - // 次に実行すべきReady状態のスレッドを探す - if let Some(next_thread) = queue.peek_next_mut() { - let next_id = next_thread.id(); - next_thread.set_state(ThreadState::Running); - - // スケジューラのタイムスライスをリセット - drop(queue); - SCHEDULER.lock().reset_slice(); - - Some(next_id) - } else { - None - } -} - -/// 現在のスレッドを明示的にCPUを手放す(yield) -/// -/// スケジューラを呼び出して次のスレッドに切り替える -pub fn yield_now() { - if !is_scheduler_enabled() { - return; - } - - // スケジューリングを実行 - if let Some(next_id) = schedule() { - let current = current_thread_id(); - - // 次のスレッドが現在のスレッドと異なる場合のみ切り替え - if Some(next_id) != current { - set_current_thread(Some(next_id)); - // TODO: コンテキストスイッチを実行 - // switch_context(current, next_id); - } - } -} - -/// スレッドをブロック状態にする -/// -/// 現在のスレッドをBlocked状態にして、次のスレッドにスケジューリング -pub fn block_current_thread() { - if let Some(current_id) = current_thread_id() { - with_thread_mut(current_id, |thread| { - thread.set_state(ThreadState::Blocked); - }); - - // 次のスレッドにスケジューリング - yield_now(); - } -} - -/// スレッドをスリープ状態にする -/// -/// 指定されたスレッドをSleeping状態にする -pub fn sleep_thread(id: ThreadId) { - with_thread_mut(id, |thread| { - thread.set_state(ThreadState::Sleeping); - }); -} - -/// スレッドを起床させる -/// -/// Sleeping/Blocked状態のスレッドをReady状態にする -pub fn wake_thread(id: ThreadId) { - with_thread_mut(id, |thread| { - let state = thread.state(); - if state == ThreadState::Sleeping || state == ThreadState::Blocked { - thread.set_state(ThreadState::Ready); - } - }); -} - -/// スレッドを終了させる -/// -/// 指定されたスレッドをTerminated状態にして削除 -pub fn terminate_thread(id: ThreadId) { - with_thread_mut(id, |thread| { - thread.set_state(ThreadState::Terminated); - }); - - // 現在のスレッドの場合は次のスレッドにスケジューリング - if Some(id) == current_thread_id() { - set_current_thread(None); - yield_now(); - } - - // スレッドをキューから削除 - remove_thread(id); -} - -/// コンテキストスイッチ -/// -/// 現在のスレッドから次のスレッドへコンテキストを切り替える -#[unsafe(naked)] -pub unsafe extern "C" fn switch_context(old_context: *mut Context, new_context: *const Context) { - core::arch::naked_asm!( - // 現在のコンテキストを保存(old_context) - // rdi = old_context, rsi = new_context - - // 汎用レジスタを保存 - "mov [rdi + 0x00], rsp", // rsp - "mov [rdi + 0x08], rbp", // rbp - "mov [rdi + 0x10], rbx", // rbx - "mov [rdi + 0x18], r12", // r12 - "mov [rdi + 0x20], r13", // r13 - "mov [rdi + 0x28], r14", // r14 - "mov [rdi + 0x30], r15", // r15 - // 戻り先アドレス(rip)を保存 - "mov rax, [rsp]", - "mov [rdi + 0x38], rax", // rip - // RFLAGSを保存 - "pushfq", - "pop rax", - "mov [rdi + 0x40], rax", // rflags - // 新しいコンテキストを復元(new_context) - - // RFLAGSを復元 - "mov rax, [rsi + 0x40]", - "push rax", - "popfq", - // 汎用レジスタを復元 - "mov rsp, [rsi + 0x00]", // rsp - "mov rbp, [rsi + 0x08]", // rbp - "mov rbx, [rsi + 0x10]", // rbx - "mov r12, [rsi + 0x18]", // r12 - "mov r13, [rsi + 0x20]", // r13 - "mov r14, [rsi + 0x28]", // r14 - "mov r15, [rsi + 0x30]", // r15 - // 新しいスレッドのripにジャンプ - "mov rax, [rsi + 0x38]", - "jmp rax", - ) -} - -/// 現在のスレッドから指定されたスレッドIDにコンテキストスイッチ -pub unsafe fn switch_to_thread(current_id: Option, next_id: ThreadId) { - let mut queue = THREAD_QUEUE.lock(); - - // 現在のスレッドのコンテキストへのポインタを取得 - let old_context_ptr = if let Some(id) = current_id { - if let Some(thread) = queue.get_mut(id) { - thread.context_mut() as *mut Context - } else { - return; // 現在のスレッドが見つからない - } - } else { - // 現在のスレッドがない場合(初回スイッチ) - // ダミーのコンテキストを使用 - core::ptr::null_mut() - }; - - // 次のスレッドのコンテキストへのポインタを取得 - let new_context_ptr = if let Some(thread) = queue.get(next_id) { - thread.context() as *const Context - } else { - return; // 次のスレッドが見つからない - }; - - // ロックを解放してからコンテキストスイッチ - drop(queue); - - // コンテキストスイッチを実行 - if old_context_ptr.is_null() { - // 初回スイッチの場合、現在のコンテキストを保存せずにジャンプ - let ctx = &*new_context_ptr; - core::arch::asm!( - "mov rsp, {rsp}", - "mov rbp, {rbp}", - "mov rbx, {rbx}", - "mov r12, {r12}", - "mov r13, {r13}", - "mov r14, {r14}", - "mov r15, {r15}", - "push {rflags}", - "popfq", - "jmp {rip}", - rsp = in(reg) ctx.rsp, - rbp = in(reg) ctx.rbp, - rbx = in(reg) ctx.rbx, - r12 = in(reg) ctx.r12, - r13 = in(reg) ctx.r13, - r14 = in(reg) ctx.r14, - r15 = in(reg) ctx.r15, - rflags = in(reg) ctx.rflags, - rip = in(reg) ctx.rip, - options(noreturn) - ); - } else { - switch_context(old_context_ptr, new_context_ptr); - } -} - -/// スケジューリングしてコンテキストスイッチを実行 -/// -/// タイマー割り込みハンドラから呼び出される -pub fn schedule_and_switch() { - if !is_scheduler_enabled() { - return; - } - - let current = current_thread_id(); - - // 次のスレッドを選択 - if let Some(next_id) = schedule() { - // 次のスレッドが現在のスレッドと異なる場合のみ切り替え - if Some(next_id) != current { - set_current_thread(Some(next_id)); - - // コンテキストスイッチを実行 - unsafe { - switch_to_thread(current, next_id); - } - } - } -} diff --git a/src/user/Cargo.toml b/src/user/Cargo.toml new file mode 100644 index 0000000..4349d58 --- /dev/null +++ b/src/user/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "swiftlib" +version = "0.1.0" +edition = "2021" + +[dependencies] + +[features] +default = ["library"] +library = [] +std-support = [] # When used from std-based binaries; disables the custom panic_handler +hosted-vga = ["std-support"] # Linuxホスト上のソフトウェアフレームバッファ + +[lib] +name = "swiftlib" +path = "lib.rs" +test = false +bench = false diff --git a/src/user/console.rs b/src/user/console.rs new file mode 100644 index 0000000..01d3318 --- /dev/null +++ b/src/user/console.rs @@ -0,0 +1,8 @@ +//! コンソール系システムコール(ユーザー側) + +use super::sys::{syscall2, SyscallNumber}; + +/// コンソールへ書き込み +pub fn write(buf: &[u8]) -> u64 { + syscall2(SyscallNumber::ConsoleWrite as u64, buf.as_ptr() as u64, buf.len() as u64) +} diff --git a/src/user/crt.rs b/src/user/crt.rs new file mode 100644 index 0000000..7cf62ad --- /dev/null +++ b/src/user/crt.rs @@ -0,0 +1,39 @@ +#![no_std] +#![no_main] + +// _startシンボルを定義 + +use core::arch::global_asm; + +global_asm!( + ".section .text", + ".global _start", + "_start:", + // カーネルによってスタック上に argc, argv, envp が構築されている。 + // RSP は argc を指している。 + + // argc を取得 (RDI) + "pop rdi", + + // argv を取得 (RSI) + // argc を pop した直後の rsp が argv 配列の先頭を指している + "mov rsi, rsp", + + // スタックアライメント + // main 呼び出し前に rsp を 16バイト境界に合わせる + "and rsp, -16", + + + "call main", + + // main の戻り値 (rax) を引数に exit を呼ぶ + "mov edi, eax", + "call _exit", +); + +extern "C" { + #[allow(unused)] + fn main(argc: i32, argv: *const *const u8) -> i32; + fn _exit(code: i32) -> !; +} + diff --git a/src/user/fs.rs b/src/user/fs.rs new file mode 100644 index 0000000..47301d6 --- /dev/null +++ b/src/user/fs.rs @@ -0,0 +1,105 @@ +//! ファイルシステム関連のシステムコール(ユーザー側) + +use super::sys::{syscall1, syscall2, syscall3, SyscallNumber}; +use alloc::vec::Vec; + +fn path_buf(path: &str) -> ([u8; 512], usize) { + let mut buf = [0u8; 512]; + let bytes = path.as_bytes(); + let len = bytes.len().min(511); + buf[..len].copy_from_slice(&bytes[..len]); + (buf, len) +} + +pub fn mkdir(path: &str, mode: u32) -> u64 { + let (buf, _) = path_buf(path); + syscall2(SyscallNumber::Mkdir as u64, buf.as_ptr() as u64, mode as u64) +} + +pub fn rmdir(path: &str) -> u64 { + let (buf, _) = path_buf(path); + syscall1(SyscallNumber::Rmdir as u64, buf.as_ptr() as u64) +} + +pub fn readdir(fd: u64, buf: &mut [u8]) -> u64 { + syscall3( + SyscallNumber::Readdir as u64, + fd, + buf.as_mut_ptr() as u64, + buf.len() as u64, + ) +} + +pub fn chdir(path: &str) -> u64 { + let (buf, _) = path_buf(path); + syscall1(SyscallNumber::Chdir as u64, buf.as_ptr() as u64) +} + +/// カレントワーキングディレクトリを取得する +pub fn getcwd(buf: &mut [u8]) -> Option<&str> { + let ret = syscall2( + SyscallNumber::Getcwd as u64, + buf.as_mut_ptr() as u64, + buf.len() as u64, + ); + if ret == 0 || ret > 0xFFFF_FFFF_0000_0000 { + return None; + } + let len = buf.iter().position(|&b| b == 0).unwrap_or(buf.len()); + core::str::from_utf8(&buf[..len]).ok() +} + +/// 実行ファイルをパス指定で起動する(カーネルの通常 exec 経路) +pub fn exec_via_fs(path: &str) -> Result { + crate::process::exec(path).map_err(|_| -2) +} + +/// ファイルを開く(通常 open システムコール) +pub fn open_via_fs(path: &str) -> Result { + let fd = crate::io::open(path, crate::io::O_RDONLY); + if fd < 0 { + Err(-2) + } else { + Ok(fd as u64) + } +} + +/// ファイルを読む(通常 read システムコール) +pub fn read_via_fs(fd: u64, out: &mut [u8]) -> Result { + let n = crate::io::read(fd, out); + if (n as i64) < 0 { + Err(-5) + } else { + Ok(n as usize) + } +} + +/// ファイルを閉じる +pub fn close_via_fs(fd: u64) { + let _ = crate::io::close(fd); +} + +/// ファイル全体を読む。存在しない場合は Ok(None)。 +pub fn read_file_via_fs(path: &str, max_size: usize) -> Result>, i64> { + let fd = match open_via_fs(path) { + Ok(fd) => fd, + Err(errno) if errno == -2 => return Ok(None), + Err(errno) => return Err(errno), + }; + + let mut out = Vec::new(); + let mut chunk = [0u8; 4096]; + while out.len() < max_size { + let to_read = core::cmp::min(chunk.len(), max_size - out.len()); + match read_via_fs(fd, &mut chunk[..to_read]) { + Ok(0) => break, + Ok(n) => out.extend_from_slice(&chunk[..n]), + Err(errno) => { + close_via_fs(fd); + return Err(errno); + } + } + } + close_via_fs(fd); + Ok(Some(out)) +} diff --git a/src/user/fs_consts.rs b/src/user/fs_consts.rs new file mode 100644 index 0000000..777cc04 --- /dev/null +++ b/src/user/fs_consts.rs @@ -0,0 +1,10 @@ +//! ファイルシステムIPC定数(カーネル・サービス・ユーザー空間共通) + +/// ファイルパスの最大長 +pub const FS_PATH_MAX: usize = 512; + +/// 1回のFS応答で送信可能なデータの最大サイズ +pub const FS_DATA_MAX: usize = 4096; + +/// IPCメッセージの最大サイズ(将来的な拡張用、現在は使用されていない) +pub const IPC_MAX_MSG_SIZE: usize = 65536; diff --git a/src/user/gfx.rs b/src/user/gfx.rs new file mode 100644 index 0000000..b54cbe0 --- /dev/null +++ b/src/user/gfx.rs @@ -0,0 +1,123 @@ +//! 描画ラッパー(mochiOS / Linux host 共通) + +use crate::vga::{self, FbInfo}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum GfxError { + FramebufferUnavailable, + InvalidSize, +} + +pub struct Surface { + fb_ptr: *mut u32, + info: FbInfo, +} + +impl Surface { + /// mochiOS では実フレームバッファ、hosted-vga ではホストバッファへ接続する。 + pub fn from_system() -> Result { + let info = vga::get_info().ok_or(GfxError::FramebufferUnavailable)?; + let fb_ptr = vga::map_framebuffer().ok_or(GfxError::FramebufferUnavailable)?; + Ok(Self { fb_ptr, info }) + } + + /// Linux host 向けバッファを作成して Surface を返す。 + #[cfg(feature = "hosted-vga")] + pub fn from_host(width: u32, height: u32) -> Result { + if width == 0 || height == 0 { + return Err(GfxError::InvalidSize); + } + vga::host_init_framebuffer(width, height).map_err(|_| GfxError::FramebufferUnavailable)?; + Self::from_system() + } + + #[inline] + pub fn width(&self) -> usize { + self.info.width as usize + } + + #[inline] + pub fn height(&self) -> usize { + self.info.height as usize + } + + #[inline] + pub fn stride(&self) -> usize { + self.info.stride as usize + } + + pub fn clear(&mut self, color: u32) { + let c = with_alpha(color); + let total = self.stride().saturating_mul(self.height()); + for i in 0..total { + unsafe { + self.fb_ptr.add(i).write_volatile(c); + } + } + } + + pub fn fill_rect(&mut self, x: i32, y: i32, w: usize, h: usize, color: u32) { + if w == 0 || h == 0 { + return; + } + let (x0, x1) = clip_axis(x, w, self.width()); + let (y0, y1) = clip_axis(y, h, self.height()); + if x0 >= x1 || y0 >= y1 { + return; + } + let c = with_alpha(color); + let stride = self.stride(); + for yy in y0..y1 { + let row = yy.saturating_mul(stride); + for xx in x0..x1 { + unsafe { + self.fb_ptr.add(row + xx).write_volatile(c); + } + } + } + } + + pub fn blit_argb(&mut self, x: i32, y: i32, width: usize, height: usize, pixels: &[u32]) { + if width == 0 || height == 0 || pixels.len() < width.saturating_mul(height) { + return; + } + let (x0, x1) = clip_axis(x, width, self.width()); + let (y0, y1) = clip_axis(y, height, self.height()); + if x0 >= x1 || y0 >= y1 { + return; + } + let stride = self.stride(); + let src_x0 = x0 - x.max(0) as usize; + let src_y0 = y0 - y.max(0) as usize; + for yy in 0..(y1 - y0) { + let dst_row = (y0 + yy).saturating_mul(stride); + let src_row = (src_y0 + yy).saturating_mul(width); + for xx in 0..(x1 - x0) { + let src = with_alpha(pixels[src_row + src_x0 + xx]); + unsafe { + self.fb_ptr.add(dst_row + x0 + xx).write_volatile(src); + } + } + } + } + + #[cfg(feature = "hosted-vga")] + pub fn dump_ppm(&self, path: &str) -> Result<(), GfxError> { + vga::host_dump_ppm(path).map_err(|_| GfxError::FramebufferUnavailable) + } +} + +#[inline] +fn with_alpha(color: u32) -> u32 { + if (color >> 24) == 0 { + color | 0xFF00_0000 + } else { + color + } +} + +fn clip_axis(pos: i32, len: usize, max: usize) -> (usize, usize) { + let start = pos.max(0) as usize; + let end = (pos.saturating_add(len as i32)).max(0) as usize; + (start.min(max), end.min(max)) +} diff --git a/src/user/input.rs b/src/user/input.rs new file mode 100644 index 0000000..795f5e2 --- /dev/null +++ b/src/user/input.rs @@ -0,0 +1,45 @@ +//! 入力注入系システムコール(ユーザー側) + +use super::sys::{syscall1, SyscallNumber, EINVAL, EPERM}; + +/// raw スキャンコードを入力キューへ注入する(Service/Core専用) +#[inline] +pub fn inject_scancode(scancode: u8) -> Result<(), u64> { + let ret = syscall1(SyscallNumber::KeyboardInject as u64, scancode as u64); + if ret == 0 { + Ok(()) + } else { + Err(ret) + } +} + +/// 4バイトマウスパケットを入力キューへ注入する(Service/Core専用) +/// +/// `buttons`: bit0=Left, bit1=Right, bit2=Middle +#[inline] +pub fn inject_mouse_packet(buttons: u8, dx: i8, dy: i8, wheel: i8) -> Result<(), u64> { + let mut status = buttons & 0x07; + status |= 0x08; + if dx < 0 { + status |= 1 << 4; + } + if dy < 0 { + status |= 1 << 5; + } + let packet = u64::from(status) + | (u64::from(dx as u8) << 8) + | (u64::from(dy as u8) << 16) + | (u64::from(wheel as u8) << 24); + let ret = syscall1(SyscallNumber::MouseInject as u64, packet); + if ret == 0 { + Ok(()) + } else { + Err(ret) + } +} + +/// 失敗理由が権限不足/引数不正かを簡易判定する補助 +#[inline] +pub fn is_permission_or_arg_error(err: u64) -> bool { + err == EPERM || err == EINVAL +} diff --git a/src/user/io.rs b/src/user/io.rs new file mode 100644 index 0000000..f3df80e --- /dev/null +++ b/src/user/io.rs @@ -0,0 +1,167 @@ +//! I/O関連のシステムコールラッパー + +use crate::sys::{syscall0, syscall1, syscall2, syscall3, SyscallNumber}; + +/// 標準出力のファイルディスクリプタ +pub const STDOUT: u64 = 1; +/// 標準エラー出力のファイルディスクリプタ +pub const STDERR: u64 = 2; +/// 標準入力のファイルディスクリプタ +pub const STDIN: u64 = 0; + +/// ファイルオープンフラグ +pub const O_RDONLY: u64 = 0; +pub const O_WRONLY: u64 = 1; +pub const O_RDWR: u64 = 2; +pub const O_CREAT: u64 = 0x40; +pub const O_TRUNC: u64 = 0x200; +pub const O_APPEND: u64 = 0x400; + +/// ファイルディスクリプタに書き込む +/// +/// # 引数 +/// - `fd`: ファイルディスクリプタ +/// - `buf`: 書き込むデータ +/// +/// # 戻り値 +/// 書き込んだバイト数、またはエラーコード +#[inline] +pub fn write(fd: u64, buf: &[u8]) -> u64 { + syscall3( + SyscallNumber::Write as u64, + fd, + buf.as_ptr() as u64, + buf.len() as u64, + ) +} + +/// 標準出力に書き込む +/// +/// # 引数 +/// - `buf`: 書き込むデータ +/// +/// # 戻り値 +/// 書き込んだバイト数、またはエラーコード +#[inline] +pub fn write_stdout(buf: &[u8]) -> u64 { + write(STDOUT, buf) +} + +/// 標準エラー出力に書き込む +/// +/// # 引数 +/// - `buf`: 書き込むデータ +/// +/// # 戻り値 +/// 書き込んだバイト数、またはエラーコード +#[inline] +pub fn write_stderr(buf: &[u8]) -> u64 { + write(STDERR, buf) +} + +/// 標準出力に文字列を書き込む +/// +/// # 引数 +/// - `s`: 書き込む文字列 +/// +/// # 戻り値 +/// 書き込んだバイト数、またはエラーコード +#[inline] +pub fn print(s: &str) -> u64 { + write_stdout(s.as_bytes()) +} + +/// ファイルディスクリプタから読み込む +/// +/// # 引数 +/// - `fd`: ファイルディスクリプタ +/// - `buf`: 読み込むバッファ +/// +/// # 戻り値 +/// 読み込んだバイト数、またはエラーコード +#[inline] +pub fn read(fd: u64, buf: &mut [u8]) -> u64 { + syscall3( + SyscallNumber::Read as u64, + fd, + buf.as_mut_ptr() as u64, + buf.len() as u64, + ) +} + +/// ファイルを開く(未実装) +/// +/// # 引数 +/// - `path`: ファイルパス +/// - `flags`: オープンフラグ +/// +/// # 戻り値 +/// ファイルディスクリプタ、またはエラーコード +#[inline] +pub fn open(path: &str, flags: u64) -> i64 { + let mut buf = [0u8; 512]; + let bytes = path.as_bytes(); + if bytes.len() >= buf.len() { + return -1; + } + buf[..bytes.len()].copy_from_slice(bytes); + // buf[bytes.len()] is already 0 (null terminator) + let ret = syscall2( + SyscallNumber::Open as u64, + buf.as_ptr() as u64, + flags, + ); + if (ret as i64) < 0 { + -1 + } else { + ret as i64 + } +} + +/// ファイルを閉じる(未実装) +/// +/// # 引数 +/// - `fd`: ファイルディスクリプタ +/// +/// # 戻り値 +/// 成功時は0、エラー時は負の値 +#[inline] +pub fn close(fd: u64) -> i64 { + let ret = syscall1(SyscallNumber::Close as u64, fd); + if (ret as i64) < 0 { + -1 + } else { + ret as i64 + } +} + +/// カーネルにログを書き込む +/// +/// # 引数 +/// - `msg`: ログメッセージ +/// - `len`: メッセージの長さ +/// - `level`: ログレベル(0=ERROR、1=WARNING、2=INFO、3=DEBUG) +/// +/// # 戻り値 +/// SUCCESSまたはエラーコード +#[inline] +pub fn log(msg: u64, len: u64, level: u64) -> u64 { + syscall3( + SyscallNumber::Log as u64, + msg, + len, + level, + ) +} + +#[inline] +pub fn check_gravity_exist() -> bool { + let answer = syscall0( + SyscallNumber::CheckGravityExist as u64 + ); + + if answer == 0 { + return true; + } + false +} \ No newline at end of file diff --git a/src/user/ipc.rs b/src/user/ipc.rs new file mode 100644 index 0000000..18f6b86 --- /dev/null +++ b/src/user/ipc.rs @@ -0,0 +1,48 @@ +//! IPC 系システムコール(ユーザー側) + +use super::sys::{syscall2, syscall3, SyscallNumber}; + +/// IPC送信(宛先スレッドID, データ) +pub fn ipc_send(dest_thread_id: u64, data: &[u8]) -> u64 { + syscall3( + SyscallNumber::IpcSend as u64, + dest_thread_id, + data.as_ptr() as u64, + data.len() as u64, + ) +} + +/// IPC受信(ノンブロッキング) +/// 戻り値: (sender_id, received_len) — メッセージがなければ (0, 0) +pub fn ipc_recv(buf: &mut [u8]) -> (u64, u64) { + let ret = syscall2( + SyscallNumber::IpcRecv as u64, + buf.as_mut_ptr() as u64, + buf.len() as u64 + ); + // EAGAIN やその他エラーは負の i64 として返る + if (ret as i64) < 0 { + return (0, 0); + } + // カーネルは (sender << 32 | len) を返す + let sender = ret >> 32; + let len = ret & 0xFFFF_FFFF; + (sender, len) +} + +/// IPC受信(ブロッキング版) +/// メッセージが届くまでスリープして待機する。 +/// 戻り値: (sender_id, received_len) +pub fn ipc_recv_wait(buf: &mut [u8]) -> (u64, u64) { + let ret = syscall2( + SyscallNumber::IpcRecvWait as u64, + buf.as_mut_ptr() as u64, + buf.len() as u64, + ); + if (ret as i64) < 0 { + return (0, 0); + } + let sender = ret >> 32; + let len = ret & 0xFFFF_FFFF; + (sender, len) +} diff --git a/src/user/keyboard.rs b/src/user/keyboard.rs new file mode 100644 index 0000000..ef54014 --- /dev/null +++ b/src/user/keyboard.rs @@ -0,0 +1,28 @@ +//! キーボード系システムコール(ユーザー側) + +use super::sys::{syscall0, SyscallNumber, ENODATA}; + +/// PS/2 rawスキャンコードを1バイト読み取り(なければ None) +/// 変換はユーザー空間で行う +pub fn read_scancode() -> Option { + let ret = syscall0(SyscallNumber::KeyboardRead as u64); + if ret == ENODATA { + None + } else { + Some(ret as u8) + } +} + +/// ドライバ監視用キューから raw スキャンコードを1バイト読み取る +/// +/// 通常入力キューを消費しないため、shell.service と並行して利用できる。 +pub fn read_scancode_tap() -> Result, u64> { + let ret = syscall0(SyscallNumber::KeyboardReadTap as u64); + if ret == ENODATA { + Ok(None) + } else if (ret as i64) < 0 { + Err(ret) + } else { + Ok(Some(ret as u8)) + } +} diff --git a/src/user/lib.rs b/src/user/lib.rs new file mode 100644 index 0000000..0efd5c4 --- /dev/null +++ b/src/user/lib.rs @@ -0,0 +1,95 @@ +#![cfg_attr(not(feature = "hosted-vga"), no_std)] + +extern crate alloc; + +use alloc::alloc::{GlobalAlloc, Layout}; + +/// システムコールの共通インターフェース +pub mod sys; + +/// CランタイムとNewlibサポート +pub mod newlib; + +/// ipc関連のシステムコール +pub mod ipc; + +/// タスク関連のシステムコール +pub mod task; + +/// 時間関連のシステムコール +pub mod time; + +/// 入出力関連のシステムコール +pub mod io; + +/// プロセス管理関連のシステムコール +pub mod process; + +/// ファイルシステム関連のシステムコール +pub mod fs; + +/// ポートI/O関連のシステムコール +pub mod port; + +/// libcのC関数 +pub mod libc; + +/// Linux/POSIX 互換スタブ (std リンク用) +pub mod posix_stubs; + +/// フレームバッファアクセス +pub mod vga; +/// 描画ラッパー(mochiOS / Linux host 共通) +pub mod gfx; + +/// キーボード入力 +pub mod keyboard; +/// マウス入力 +pub mod mouse; +/// 入力注入 +pub mod input; +/// MMIO/物理メモリマップ +pub mod mmio; + +/// ファイルシステムIPC定数 +pub mod fs_consts; + +/// 特権システムコール(Service権限専用) +pub mod privileged; + +#[cfg(not(feature = "std-support"))] +use core::panic::PanicInfo; +#[cfg(not(feature = "std-support"))] +use crate::sys::SyscallNumber; + +#[cfg(not(feature = "std-support"))] +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + unsafe { + core::arch::asm!( + "int 0x80", + in("rax") SyscallNumber::ExitGroup as u64, + in("rdi") 1u64, + options(nostack, noreturn) + ) + } +} + +struct NewlibAllocator; + +unsafe impl GlobalAlloc for NewlibAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + libc::memalign(layout.align(), layout.size()) + } + + unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { + libc::free(ptr); + } + + unsafe fn realloc(&self, ptr: *mut u8, _layout: Layout, new_size: usize) -> *mut u8 { + libc::realloc(ptr, new_size) + } +} + +#[global_allocator] +static ALLOCATOR: NewlibAllocator = NewlibAllocator; diff --git a/src/user/libc.rs b/src/user/libc.rs new file mode 100644 index 0000000..f29ac9c --- /dev/null +++ b/src/user/libc.rs @@ -0,0 +1,95 @@ +pub unsafe extern "C" fn write(fd: i32, buf: *const u8, count: usize) -> isize { + let slice = core::slice::from_raw_parts(buf, count); + let ret = crate::io::write(fd as u64, slice); + + if (ret as i64) < 0 { + -1 + } else { + ret as isize + } +} + +pub unsafe extern "C" fn read(fd: i32, buf: *mut u8, count: usize) -> isize { + let slice = core::slice::from_raw_parts_mut(buf, count); + let ret = crate::io::read(fd as u64, slice); + + if (ret as i64) < 0 { + -1 + } else { + ret as isize + } +} + +pub unsafe extern "C" fn memalign(alignment: usize, size: usize) -> *mut u8 { + extern "C" { + #[link_name = "memalign"] + fn c_memalign(alignment: usize, size: usize) -> *mut u8; + } + c_memalign(alignment, size) +} + +pub unsafe extern "C" fn free(ptr: *mut u8) { + extern "C" { + #[link_name = "free"] + fn c_free(ptr: *mut u8); + } + c_free(ptr); +} + +pub unsafe extern "C" fn realloc(ptr: *mut u8, size: usize) -> *mut u8 { + extern "C" { + #[link_name = "realloc"] + fn c_realloc(ptr: *mut u8, size: usize) -> *mut u8; + } + c_realloc(ptr, size) +} + +pub unsafe extern "C" fn open(path: *const u8, flags: i32) -> i32 { + if path.is_null() { + return -1; + } + let ret = crate::sys::syscall2( + crate::sys::SyscallNumber::Open as u64, + path as u64, + flags as u64, + ); + if (ret as i64) < 0 { + -1 + } else { + ret as i32 + } +} + +pub unsafe extern "C" fn close(fd: i32) -> i32 { + crate::io::close(fd as u64) as i32 +} + +#[inline] +pub fn inb(port: u16) -> u8 { + crate::port::inb(port) +} + +#[inline] +pub fn outb(port: u16, value: u8) { + crate::port::outb(port, value) +} + +#[inline] +pub fn inw(port: u16) -> u16 { + crate::port::inw(port) +} + +#[inline] +pub fn outw(port: u16, value: u16) { + crate::port::outw(port, value) +} + +#[inline] +pub fn inl(port: u16) -> u32 { + crate::port::inl(port) +} + +#[inline] +pub fn outl(port: u16, value: u32) { + crate::port::outl(port, value) +} diff --git a/src/user/linker.ld b/src/user/linker.ld new file mode 100644 index 0000000..c620a88 --- /dev/null +++ b/src/user/linker.ld @@ -0,0 +1,36 @@ +OUTPUT_FORMAT("elf64-x86-64") +OUTPUT_ARCH(i386:x86-64) +ENTRY(_start) + +PHDRS { + text PT_LOAD FLAGS(5); + data PT_LOAD FLAGS(6); +} + +SECTIONS { + /* Link user programs at 0x200000 (matches kernel expectations) */ + . = 0x200000; + + .text : { + *(.text._start) + *(.text .text.*) + } :text + + .rodata : ALIGN(0x1000) { + *(.rodata .rodata.*) + } :data + + .data : ALIGN(0x1000) { + *(.data .data.*) + } :data + + .bss : ALIGN(0x1000) { + *(COMMON) + *(.bss .bss.*) + } :data + + /DISCARD/ : { + *(.eh_frame) + *(.comment) + } +} diff --git a/src/user/mmio.rs b/src/user/mmio.rs new file mode 100644 index 0000000..fff719d --- /dev/null +++ b/src/user/mmio.rs @@ -0,0 +1,36 @@ +//! MMIO/物理メモリマップ関連システムコール + +use crate::sys::{syscall1, syscall2, SyscallNumber}; + +const EINVAL: u64 = (-22i64) as u64; + +/// 物理アドレス範囲を現在プロセスのユーザー空間にマップする +/// +/// 成功時はマップされた先頭仮想アドレスを返す。 +pub fn map_physical(phys_addr: u64, size: usize) -> Result<*mut u8, u64> { + if size == 0 { + return Err(EINVAL); + } + if phys_addr.checked_add(size as u64).is_none() { + return Err(EINVAL); + } + + let ret = syscall2(SyscallNumber::MapPhysicalRange as u64, phys_addr, size as u64); + let signed_ret = ret as i64; + if (-4095..=-1).contains(&signed_ret) { + Err((-signed_ret) as u64) + } else { + Ok(ret as *mut u8) + } +} + +/// ユーザー仮想アドレスを物理アドレスへ変換する +pub fn virt_to_phys(ptr: *const u8) -> Result { + let ret = syscall1(SyscallNumber::VirtToPhys as u64, ptr as u64); + let signed_ret = ret as i64; + if (-4095..=-1).contains(&signed_ret) { + Err((-signed_ret) as u64) + } else { + Ok(ret) + } +} diff --git a/src/user/mouse.rs b/src/user/mouse.rs new file mode 100644 index 0000000..a9d3a87 --- /dev/null +++ b/src/user/mouse.rs @@ -0,0 +1,93 @@ +//! マウス系システムコール(ユーザー側) + +use super::sys::{syscall1, SyscallNumber, ENODATA}; + +/// PS/2 3バイトパケットを展開した入力イベント +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MousePacket { + /// ボタンビット(bit0=Left, bit1=Right, bit2=Middle) + pub buttons: u8, + /// X移動量(符号付き) + pub dx: i8, + /// Y移動量(符号付き、PS/2生値) + pub dy: i8, +} + +impl MousePacket { + #[inline] + pub fn left(&self) -> bool { + (self.buttons & 0x01) != 0 + } + + #[inline] + pub fn right(&self) -> bool { + (self.buttons & 0x02) != 0 + } + + #[inline] + pub fn middle(&self) -> bool { + (self.buttons & 0x04) != 0 + } +} + +/// PS/2 マウスパケットを1件読み取る(非ブロッキング) +pub fn read_packet() -> Result, u64> { + let mut raw: u32 = 0; + let ret = syscall1(SyscallNumber::MouseRead as u64, (&mut raw as *mut u32) as u64); + if ret == ENODATA { + return Ok(None); + } + if (ret as i64) < 0 { + return Err(ret); + } + + let b0 = (raw & 0xFF) as u8; + let b1 = ((raw >> 8) & 0xFF) as u8; + let b2 = ((raw >> 16) & 0xFF) as u8; + Ok(Some(MousePacket { + buttons: b0 & 0x07, + dx: b1 as i8, + dy: b2 as i8, + })) +} + +/// PS/2 マウスパケットを1件読み取る(ブロッキング) +pub fn read_packet_wait() -> Result { + let mut raw: u32 = 0; + let ret = syscall1( + SyscallNumber::MouseReadWait as u64, + (&mut raw as *mut u32) as u64, + ); + if (ret as i64) < 0 { + return Err(ret); + } + + let b0 = (raw & 0xFF) as u8; + let b1 = ((raw >> 8) & 0xFF) as u8; + let b2 = ((raw >> 16) & 0xFF) as u8; + Ok(MousePacket { + buttons: b0 & 0x07, + dx: b1 as i8, + dy: b2 as i8, + }) +} + +/// PS/2 マウスパケットを1件読み取る(非ブロッキング, 戻り値直受け) +pub fn read_packet_raw() -> Result, u64> { + let ret = syscall1(SyscallNumber::MouseRead as u64, 0); + if ret == ENODATA { + return Ok(None); + } + if (ret as i64) < 0 { + return Err(ret); + } + let raw = ret as u32; + let b0 = (raw & 0xFF) as u8; + let b1 = ((raw >> 8) & 0xFF) as u8; + let b2 = ((raw >> 16) & 0xFF) as u8; + Ok(Some(MousePacket { + buttons: b0 & 0x07, + dx: b1 as i8, + dy: b2 as i8, + })) +} diff --git a/src/user/newlib.rs b/src/user/newlib.rs new file mode 100644 index 0000000..6f41b4a --- /dev/null +++ b/src/user/newlib.rs @@ -0,0 +1,258 @@ +//! Newlib サポート用のシステムコールグルーコード + +use super::sys::{syscall1, syscall2, syscall3, SyscallNumber}; +use core::sync::atomic::{AtomicBool, Ordering}; + +static SBRK_LOCK: AtomicBool = AtomicBool::new(false); + +unsafe fn set_errno_from_ret(ret: i64) { + if ret >= 0 { + return; + } + unsafe extern "C" { + fn __errno_location() -> *mut i32; + } + let errno = (-ret) as i32; + let p = unsafe { __errno_location() }; + if !p.is_null() { + unsafe { *p = errno }; + } +} + +struct SbrkLockGuard; + +impl SbrkLockGuard { + fn lock() -> Self { + while SBRK_LOCK + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_err() + { + core::hint::spin_loop(); + } + Self + } +} + +impl Drop for SbrkLockGuard { + fn drop(&mut self) { + SBRK_LOCK.store(false, Ordering::Release); + } +} + +#[no_mangle] +pub extern "C" fn _write(fd: i32, buf: *const u8, len: usize) -> isize { + let ret = syscall3(SyscallNumber::Write as u64, fd as u64, buf as u64, len as u64) as i64; + if ret < 0 { + unsafe { set_errno_from_ret(ret) }; + -1 + } else { + ret as isize + } +} + +#[no_mangle] +pub extern "C" fn write(fd: i32, buf: *const u8, len: usize) -> isize { + _write(fd, buf, len) +} + +#[no_mangle] +pub extern "C" fn _read(fd: i32, buf: *mut u8, len: usize) -> isize { + let ret = syscall3(SyscallNumber::Read as u64, fd as u64, buf as u64, len as u64) as i64; + if ret < 0 { + unsafe { set_errno_from_ret(ret) }; + -1 + } else { + ret as isize + } +} + +#[no_mangle] +pub extern "C" fn read(fd: i32, buf: *mut u8, len: usize) -> isize { + _read(fd, buf, len) +} + +#[no_mangle] +pub extern "C" fn _close(fd: i32) -> i32 { + let ret = syscall1(SyscallNumber::Close as u64, fd as u64) as i64; + if ret < 0 { + unsafe { set_errno_from_ret(ret) }; + -1 + } else { + ret as i32 + } +} + +#[no_mangle] +pub extern "C" fn close(fd: i32) -> i32 { + _close(fd) +} + +#[no_mangle] +pub extern "C" fn _open(path: *const u8, flags: i32, _mode: i32) -> i32 { + let ret = syscall2(SyscallNumber::Open as u64, path as u64, flags as u64) as i64; + if ret < 0 { + unsafe { set_errno_from_ret(ret) }; + -1 + } else { + ret as i32 + } +} + +#[no_mangle] +pub extern "C" fn _lseek(fd: i32, offset: isize, whence: i32) -> isize { + let ret = syscall3( + SyscallNumber::Lseek as u64, + fd as u64, + offset as u64, + whence as u64, + ) as i64; + if ret < 0 { + unsafe { set_errno_from_ret(ret) }; + -1 + } else { + ret as isize + } +} + +#[no_mangle] +pub extern "C" fn lseek(fd: i32, offset: isize, whence: i32) -> isize { + _lseek(fd, offset, whence) +} + +#[no_mangle] +pub extern "C" fn _exit(code: i32) -> ! { + syscall1(SyscallNumber::Exit as u64, code as u64); + loop {} +} + +// exit は libc にあるので定義しなくてよいかも?でも _exit を呼ぶはず。 +// ただし crt0 から呼ばれるのは _exit だったりする。 + +#[no_mangle] +pub extern "C" fn _fstat(fd: i32, stat: *mut u8) -> i32 { + let ret = syscall2(SyscallNumber::Fstat as u64, fd as u64, stat as u64) as i64; + if ret < 0 { + unsafe { set_errno_from_ret(ret) }; + -1 + } else { + ret as i32 + } +} + +#[no_mangle] +pub extern "C" fn fstat(fd: i32, stat: *mut u8) -> i32 { + _fstat(fd, stat) +} + +#[no_mangle] +pub extern "C" fn _ioctl(fd: i32, request: u64, arg: u64) -> i32 { + let ret = syscall3( + SyscallNumber::Ioctl as u64, + fd as u64, + request, + arg, + ) as i64; + if ret < 0 { + unsafe { set_errno_from_ret(ret) }; + -1 + } else { + ret as i32 + } +} + +#[no_mangle] +pub extern "C" fn _isatty(fd: i32) -> i32 { + const TCGETS: u64 = 0x5401; + const XCGETA: u64 = ((b'x' as u64) << 8) | 1; + + if fd < 0 { + return 0; + } + + // Linux系 termios ioctl + let mut termios = [0u8; 36]; + let ret = syscall3( + SyscallNumber::Ioctl as u64, + fd as u64, + TCGETS, + termios.as_mut_ptr() as u64, + ) as i64; + if ret >= 0 { + return 1; + } + + // newlib(sysvi386) 互換 ioctl + let mut termio = [0u8; 18]; + let ret = syscall3( + SyscallNumber::Ioctl as u64, + fd as u64, + XCGETA, + termio.as_mut_ptr() as u64, + ) as i64; + if ret >= 0 { 1 } else { 0 } +} + +#[no_mangle] +pub extern "C" fn isatty(fd: i32) -> i32 { + _isatty(fd) +} + +#[no_mangle] +pub extern "C" fn _sbrk(incr: isize) -> *mut u8 { + let _sbrk_guard = SbrkLockGuard::lock(); + + // brk は mmap/MMIO マッピングでも更新されるため、ユーザー側で末端を + // キャッシュすると整合性が壊れてヒープ破壊につながる。 + // 毎回 brk(0) で現在値を取得してから更新する。 + let cur = syscall1(SyscallNumber::Brk as u64, 0); + if cur == 0 || cur > 0xffff_ffff_ffff_f000 { + return -1_isize as *mut u8; + } + let old_heap_end = cur; + + // 安全側に倒して縮小は未サポートにする(MMIO 併用時の破壊回避)。 + if incr < 0 { + return -1_isize as *mut u8; + } + if incr == 0 { + return old_heap_end as *mut u8; + } + + let incr_u64 = incr as u64; + let new_heap_end = match old_heap_end.checked_add(incr_u64) { + Some(v) => v, + None => return -1_isize as *mut u8, + }; + let ret = syscall1(SyscallNumber::Brk as u64, new_heap_end); + if ret == new_heap_end { + old_heap_end as *mut u8 + } else { + -1_isize as *mut u8 + } +} + +#[no_mangle] +pub extern "C" fn sbrk(incr: isize) -> *mut u8 { + _sbrk(incr) +} + +#[no_mangle] +pub extern "C" fn _getpid() -> i32 { + syscall1(SyscallNumber::GetPid as u64, 0) as i32 +} + +#[no_mangle] +pub extern "C" fn getpid() -> i32 { + _getpid() +} + +#[no_mangle] +pub extern "C" fn _kill(_pid: i32, _sig: i32) -> i32 { + // 未実装 + -1 +} + +#[no_mangle] +pub extern "C" fn kill(pid: i32, sig: i32) -> i32 { + _kill(pid, sig) +} diff --git a/src/user/port.rs b/src/user/port.rs new file mode 100644 index 0000000..40c8293 --- /dev/null +++ b/src/user/port.rs @@ -0,0 +1,77 @@ +//! ポートI/O関連のシステムコール + +use crate::sys::{syscall3, SyscallNumber}; + +/// I/Oポートから1バイト読み取り +#[inline] +pub fn inb(port: u16) -> u8 { + syscall3(SyscallNumber::PortIn as u64, port as u64, 1, 0) as u8 +} + +/// I/Oポートへ1バイト書き込み +#[inline] +pub fn outb(port: u16, value: u8) { + syscall3(SyscallNumber::PortOut as u64, port as u64, value as u64, 1); +} + +/// I/Oポートから2バイト読み取り +#[inline] +pub fn inw(port: u16) -> u16 { + syscall3(SyscallNumber::PortIn as u64, port as u64, 2, 0) as u16 +} + +/// I/Oポートへ2バイト書き込み +#[inline] +pub fn outw(port: u16, value: u16) { + syscall3(SyscallNumber::PortOut as u64, port as u64, value as u64, 2); +} + +/// I/Oポートから4バイト読み取り +#[inline] +pub fn inl(port: u16) -> u32 { + syscall3(SyscallNumber::PortIn as u64, port as u64, 4, 0) as u32 +} + +/// I/Oポートへ4バイト書き込み +#[inline] +pub fn outl(port: u16, value: u32) { + syscall3(SyscallNumber::PortOut as u64, port as u64, value as u64, 4); +} + +/// I/Oポートから16-bitワード列を一括読み取り +#[inline] +pub fn inw_words(port: u16, out: &mut [u16]) -> Result<(), ()> { + if out.is_empty() { + return Ok(()); + } + let ret = syscall3( + SyscallNumber::PortInWords as u64, + port as u64, + out.as_mut_ptr() as u64, + out.len() as u64, + ); + if (ret as i64) < 0 { + Err(()) + } else { + Ok(()) + } +} + +/// I/Oポートへ16-bitワード列を一括書き込み +#[inline] +pub fn outw_words(port: u16, input: &[u16]) -> Result<(), ()> { + if input.is_empty() { + return Ok(()); + } + let ret = syscall3( + SyscallNumber::PortOutWords as u64, + port as u64, + input.as_ptr() as u64, + input.len() as u64, + ); + if (ret as i64) < 0 { + Err(()) + } else { + Ok(()) + } +} diff --git a/src/user/posix_stubs.rs b/src/user/posix_stubs.rs new file mode 100644 index 0000000..204097e --- /dev/null +++ b/src/user/posix_stubs.rs @@ -0,0 +1,624 @@ +//! Linux/POSIX 互換スタブ +//! +//! Rust std (build-std) がリンク時に要求する C ライブラリ関数を実装する。 +//! 各関数は最小限の実装か、成功を返すスタブ。 + +use crate::sys::{syscall1, syscall2, syscall3, syscall4, syscall6, SyscallNumber}; + +// errno +static mut ERRNO_VAL: i32 = 0; + +#[inline] +unsafe fn set_errno(errno: i32) { + ERRNO_VAL = errno; +} + +#[inline] +unsafe fn errno_from_neg_ret(ret: i64) -> i32 { + (-ret) as i32 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __errno_location() -> *mut i32 { + &raw mut ERRNO_VAL +} + +// メモリ管理 + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn mmap( + addr: *mut u8, + len: usize, + prot: i32, + flags: i32, + fd: i32, + offset: i64, +) -> *mut u8 { + let ret = syscall6( + SyscallNumber::Mmap as u64, + addr as u64, + len as u64, + prot as u64, + flags as u64, + fd as u64, + offset as u64, + ); + if ret as i64 == -1 || (ret as i64) < 0 { + usize::MAX as *mut u8 // MAP_FAILED = (void*)-1 + } else { + ret as *mut u8 + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn munmap(addr: *mut u8, len: usize) -> i32 { + let ret = syscall2(SyscallNumber::Munmap as u64, addr as u64, len as u64); + if (ret as i64) < 0 { -1 } else { 0 } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn mprotect(_addr: *mut u8, _len: usize, _prot: i32) -> i32 { + 0 // 成功 +} + +/// C の syscall(nr, arg0, arg1, arg2, arg3, arg4, arg5) の実装 +/// SysV ABI: nr=rdi, arg0=rsi, arg1=rdx, arg2=rcx, arg3=r8, arg4=r9 +#[unsafe(naked)] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn syscall() { + core::arch::naked_asm!( + "mov rax, rdi", // syscall number + "mov rdi, rsi", // arg0 + "mov rsi, rdx", // arg1 + "mov rdx, rcx", // arg2 + "mov r10, r8", // arg3 (Linux: r10, not rcx) + "mov r8, r9", // arg4 + // arg5 would be at [rsp+8] but ignore for now + "syscall", + "ret", + ); +} + + +// 単純なスレッドローカルストレージ (シングルスレッド用) +const MAX_TLS_KEYS: usize = 128; +static mut TLS_VALUES: [*mut u8; MAX_TLS_KEYS] = [core::ptr::null_mut(); MAX_TLS_KEYS]; +static mut TLS_DESTRUCTORS: [Option; MAX_TLS_KEYS] = + [None; MAX_TLS_KEYS]; +static mut TLS_NEXT_KEY: usize = 1; // 0 は無効なキー + +#[unsafe(no_mangle)] +// Provide minimal stubs to satisfy libstd linking on custom target (no real pthread support) +// sched_yield is required by std::sys::thread::unix::yield_now +pub extern "C" fn sched_yield() -> i32 { + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_self() -> u64 { + 1 // スレッド ID = 1 (シングルスレッド) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_key_create( + key_out: *mut u32, + destructor: Option, +) -> i32 { + if TLS_NEXT_KEY >= MAX_TLS_KEYS { + return 12; // ENOMEM + } + let key = TLS_NEXT_KEY as u32; + TLS_NEXT_KEY += 1; + TLS_DESTRUCTORS[key as usize] = destructor; + *key_out = key; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_key_delete(key: u32) -> i32 { + if (key as usize) < MAX_TLS_KEYS { + TLS_VALUES[key as usize] = core::ptr::null_mut(); + TLS_DESTRUCTORS[key as usize] = None; + } + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_getspecific(key: u32) -> *mut u8 { + if (key as usize) < MAX_TLS_KEYS { + TLS_VALUES[key as usize] + } else { + core::ptr::null_mut() + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_setspecific(key: u32, val: *const u8) -> i32 { + if (key as usize) < MAX_TLS_KEYS { + TLS_VALUES[key as usize] = val as *mut u8; + 0 + } else { + 22 // EINVAL + } +} + +/// pthread_attr_t の最小実装 (128 バイトのダミー構造) +#[repr(C)] +pub struct PthreadAttr { + _data: [u8; 64], +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_attr_init(attr: *mut PthreadAttr) -> i32 { + if !attr.is_null() { + core::ptr::write_bytes(attr as *mut u8, 0, size_of::()); + } + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_attr_destroy(_attr: *mut PthreadAttr) -> i32 { + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_attr_getguardsize( + _attr: *const PthreadAttr, + size_out: *mut usize, +) -> i32 { + if !size_out.is_null() { + *size_out = 4096; + } + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_attr_getstack( + _attr: *const PthreadAttr, + stack_addr_out: *mut *mut u8, + stack_size_out: *mut usize, +) -> i32 { + if !stack_addr_out.is_null() { + *stack_addr_out = core::ptr::null_mut(); + } + if !stack_size_out.is_null() { + *stack_size_out = 0; + } + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_getattr_np( + _thread: u64, + attr: *mut PthreadAttr, +) -> i32 { + pthread_attr_init(attr) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sigaction( + _signum: i32, + _act: *const u8, + _oldact: *mut u8, +) -> i32 { + 0 // 成功 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sigaltstack(_ss: *const u8, _oss: *mut u8) -> i32 { + 0 +} + +/// nanosleep(req, rem) - 簡易実装 (yield で代用) +#[unsafe(no_mangle)] +pub unsafe extern "C" fn nanosleep(_req: *const u8, _rem: *mut u8) -> i32 { + // 少しだけ yield + for _ in 0..10 { + syscall1(SyscallNumber::Yield as u64, 0); + } + 0 +} + +/// pause() - シグナル待ち (実装なし: すぐリターン) +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pause() -> i32 { + -1 // EINTR +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fcntl(fd: i32, cmd: i32, arg: i64) -> i32 { + let ret = syscall3( + SyscallNumber::Fcntl as u64, + fd as u64, + cmd as u64, + arg as u64, + ) as i64; + if ret < 0 { + set_errno(errno_from_neg_ret(ret)); + -1 + } else { + ret as i32 + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pipe2(_pipefd: *mut i32, _flags: i32) -> i32 { + -1 // ENOSYS +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn recv( + _fd: i32, + _buf: *mut u8, + _len: usize, + _flags: i32, +) -> isize { + -1 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn socketpair( + _domain: i32, + _type_: i32, + _protocol: i32, + _sv: *mut i32, +) -> i32 { + -1 // ENOSYS +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getauxval(_type_: u64) -> u64 { + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn waitpid(_pid: i32, _status: *mut i32, _options: i32) -> i32 { + -1 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn dup(_oldfd: i32) -> i32 { + -1 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn dup2(_oldfd: i32, _newfd: i32) -> i32 { + -1 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn chdir(_path: *const u8) -> i32 { + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn chroot(_path: *const u8) -> i32 { + -1 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn setuid(_uid: u32) -> i32 { + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn setgid(_gid: u32) -> i32 { + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn setgroups(_size: usize, _list: *const u32) -> i32 { + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawn_file_actions_init(_actions: *mut u8) -> i32 { + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawn_file_actions_adddup2( + _actions: *mut u8, _fd: i32, _newfd: i32, +) -> i32 { + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawnattr_init(_attr: *mut u8) -> i32 { + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawnattr_setpgroup(_attr: *mut u8, _pgroup: i32) -> i32 { + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawnattr_setsigdefault( + _attr: *mut u8, _sigset: *const u8, +) -> i32 { + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawnattr_setflags(_attr: *mut u8, _flags: i16) -> i32 { + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawn( + pid_out: *mut i32, + path: *const u8, + _file_actions: *const u8, + _attr: *const u8, + _argv: *const *const u8, + _envp: *const *const u8, +) -> i32 { + // カーネルの Exec syscall (516) を使って新しいプロセスを生成する + // 第2引数(args_ptr)は未使用でも0を明示してABIを安定化させる + let ret = syscall2(SyscallNumber::Exec as u64, path as u64, 0); + let ret_i64 = ret as i64; + if ret_i64 < 0 { + return (-ret_i64) as i32; // posix_spawn は正のerror numberを返す + } + if !pid_out.is_null() { + *pid_out = ret as i32; + } + 0 // 成功 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawnp( + pid_out: *mut i32, + file: *const u8, + file_actions: *const u8, + attr: *const u8, + argv: *const *const u8, + envp: *const *const u8, +) -> i32 { + posix_spawn(pid_out, file, file_actions, attr, argv, envp) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sigemptyset(set: *mut u8) -> i32 { + if !set.is_null() { + core::ptr::write_bytes(set, 0, 128); // sigset_t は最大 128 バイト + } + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sigaddset(_set: *mut u8, _signum: i32) -> i32 { + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn recvmsg(_fd: i32, _msg: *mut u8, _flags: i32) -> isize { + -1 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sendmsg(_fd: i32, _msg: *const u8, _flags: i32) -> isize { + -1 +} + +/// sysconf - システム設定値を取得 +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sysconf(name: i32) -> i64 { + const SC_PAGESIZE: i32 = 30; + const SC_NPROCESSORS_ONLN: i32 = 84; + const SC_NPROCESSORS_CONF: i32 = 83; + const SC_GETPW_R_SIZE_MAX: i32 = 70; + const SC_GETGR_R_SIZE_MAX: i32 = 69; + const SC_OPEN_MAX: i32 = 4; + const SC_CLK_TCK: i32 = 2; + + match name { + SC_PAGESIZE => 4096, + SC_NPROCESSORS_ONLN | SC_NPROCESSORS_CONF => 1, + SC_GETPW_R_SIZE_MAX | SC_GETGR_R_SIZE_MAX => 1024, + SC_OPEN_MAX => 256, + SC_CLK_TCK => 100, + _ => -1, + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn setpgid(_pid: i32, _pgid: i32) -> i32 { 0 } + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn setsid() -> i32 { 1 } + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn execvp(_file: *const u8, _argv: *const *const u8) -> i32 { -1 } + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn waitid(_which: i32, _id: u32, _infop: *mut u8, _options: i32) -> i32 { -1 } + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn poll(fds: *mut u8, nfds: u64, timeout: i32) -> i32 { + let ret = syscall3( + SyscallNumber::Poll as u64, + fds as u64, + nfds, + timeout as u64, + ) as i64; + if ret < 0 { + set_errno(errno_from_neg_ret(ret)); + -1 + } else { + ret as i32 + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn ioctl(fd: i32, request: u64, arg: u64) -> i32 { + let ret = syscall3( + SyscallNumber::Ioctl as u64, + fd as u64, + request, + arg, + ) as i64; + if ret < 0 { + set_errno(errno_from_neg_ret(ret)); + -1 + } else { + ret as i32 + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawn_file_actions_destroy(_actions: *mut u8) -> i32 { 0 } + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawn_file_actions_addchdir_np( + _actions: *mut u8, _path: *const u8, +) -> i32 { 0 } + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawnattr_destroy(_attr: *mut u8) -> i32 { 0 } + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_memalign( + memptr: *mut *mut u8, alignment: usize, size: usize, +) -> i32 { + // POSIX: alignment は 2 のべき乗かつ sizeof(void*) の倍数である必要がある。 + if memptr.is_null() + || alignment == 0 + || !alignment.is_power_of_two() + || (alignment % core::mem::size_of::<*mut u8>()) != 0 + { + return 22; // EINVAL + } + + let ptr = crate::libc::memalign(alignment, size); + if ptr.is_null() { + return 12; // ENOMEM + } + *memptr = ptr; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn clock_gettime(_clk_id: i32, tp: *mut u8) -> i32 { + // timespec: { tv_sec: i64, tv_nsec: i64 } + // タイマーティック (1ティック = 1ms) から計算 + let ticks = syscall1(SyscallNumber::GetTicks as u64, 0); + let sec = (ticks / 1000) as i64; + let nsec = ((ticks % 1000) * 1_000_000) as i64; + if !tp.is_null() { + core::ptr::write(tp as *mut i64, sec); + core::ptr::write((tp as *mut i64).add(1), nsec); + } + 0 +} + +// ── ファイル I/O(std がリンク時に要求する基本 POSIX 関数)──────────────── + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn open(path: *const u8, flags: i32, _mode: u32) -> i32 { + let ret = syscall2(SyscallNumber::Open as u64, path as u64, flags as u64); + let ret_i64 = ret as i64; + if ret_i64 < 0 { + set_errno(errno_from_neg_ret(ret_i64)); + -1 + } else { + ret as i32 + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn open64(path: *const u8, flags: i32, mode: u32) -> i32 { + open(path, flags, mode) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __libc_open64(path: *const u8, flags: i32, mode: u32) -> i32 { + open(path, flags, mode) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn openat(dirfd: i32, path: *const u8, flags: i32, mode: u32) -> i32 { + const SYS_OPENAT: u64 = 257; + let ret = syscall4( + SYS_OPENAT, + dirfd as i64 as u64, + path as u64, + flags as u64, + mode as u64, + ) as i64; + if ret < 0 { + set_errno(errno_from_neg_ret(ret)); + -1 + } else { + ret as i32 + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn openat64(dirfd: i32, path: *const u8, flags: i32, mode: u32) -> i32 { + openat(dirfd, path, flags, mode) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fstat64(fd: i32, stat: *mut u8) -> i32 { + let ret = syscall2(SyscallNumber::Fstat as u64, fd as u64, stat as u64) as i64; + if ret < 0 { + set_errno(errno_from_neg_ret(ret)); + -1 + } else { + ret as i32 + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn stat64(path: *const u8, stat: *mut u8) -> i32 { + let ret = syscall2(SyscallNumber::Stat as u64, path as u64, stat as u64) as i64; + if ret < 0 { + set_errno(errno_from_neg_ret(ret)); + -1 + } else { + ret as i32 + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lstat64(path: *const u8, stat: *mut u8) -> i32 { + let ret = syscall2(SyscallNumber::Lstat as u64, path as u64, stat as u64) as i64; + if ret < 0 { + set_errno(errno_from_neg_ret(ret)); + -1 + } else { + ret as i32 + } +} + +/// `iovec` structure for writev +#[repr(C)] +struct IoVec { + iov_base: *const u8, + iov_len: usize, +} + +#[unsafe(no_mangle)] +#[allow(private_interfaces)] +pub unsafe extern "C" fn writev(fd: i32, iov: *const IoVec, iovcnt: i32) -> isize { + let mut total: isize = 0; + for i in 0..iovcnt { + let v = &*iov.add(i as usize); + if v.iov_len == 0 { + continue; + } + let ret = crate::sys::syscall3( + SyscallNumber::Write as u64, + fd as u64, + v.iov_base as u64, + v.iov_len as u64, + ); + if (ret as i64) < 0 { + return if total == 0 { -1 } else { total }; + } + total += ret as isize; + } + total +} diff --git a/src/user/privileged.rs b/src/user/privileged.rs new file mode 100644 index 0000000..53e56a7 --- /dev/null +++ b/src/user/privileged.rs @@ -0,0 +1,141 @@ +//! 特権システムコール(Service権限プロセス専用) + +use super::sys::{syscall2, syscall3, syscall4, SyscallNumber}; + +/// 物理ページ配列をターゲットプロセスのアドレス空間にマップ +/// +/// **Service権限専用**: PrivilegeLevel::Service以外から呼び出すとEPERMを返す +/// +/// # Arguments +/// * `target_thread_id` - マップ先のスレッドID +/// * `phys_pages` - 物理ページアドレス配列 (各要素は4KBページの物理アドレス) +/// * `virt_addr_hint` - 仮想アドレスのヒント (0=自動割り当て) +/// +/// # Returns +/// 成功時: マップされた仮想アドレス +/// エラー時: 負のエラーコード(u64としてキャスト) +/// +/// # Safety +/// - 物理ページアドレスが有効であることを呼び出し側で保証する必要がある +/// - マップされたメモリへのアクセスは適切な同期が必要 +pub unsafe fn map_physical_pages( + target_thread_id: u64, + phys_pages: &[u64], + virt_addr_hint: u64, +) -> u64 { + syscall4( + SyscallNumber::MapPhysicalPages as u64, + target_thread_id, + phys_pages.as_ptr() as u64, + phys_pages.len() as u64, + virt_addr_hint, + ) +} + +/// 仮想アドレスから物理アドレスを取得 +/// +/// **Service権限専用**: PrivilegeLevel::Service以外から呼び出すとEPERMを返す +/// +/// # Arguments +/// * `virt_addr` - 変換したい仮想アドレス +/// * `target_thread_id` - 対象スレッドID (0=自プロセス) +/// +/// # Returns +/// 成功時: 物理アドレス +/// エラー時: 負のエラーコード(u64としてキャスト) +pub fn get_physical_addr(virt_addr: u64, target_thread_id: u64) -> u64 { + syscall2( + SyscallNumber::GetPhysicalAddr as u64, + virt_addr, + target_thread_id, + ) +} + +/// 共有用物理ページを割り当て、自プロセスにマップして物理アドレスを返す +/// +/// **Service権限専用**: PrivilegeLevel::Service以外から呼び出すとEPERMを返す +/// +/// # Arguments +/// * `page_count` - 割り当てるページ数(最大128) +/// * `phys_addrs_out` - 物理アドレス配列を書き込むバッファ(Option、Noneなら書き込まない) +/// * `virt_addr_hint` - 仮想アドレスのヒント (0=自動割り当て) +/// +/// # Returns +/// 成功時: マップされた仮想アドレス +/// エラー時: 負のエラーコード(u64としてキャスト) +/// +/// # Safety +/// - 返された仮想アドレス範囲は適切に管理する必要がある +/// - 使用後は `unmap_pages` で解放すること +pub unsafe fn alloc_shared_pages( + page_count: u64, + phys_addrs_out: Option<&mut [u64]>, + virt_addr_hint: u64, +) -> u64 { + const EINVAL: u64 = (-22i64) as u64; + + let (ptr, out_len) = match phys_addrs_out { + Some(slice) => { + if (slice.len() as u64) < page_count { + return EINVAL; + } + (slice.as_mut_ptr() as u64, slice.len() as u64) + } + None => (0, 0), + }; + + syscall4( + SyscallNumber::AllocSharedPages as u64, + page_count, + ptr, + out_len, + virt_addr_hint, + ) +} + +/// 物理ページをアンマップして解放 +/// +/// **Service権限専用**: PrivilegeLevel::Service以外から呼び出すとEPERMを返す +/// +/// # Arguments +/// * `virt_addr` - アンマップする仮想アドレス(ページ境界) +/// * `page_count` - アンマップするページ数 +/// * `deallocate` - true=物理ページも解放、false=アンマップのみ +/// +/// # Returns +/// 成功時: 0 +/// エラー時: 負のエラーコード(u64としてキャスト) +pub fn unmap_pages(virt_addr: u64, page_count: u64, deallocate: bool) -> u64 { + syscall3( + SyscallNumber::UnmapPages as u64, + virt_addr, + page_count, + if deallocate { 1 } else { 0 }, + ) +} + +/// IPC経由で物理ページをターゲットプロセスへ送信 +/// +/// **Service権限専用**: PrivilegeLevel::Service以外から呼び出すとEPERMを返す +/// +/// # Arguments +/// * `dest_thread_id` - 送信先スレッドID +/// * `phys_pages` - 物理ページアドレス配列 +/// * `map_start` - マップ先の仮想アドレスヒント (0=自動) +/// +/// # Returns +/// 成功時: 0 +/// エラー時: 負のエラーコード(u64としてキャスト) +/// +/// # Safety +/// - 送信先プロセスが存在すること +/// - 物理ページが有効であること +pub unsafe fn ipc_send_pages(dest_thread_id: u64, phys_pages: &[u64], map_start: u64) -> u64 { + syscall4( + SyscallNumber::IpcSendPages as u64, + dest_thread_id, + phys_pages.as_ptr() as u64, + phys_pages.len() as u64, + map_start, + ) +} diff --git a/src/user/process.rs b/src/user/process.rs new file mode 100644 index 0000000..f71f9ff --- /dev/null +++ b/src/user/process.rs @@ -0,0 +1,236 @@ +//! プロセス管理関連のシステムコール + +use super::sys::{syscall2, SyscallNumber}; + +/// 実行可能ファイルを起動する +/// パスから新しいプロセスを起動し、そのPIDを返す +pub fn exec(path: &str) -> Result { + // null終端文字列を作成 + let mut path_buf = [0u8; 256]; + let path_bytes = path.as_bytes(); + if path_bytes.len() >= 255 { + return Err(()); + } + path_buf[..path_bytes.len()].copy_from_slice(path_bytes); + path_buf[path_bytes.len()] = 0; + + let result = syscall2( + SyscallNumber::Exec as u64, + path_buf.as_ptr() as u64, + 0, + ); + + if (result as i64) < 0 { + Err(()) + } else { + Ok(result) + } +} + +/// 引数付きで実行可能ファイルを起動する +/// args: 各引数のスライス(argv[0] = path は自動で設定) +pub fn exec_with_args(path: &str, args: &[&str]) -> Result { + let mut path_buf = [0u8; 256]; + let path_bytes = path.as_bytes(); + if path_bytes.len() >= 255 { + return Err(()); + } + path_buf[..path_bytes.len()].copy_from_slice(path_bytes); + path_buf[path_bytes.len()] = 0; + + // ヌル区切り引数文字列: "arg1\0arg2\0\0" + let mut args_buf = [0u8; 512]; + let mut pos = 0usize; + for arg in args { + let b = arg.as_bytes(); + if pos + b.len() + 2 > args_buf.len() { + break; + } + args_buf[pos..pos + b.len()].copy_from_slice(b); + pos += b.len(); + args_buf[pos] = 0; // null terminate arg + pos += 1; + } + args_buf[pos] = 0; // double-null = end of args + + let result = syscall2( + SyscallNumber::Exec as u64, + path_buf.as_ptr() as u64, + if args.is_empty() { 0 } else { args_buf.as_ptr() as u64 }, + ); + + if (result as i64) < 0 { + Err(()) + } else { + Ok(result) + } +} + +/// メモリ上の ELF データから新プロセスを起動する +pub fn exec_from_buffer(elf_data: &[u8]) -> Result { + use super::sys::syscall2; + let result = syscall2( + SyscallNumber::ExecFromBuffer as u64, + elf_data.as_ptr() as u64, + elf_data.len() as u64, + ); + if (result as i64) < 0 { + Err(()) + } else { + Ok(result) + } +} + +/// メモリ上の ELF データと実行パス名から新プロセスを起動する +pub fn exec_from_buffer_named(path: &str, elf_data: &[u8]) -> Result { + use super::sys::syscall3; + + let mut path_buf = [0u8; 256]; + let path_bytes = path.as_bytes(); + if path_bytes.is_empty() || path_bytes.len() >= 255 { + return Err(-22); + } + path_buf[..path_bytes.len()].copy_from_slice(path_bytes); + path_buf[path_bytes.len()] = 0; + + let result = syscall3( + SyscallNumber::ExecFromBufferNamed as u64, + elf_data.as_ptr() as u64, + elf_data.len() as u64, + path_buf.as_ptr() as u64, + ); + if (result as i64) < 0 { + Err(result as i64) + } else { + Ok(result) + } +} + +/// メモリ上の ELF データと実行パス名・引数から新プロセスを起動する +pub fn exec_from_buffer_named_with_args( + path: &str, + elf_data: &[u8], + args: &[&str], +) -> Result { + use super::sys::syscall4; + + let mut path_buf = [0u8; 256]; + let path_bytes = path.as_bytes(); + if path_bytes.is_empty() || path_bytes.len() >= 255 { + return Err(-22); + } + path_buf[..path_bytes.len()].copy_from_slice(path_bytes); + path_buf[path_bytes.len()] = 0; + + // ヌル区切り引数文字列: "arg1\0arg2\0\0" + let mut args_buf = [0u8; 512]; + let mut pos = 0usize; + for arg in args { + let b = arg.as_bytes(); + if pos + b.len() + 2 > args_buf.len() { + return Err(-22); + } + args_buf[pos..pos + b.len()].copy_from_slice(b); + pos += b.len(); + args_buf[pos] = 0; + pos += 1; + } + args_buf[pos] = 0; + let args_ptr = if args.is_empty() { 0 } else { args_buf.as_ptr() as u64 }; + + let result = syscall4( + SyscallNumber::ExecFromBufferNamedArgs as u64, + elf_data.as_ptr() as u64, + elf_data.len() as u64, + path_buf.as_ptr() as u64, + args_ptr, + ); + if (result as i64) < 0 { + Err(result as i64) + } else { + Ok(result) + } +} + +/// メモリ上の ELF データと実行パス名・引数・要求元スレッドIDから新プロセスを起動する +pub fn exec_from_buffer_named_with_args_and_requester( + path: &str, + elf_data: &[u8], + args: &[&str], + requester_tid: u64, +) -> Result { + use super::sys::syscall5; + + let mut path_buf = [0u8; 256]; + let path_bytes = path.as_bytes(); + if path_bytes.is_empty() || path_bytes.len() >= 255 { + return Err(-22); + } + path_buf[..path_bytes.len()].copy_from_slice(path_bytes); + path_buf[path_bytes.len()] = 0; + + let mut args_buf = [0u8; 512]; + let mut pos = 0usize; + for arg in args { + let b = arg.as_bytes(); + if pos + b.len() + 2 > args_buf.len() { + return Err(-22); + } + args_buf[pos..pos + b.len()].copy_from_slice(b); + pos += b.len(); + args_buf[pos] = 0; + pos += 1; + } + args_buf[pos] = 0; + let args_ptr = if args.is_empty() { 0 } else { args_buf.as_ptr() as u64 }; + + let result = syscall5( + SyscallNumber::ExecFromBufferNamedArgsWithRequester as u64, + elf_data.as_ptr() as u64, + elf_data.len() as u64, + path_buf.as_ptr() as u64, + args_ptr, + requester_tid, + ); + if (result as i64) < 0 { + Err(result as i64) + } else { + Ok(result) + } +} + +/// Request kernel to exec via streamed image path (mapped-write zero-copy preferred) +pub fn exec_via_fs_stream(path: &str, args: &[&str]) -> Result { + use super::sys::syscall2; + + let mut path_buf = [0u8; 256]; + let path_bytes = path.as_bytes(); + if path_bytes.is_empty() || path_bytes.len() >= 255 { + return Err(-22); + } + path_buf[..path_bytes.len()].copy_from_slice(path_bytes); + path_buf[path_bytes.len()] = 0; + + // build nul-separated args buffer + let mut args_buf = [0u8; 512]; + let mut pos = 0usize; + for arg in args { + let b = arg.as_bytes(); + if pos + b.len() + 2 > args_buf.len() { + return Err(-22); + } + args_buf[pos..pos + b.len()].copy_from_slice(b); + pos += b.len(); + args_buf[pos] = 0; + pos += 1; + } + args_buf[pos] = 0; + let args_ptr = if args.is_empty() { 0 } else { args_buf.as_ptr() as u64 }; + + let res = syscall2( + SyscallNumber::ExecFromFsStream as u64, + path_buf.as_ptr() as u64, + args_ptr, + ); + if (res as i64) < 0 { Err(res as i64) } else { Ok(res) } +} diff --git a/src/user/stub.rs b/src/user/stub.rs new file mode 100644 index 0000000..9d9532f --- /dev/null +++ b/src/user/stub.rs @@ -0,0 +1,20 @@ +//! ユーザー側システムコールスタブ +//! +//! 種類ごとのモジュールに分割し、ここから再エクスポートする。 + +pub mod ipc; +pub mod task; +pub mod time; +pub mod io; + +// CランタイムとNewlibサポート +// mod crt; // crtは個別にコンパイルする +pub mod newlib; + +mod sys; + +pub use sys::SyscallNumber; +pub use ipc::{ipc_recv, ipc_send}; +pub use task::{yield_now, exit, current_thread_id, thread_id_by_name}; +pub use time::get_ticks; +pub use io::{exit, print, read, write, write_stderr, write_stdout, STDERR, STDIN, STDOUT}; diff --git a/src/user/sys.rs b/src/user/sys.rs new file mode 100644 index 0000000..46ac5f2 --- /dev/null +++ b/src/user/sys.rs @@ -0,0 +1,288 @@ +//! ユーザー側システムコール共通部 + +use core::arch::asm; + +/// システムコール番号 (Linux x86_64 互換) +#[repr(u64)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SyscallNumber { + /// 読み込み + Read = 0, + /// 書き込み + Write = 1, + /// poll + Poll = 7, + /// ioctl + Ioctl = 16, + /// ベクタ書き込み + Writev = 20, + /// select + Select = 23, + /// ファイルを開く + Open = 2, + /// ファイルを閉じる + Close = 3, + /// ファイル情報取得 (path) + Stat = 4, + /// ファイル情報取得 + Fstat = 5, + /// lstat (stat のシンボリックリンク非追跡版) + Lstat = 6, + /// ファイルシーク + Lseek = 8, + /// メモリマップ + Mmap = 9, + /// メモリアンマップ + Munmap = 11, + /// メモリブレーク + Brk = 12, + /// シグナル処理(スタブ) + RtSigaction = 13, + /// シグナルリターン + RtSigreturn = 15, + /// シグナルマスク(スタブ) + RtSigprocmask = 14, + /// Fork + Fork = 57, + /// プロセス終了 + Exit = 60, + /// Wait + Wait = 61, + /// 現在のプロセスIDを取得 + GetPid = 39, + /// 現在のスレッドIDを取得 + GetTid = 186, + /// clone (スレッド生成) + Clone = 56, + /// arch_prctl (TLS用FSベース設定) + ArchPrctl = 158, + /// clock_gettime + ClockGettime = 228, + /// futex + Futex = 202, + /// exit_group + ExitGroup = 231, + /// kill (シグナルを送る) + Kill = 62, + /// fcntl + Fcntl = 72, + /// tkill (スレッドID宛てシグナル) + Tkill = 200, + /// tgkill (スレッドグループ+スレッドID宛てシグナル) + Tgkill = 234, + /// getcwd + Getcwd = 79, + /// unlink (ファイル削除) + Unlink = 87, + + // mochiOS独自syscall (Linux未使用番号帯を使用: 512+) + /// スケジューラへ譲る + Yield = 512, + /// タイマーティック数を取得 + GetTicks = 513, + /// IPC送信 + IpcSend = 514, + /// IPC受信 + IpcRecv = 515, + /// initfsから実行可能ファイルを実行 + Exec = 516, + /// スリープ (ms単位) + Sleep = 517, + /// 名前からプロセスIDを検索 + FindProcessByName = 518, + /// カーネルログを出力 + Log = 519, + /// I/Oポート入力 + PortIn = 520, + /// I/Oポート出力 + PortOut = 521, + /// ディレクトリ作成 + Mkdir = 522, + /// ディレクトリ削除 + Rmdir = 523, + /// ディレクトリエントリ読み取り + Readdir = 524, + /// カレントディレクトリ変更 + Chdir = 525, + /// キーボードから1文字読み取る(ユーザー側) + KeyboardRead = 526, +/// スレッドIDからプロセスの権限レベルを取得 (0=Core, 1=Service, 2=User) + GetThreadPrivilege = 527, + /// フレームバッファ情報取得 + GetFramebufferInfo = 528, + /// フレームバッファをマップ + MapFramebuffer = 529, + /// メモリ上の ELF バッファから新プロセスを起動 + ExecFromBuffer = 530, + /// コンソールカーソルのピクセルY位置を設定 + SetConsoleCursor = 531, + /// コンソールカーソルのピクセルY位置を取得 + GetConsoleCursor = 532, + /// IPC受信(ブロッキング版):メッセージが届くまでスリープして待機 + IpcRecvWait = 533, + /// キーボード入力の監視用タップ(通常入力を消費しない) + KeyboardReadTap = 534, + /// PS/2 マウスの3バイトパケットを読み取る(b0 | b1<<8 | b2<<16) + MouseRead = 535, + /// 物理アドレス範囲をユーザー空間にマップ + MapPhysicalRange = 536, + /// ユーザー仮想アドレスを物理アドレスへ変換 + VirtToPhys = 537, + /// I/Oポートから 16-bit ワード列を一括読み取り + PortInWords = 538, + /// I/Oポートへ 16-bit ワード列を一括書き込み + PortOutWords = 539, + /// キーボード入力キューへ raw スキャンコードを注入 + KeyboardInject = 540, + /// マウス入力キューへ 3バイトパケットを注入 + MouseInject = 541, + /// メモリ上の ELF バッファと実行パス名から新プロセスを起動 + ExecFromBufferNamed = 542, + /// メモリ上の ELF バッファと実行パス名+引数から新プロセスを起動 + ExecFromBufferNamedArgs = 543, + /// メモリ上の ELF バッファと実行パス名+引数+要求元スレッドIDから新プロセスを起動 + ExecFromBufferNamedArgsWithRequester = 544, + /// FS 経由のストリーム exec(マップ書き込みを試行) + ExecFromFsStream = 545, + /// 物理ページ配列をターゲットプロセスのアドレス空間にマップ(Service権限専用) + MapPhysicalPages = 546, + /// 仮想アドレスから物理アドレスを取得(Service権限専用) + GetPhysicalAddr = 547, + /// 共有用物理ページを割り当て、自プロセスにマップして物理アドレスを返す(Service権限専用) + AllocSharedPages = 548, + /// 物理ページをアンマップして解放(Service権限専用) + UnmapPages = 549, + /// IPC経由で物理ページをターゲットプロセスへ送信(Service権限専用) + IpcSendPages = 550, + /// PS/2 マウスの3バイトパケットを読み取る(ブロッキング版) + MouseReadWait = 551, + /// プロセス一覧を取得(ユーザーバッファへ書き込む) + ListProcesses = 552, + /// 重力が存在するか + CheckGravityExist = 999, +} + +/// 操作が許可されていない +pub const EPERM: u64 = (-1i64) as u64; +/// 無効な引数 +pub const EINVAL: u64 = (-22i64) as u64; +/// 入力が空 +pub const ENODATA: u64 = (-61i64) as u64; + +#[inline(always)] +pub(crate) fn syscall0(num: u64) -> u64 { + let ret: u64; + unsafe { + asm!( + "int 0x80", + inlateout("rax") num => ret, + options(nostack, preserves_flags) + ); + } + ret +} + +#[inline(always)] +pub(crate) fn syscall1(num: u64, arg0: u64) -> u64 { + let ret: u64; + unsafe { + asm!( + "int 0x80", + inlateout("rax") num => ret, + in("rdi") arg0, + options(nostack, preserves_flags) + ); + } + ret +} + +#[inline(always)] +pub(crate) fn syscall2(num: u64, arg0: u64, arg1: u64) -> u64 { + let ret: u64; + unsafe { + asm!( + "int 0x80", + inlateout("rax") num => ret, + in("rdi") arg0, + in("rsi") arg1, + options(nostack, preserves_flags) + ); + } + ret +} + +#[inline(always)] +#[allow(dead_code)] +pub(crate) fn syscall3(num: u64, arg0: u64, arg1: u64, arg2: u64) -> u64 { + let ret: u64; + unsafe { + asm!( + "int 0x80", + inlateout("rax") num => ret, + in("rdi") arg0, + in("rsi") arg1, + in("rdx") arg2, + options(nostack, preserves_flags) + ); + } + ret +} + +#[inline(always)] +#[allow(dead_code)] +pub(crate) fn syscall4(num: u64, arg0: u64, arg1: u64, arg2: u64, arg3: u64) -> u64 { + let ret: u64; + unsafe { + asm!( + "int 0x80", + inlateout("rax") num => ret, + in("rdi") arg0, + in("rsi") arg1, + in("rdx") arg2, + in("r10") arg3, + options(nostack, preserves_flags) + ); + } + ret +} + +#[inline(always)] +#[allow(dead_code)] +pub(crate) fn syscall5(num: u64, arg0: u64, arg1: u64, arg2: u64, arg3: u64, arg4: u64) -> u64 { + let ret: u64; + unsafe { + asm!( + "int 0x80", + inlateout("rax") num => ret, + in("rdi") arg0, + in("rsi") arg1, + in("rdx") arg2, + in("r10") arg3, + in("r8") arg4, + options(nostack, preserves_flags) + ); + } + ret +} + +#[inline(always)] +#[allow(dead_code)] +pub(crate) fn syscall6( + num: u64, arg0: u64, arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64, +) -> u64 { + let ret: u64; + unsafe { + asm!( + "int 0x80", + inlateout("rax") num => ret, + in("rdi") arg0, + in("rsi") arg1, + in("rdx") arg2, + in("r10") arg3, + in("r8") arg4, + in("r9") arg5, + options(nostack, preserves_flags) + ); + } + ret +} diff --git a/src/user/task.rs b/src/user/task.rs new file mode 100644 index 0000000..7a0aeac --- /dev/null +++ b/src/user/task.rs @@ -0,0 +1,120 @@ +//! タスク系システムコール(ユーザー側) + +use super::sys::{syscall0, syscall1, SyscallNumber}; + +/// スケジューラに実行権を譲る +pub fn yield_now() { + let _ = syscall0(SyscallNumber::Yield as u64); +} + +/// 現在のプロセスIDを取得 +pub fn getpid() -> u64 { + syscall0(SyscallNumber::GetPid as u64) +} + +/// 現在のスレッドIDを取得 +pub fn gettid() -> u64 { + syscall0(SyscallNumber::GetTid as u64) +} + +/// 指定されたミリ秒数の間スリープする +pub fn sleep(milliseconds: u64) { + let _ = syscall1(SyscallNumber::Sleep as u64, milliseconds); +} + +/// プロセスをフォークする(未実装) +pub fn fork() -> i64 { + let ret = syscall0(SyscallNumber::Fork as u64); + if (ret as i64) < 0 { + -1 + } else { + ret as i64 + } +} + +/// 子プロセスの終了を待つ +pub fn wait(pid: i64) -> (i64, i32) { + let ret = syscall1(SyscallNumber::Wait as u64, pid as u64); + if (ret as i64) < 0 { + (-1, 0) + } else { + (ret as i64, 0) + } +} + +/// 子プロセスの終了を非ブロッキングで確認する(WNOHANG) +/// +/// 戻り値: +/// - `WaitNonblockingStatus::Exited(pid)`: 子プロセス終了済み +/// - `WaitNonblockingStatus::Running`: 子プロセスはまだ実行中 +/// - `WaitNonblockingStatus::NoChild`: 対象子プロセスなし +/// - `WaitNonblockingStatus::Error(errno)`: その他エラー +pub fn wait_nonblocking(pid: i64) -> WaitNonblockingStatus { + wait_nonblocking_status(pid) +} + +/// `wait(WNOHANG)` の詳細結果 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum WaitNonblockingStatus { + /// 子プロセスが終了済み(回収された PID) + Exited(i64), + /// 対象子プロセスはまだ実行中 + Running, + /// 対象に一致する子プロセスが存在しない(ECHILD) + NoChild, + /// その他のエラー(負の errno) + Error(i64), +} + +/// 子プロセスの終了を非ブロッキングで確認する(WNOHANG, 詳細版) +pub fn wait_nonblocking_status(pid: i64) -> WaitNonblockingStatus { + use super::sys::syscall3; + const WNOHANG: u64 = 0x1; + const ECHILD: i64 = -10; + let ret = syscall3(SyscallNumber::Wait as u64, pid as u64, 0, WNOHANG); + let ret_i64 = ret as i64; + if ret_i64 > 0 { + WaitNonblockingStatus::Exited(ret_i64) + } else if ret_i64 == 0 { + WaitNonblockingStatus::Running + } else if ret_i64 == ECHILD { + WaitNonblockingStatus::NoChild + } else { + WaitNonblockingStatus::Error(ret_i64) + } +} + +/// プロセスを終了する +pub fn exit(code: i32) -> ! { + let _ = syscall1(SyscallNumber::Exit as u64, code as u64); + loop { + core::hint::spin_loop(); + } +} + +/// スレッドIDからプロセスの権限レベルを取得 +/// +/// # 戻り値 +/// 0=Core, 1=Service, 2=User, または u64::MAX (エラー) +pub fn get_thread_privilege(tid: u64) -> u64 { + syscall1(SyscallNumber::GetThreadPrivilege as u64, tid) +} + +/// 名前でプロセスを検索し、そのスレッドIDを返す(見つからなければ None) +pub fn find_process_by_name(name: &str) -> Option { + use super::sys::syscall2; + let bytes = name.as_bytes(); + if bytes.is_empty() || bytes.len() > 64 { + return None; + } + let ret = syscall2( + SyscallNumber::FindProcessByName as u64, + bytes.as_ptr() as u64, + bytes.len() as u64, + ); + if ret == 0 { + None + } else { + Some(ret) + } +} diff --git a/src/user/time.rs b/src/user/time.rs new file mode 100644 index 0000000..c02e1c2 --- /dev/null +++ b/src/user/time.rs @@ -0,0 +1,13 @@ +//! 時刻系システムコール(ユーザー側) + +use super::sys::{syscall0, syscall1, SyscallNumber}; + +/// タイマーティック数を取得 +pub fn get_ticks() -> u64 { + syscall0(SyscallNumber::GetTicks as u64) +} + +/// ミリ秒単位でスリープ +pub fn sleep_ms(ms: u64) { + syscall1(SyscallNumber::Sleep as u64, ms); +} diff --git a/src/user/vga.rs b/src/user/vga.rs new file mode 100644 index 0000000..d4202ce --- /dev/null +++ b/src/user/vga.rs @@ -0,0 +1,141 @@ +//! フレームバッファアクセス + +#[cfg(not(feature = "hosted-vga"))] +use crate::sys::{syscall0, syscall1, SyscallNumber}; +#[cfg(feature = "hosted-vga")] +use alloc::vec; +#[cfg(feature = "hosted-vga")] +use alloc::vec::Vec; +#[cfg(feature = "hosted-vga")] +use alloc::format; + +#[cfg(feature = "hosted-vga")] +extern crate std; +#[cfg(feature = "hosted-vga")] +use std::fs::File; +#[cfg(feature = "hosted-vga")] +use std::io::Write; +#[cfg(feature = "hosted-vga")] +use std::sync::Mutex; + +/// フレームバッファ情報 +#[repr(C)] +#[derive(Clone, Copy)] +pub struct FbInfo { + pub width: u32, + pub height: u32, + /// 1行あたりの u32 ピクセル数 + pub stride: u32, + pub _pad: u32, +} + +#[cfg(feature = "hosted-vga")] +struct HostFramebuffer { + info: FbInfo, + pixels: Vec, +} + +#[cfg(feature = "hosted-vga")] +static HOST_FB: Mutex> = Mutex::new(None); + +/// フレームバッファ情報を取得する(mochiOS) +#[cfg(not(feature = "hosted-vga"))] +pub fn get_info() -> Option { + let mut info = FbInfo { width: 0, height: 0, stride: 0, _pad: 0 }; + let ret = syscall1( + SyscallNumber::GetFramebufferInfo as u64, + &raw mut info as u64, + ); + if ret == 0 { Some(info) } else { None } +} + +/// フレームバッファ情報を取得する(hosted-vga) +#[cfg(feature = "hosted-vga")] +pub fn get_info() -> Option { + HOST_FB.lock().ok().and_then(|g| g.as_ref().map(|host| host.info)) +} + +/// フレームバッファをプロセスのアドレス空間にマップし、 +/// `*mut u32` ピクセルバッファへのポインタを返す +#[cfg(not(feature = "hosted-vga"))] +pub fn map_framebuffer() -> Option<*mut u32> { + let addr = syscall0(SyscallNumber::MapFramebuffer as u64); + if addr == 0 || (addr as i64) < 0 { + None + } else { + Some(addr as *mut u32) + } +} + +#[cfg(feature = "hosted-vga")] +pub fn map_framebuffer() -> Option<*mut u32> { + let mut guard = HOST_FB.lock().ok()?; + guard.as_mut().map(|host| host.pixels.as_mut_ptr()) +} + +/// カーネルコンソールのカーソルをシェルのピクセルY位置に同期する +#[cfg(not(feature = "hosted-vga"))] +pub fn set_console_cursor(pixel_y: u32) { + syscall1(SyscallNumber::SetConsoleCursor as u64, pixel_y as u64); +} + +#[cfg(feature = "hosted-vga")] +pub fn set_console_cursor(pixel_y: u32) { + let _ = pixel_y; +} + +/// カーネルコンソールのカーソルの現在ピクセルY位置を取得する +#[cfg(not(feature = "hosted-vga"))] +pub fn get_console_cursor() -> u32 { + syscall0(SyscallNumber::GetConsoleCursor as u64) as u32 +} + +#[cfg(feature = "hosted-vga")] +pub fn get_console_cursor() -> u32 { + 0 +} + +/// Linuxホスト上のソフトウェアフレームバッファを初期化する。 +/// `hosted-vga` 有効時のみ利用可能。 +#[cfg(feature = "hosted-vga")] +pub fn host_init_framebuffer(width: u32, height: u32) -> Result<(), &'static str> { + if width == 0 || height == 0 { + return Err("invalid framebuffer size"); + } + let len = (width as usize).saturating_mul(height as usize); + let info = FbInfo { + width, + height, + stride: width, + _pad: 0, + }; + let mut guard = HOST_FB.lock().map_err(|_| "host framebuffer lock poisoned")?; + *guard = Some(HostFramebuffer { + info, + pixels: vec![0xFF00_0000; len], + }); + Ok(()) +} + +/// ホストフレームバッファをPPMへ保存する(デバッグ用)。 +#[cfg(feature = "hosted-vga")] +pub fn host_dump_ppm(path: &str) -> Result<(), &'static str> { + let (w, h, pixels) = { + let guard = HOST_FB.lock().map_err(|_| "host framebuffer lock poisoned")?; + let host = guard.as_ref().ok_or("host framebuffer is not initialized")?; + (host.info.width as usize, host.info.height as usize, host.pixels.clone()) + }; + + let mut file = File::create(path).map_err(|_| "failed to create ppm file")?; + file.write_all(format!("P6\n{} {}\n255\n", w, h).as_bytes()) + .map_err(|_| "failed to write ppm header")?; + let mut rgb = Vec::with_capacity(w.saturating_mul(h).saturating_mul(3)); + for px in pixels { + rgb.push(((px >> 16) & 0xFF) as u8); + rgb.push(((px >> 8) & 0xFF) as u8); + rgb.push((px & 0xFF) as u8); + } + file.write_all(&rgb) + .map_err(|_| "failed to write ppm data")?; + Ok(()) +} diff --git a/src/util/vga.rs b/src/util/vga.rs deleted file mode 100644 index 0f15b1b..0000000 --- a/src/util/vga.rs +++ /dev/null @@ -1,180 +0,0 @@ -//! フレームバッファ出力 - -use core::fmt; -use spin::{Mutex, Once}; - -/// フレームバッファ情報 -static FB_INFO: Once = Once::new(); - -#[derive(Clone, Copy)] -struct FramebufferInfo { - addr: u64, - width: usize, - height: usize, - stride: usize, -} - -/// 8x16 ビットマップフォント(簡易版) -const FONT_HEIGHT: usize = 16; -const FONT_WIDTH: usize = 8; - -/// フレームバッファライター -pub struct Writer { - column: usize, - row: usize, - max_cols: usize, - max_rows: usize, -} - -impl Writer { - fn new(info: &FramebufferInfo) -> Self { - let max_cols = info.width / FONT_WIDTH; - let max_rows = info.height / FONT_HEIGHT; - Self { - column: 0, - row: 0, - max_cols, - max_rows, - } - } - - /// ピクセルを描画 - fn put_pixel(&self, x: usize, y: usize, color: u32) { - if let Some(info) = FB_INFO.get() { - // 境界チェック - if x >= info.width || y >= info.height { - return; - } - let offset = y * info.stride + x; - let fb_ptr = info.addr as *mut u32; - unsafe { - fb_ptr.add(offset).write_volatile(color); - } - } - } - - /// 文字を描画(簡易版:矩形で代用) - fn draw_char(&self, ch: u8, x: usize, y: usize, fg: u32, bg: u32) { - // 簡易実装:背景色で矩形を描画 - for row in 0..FONT_HEIGHT { - for col in 0..FONT_WIDTH { - // 文字の輪郭を簡易的に表現(実際のフォントデータは省略) - let is_char = ch != b' ' - && (row == 0 || row == FONT_HEIGHT - 1 || col == 0 || col == FONT_WIDTH - 1); - let color = if is_char { fg } else { bg }; - self.put_pixel(x + col, y + row, color); - } - } - } - - /// 1バイト書き込み - pub fn write_byte(&mut self, byte: u8) { - match byte { - b'\n' => self.new_line(), - byte => { - if self.column >= self.max_cols { - self.new_line(); - } - - let x = self.column * FONT_WIDTH; - let y = self.row * FONT_HEIGHT; - self.draw_char(byte, x, y, 0xFFFFFF, 0x000000); // 白文字、黒背景 - - self.column += 1; - } - } - } - - /// 文字列を書き込み - pub fn write_string(&mut self, s: &str) { - for byte in s.bytes() { - match byte { - 0x20..=0x7e | b'\n' => self.write_byte(byte), - _ => self.write_byte(0x20), // 非対応文字はスペース - } - } - } - - /// 改行処理 - fn new_line(&mut self) { - self.row += 1; - self.column = 0; - if self.row >= self.max_rows { - // スクロールの代わりに画面クリア(簡易版) - self.clear_screen(); - } - } - - /// 画面全体をクリア - pub fn clear_screen(&mut self) { - if let Some(info) = FB_INFO.get() { - let fb_ptr = info.addr as *mut u32; - - let total_pixels = info.height * info.width; - unsafe { - for i in 0..total_pixels { - fb_ptr.add(i).write_volatile(0x000000); // 黒 - } - } - } - self.row = 0; - self.column = 0; - } -} - -impl fmt::Write for Writer { - fn write_str(&mut self, s: &str) -> fmt::Result { - self.write_string(s); - Ok(()) - } -} - -/// グローバルライター(遅延初期化) -static WRITER: Once> = Once::new(); - -/// フレームバッファを初期化 -pub fn init(addr: u64, width: usize, height: usize, stride: usize) { - FB_INFO.call_once(|| FramebufferInfo { - addr, - width, - height, - stride, - }); - - if let Some(info) = FB_INFO.get() { - WRITER.call_once(|| Mutex::new(Writer::new(info))); - - // 画面をクリア - if let Some(writer) = WRITER.get() { - writer.lock().clear_screen(); - } - } -} - -/// フレームバッファに文字列を出力(割り込み対応) -pub fn print(args: fmt::Arguments) { - use core::fmt::Write; - if let Some(writer) = WRITER.get() { - // 割り込みを無効化してロック取得(デッドロック防止) - x86_64::instructions::interrupts::without_interrupts(|| { - let _ = writer.lock().write_fmt(args); - }); - } -} - -/// フレームバッファ出力マクロ -#[macro_export] -macro_rules! vprint { - ($($arg:tt)*) => { - $crate::util::vga::print(format_args!($($arg)*)) - }; -} - -/// 改行付きフレームバッファ出力マクロ -#[macro_export] -macro_rules! vprintln { - () => ($crate::vprint!("\n")); - ($($arg:tt)*) => { - $crate::vprint!("{}\n", format_args!($($arg)*)) - }; -} diff --git a/src/utils/.cargo/config.toml b/src/utils/.cargo/config.toml new file mode 100644 index 0000000..0387665 --- /dev/null +++ b/src/utils/.cargo/config.toml @@ -0,0 +1,5 @@ +[build] +target = "../x86_64-mochios.json" + +[unstable] +json-target-spec = true diff --git a/src/utils/Cargo.toml b/src/utils/Cargo.toml new file mode 100644 index 0000000..603ded0 --- /dev/null +++ b/src/utils/Cargo.toml @@ -0,0 +1,118 @@ +[package] +name = "utils" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "ls" +path = "src/bin/ls.rs" + +[[bin]] +name = "cat" +path = "src/bin/cat.rs" + +[[bin]] +name = "pwd" +path = "src/bin/pwd.rs" + +[[bin]] +name = "echo" +path = "src/bin/echo.rs" + +[[bin]] +name = "clear" +path = "src/bin/clear.rs" + +[[bin]] +name = "mkdir" +path = "src/bin/mkdir.rs" + +[[bin]] +name = "touch" +path = "src/bin/touch.rs" + +[[bin]] +name = "rm" +path = "src/bin/rm.rs" + +[[bin]] +name = "head" +path = "src/bin/head.rs" + +[[bin]] +name = "tail" +path = "src/bin/tail.rs" + +[[bin]] +name = "wc" +path = "src/bin/wc.rs" + +[[bin]] +name = "cp" +path = "src/bin/cp.rs" + +[[bin]] +name = "true" +path = "src/bin/true.rs" + +[[bin]] +name = "false" +path = "src/bin/false.rs" + +[[bin]] +name = "yes" +path = "src/bin/yes.rs" + +[[bin]] +name = "sleep" +path = "src/bin/sleep.rs" + +[[bin]] +name = "date" +path = "src/bin/date.rs" + +[[bin]] +name = "seq" +path = "src/bin/seq.rs" + +[[bin]] +name = "basename" +path = "src/bin/basename.rs" + +[[bin]] +name = "dirname" +path = "src/bin/dirname.rs" + +[[bin]] +name = "cmp" +path = "src/bin/cmp.rs" + +[[bin]] +name = "uniq" +path = "src/bin/uniq.rs" + +[[bin]] +name = "grep" +path = "src/bin/grep.rs" + +[[bin]] +name = "cut" +path = "src/bin/cut.rs" + +[[bin]] +name = "tr" +path = "src/bin/tr.rs" + +[[bin]] +name = "which" +path = "src/bin/which.rs" + +[dependencies] +swiftlib = { path = "../user" } + +[profile.release] +opt-level = "z" +lto = true +codegen-units = 1 +panic = "abort" +strip = true diff --git a/src/utils/src/bin/basename.rs b/src/utils/src/bin/basename.rs new file mode 100644 index 0000000..b11759a --- /dev/null +++ b/src/utils/src/bin/basename.rs @@ -0,0 +1,40 @@ +#![no_std] +#![no_main] + +use swiftlib::io; + +#[no_mangle] +pub extern "C" fn main(argc: i32, argv: *const *const u8) -> i32 { + if argc < 2 { + io::print("Usage: basename \n"); + return 1; + } + + let path = unsafe { + let arg_ptr = *argv.offset(1); + if arg_ptr.is_null() { + return 1; + } + let mut len = 0; + while *arg_ptr.offset(len) != 0 { + len += 1; + } + match core::str::from_utf8(core::slice::from_raw_parts(arg_ptr, len as usize)) { + Ok(s) => s, + Err(_) => return 1, + } + }; + + // 末尾の / を削除 + let path = path.trim_end_matches('/'); + + // 最後の / 以降を取得 + if let Some(pos) = path.rfind('/') { + io::print(&path[pos + 1..]); + } else { + io::print(path); + } + io::print("\n"); + + 0 +} diff --git a/src/utils/src/bin/cat.rs b/src/utils/src/bin/cat.rs new file mode 100644 index 0000000..ca32fe7 --- /dev/null +++ b/src/utils/src/bin/cat.rs @@ -0,0 +1,121 @@ +#![no_std] +#![no_main] + +use swiftlib::{io, sys::SyscallNumber}; + +fn syscall1(num: u64, arg1: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn syscall2(num: u64, arg1: u64, arg2: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + in("rsi") arg2, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn syscall3(num: u64, arg1: u64, arg2: u64, arg3: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + in("rsi") arg2, + in("rdx") arg3, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn open(path: &str, flags: u64) -> i64 { + let mut buf = [0u8; 512]; + let bytes = path.as_bytes(); + let len = bytes.len().min(511); + buf[..len].copy_from_slice(&bytes[..len]); + syscall2(SyscallNumber::Open as u64, buf.as_ptr() as u64, flags) as i64 +} + +fn read(fd: u64, buf: &mut [u8]) -> i64 { + syscall3( + SyscallNumber::Read as u64, + fd, + buf.as_mut_ptr() as u64, + buf.len() as u64, + ) as i64 +} + +fn close(fd: u64) { + syscall1(SyscallNumber::Close as u64, fd); +} + +#[no_mangle] +pub extern "C" fn main(argc: i32, argv: *const *const u8) -> i32 { + if argc <= 1 { + io::print("Usage: cat ...\n"); + return 1; + } + + for i in 1..argc { + let path = unsafe { + let arg_ptr = *argv.offset(i as isize); + if arg_ptr.is_null() { + continue; + } + let mut len = 0; + while *arg_ptr.offset(len) != 0 { + len += 1; + } + match core::str::from_utf8(core::slice::from_raw_parts(arg_ptr, len as usize)) { + Ok(s) => s, + Err(_) => continue, + } + }; + + let fd = open(path, 0); + if fd < 0 { + io::print("cat: "); + io::print(path); + io::print(": cannot open\n"); + continue; + } + + let fd_u = fd as u64; + let mut buf = [0u8; 2048]; + + loop { + let n = read(fd_u, &mut buf); + if n <= 0 { + break; + } + + if let Ok(s) = core::str::from_utf8(&buf[..n as usize]) { + io::print(s); + } + } + + close(fd_u); + } + + 0 +} diff --git a/src/utils/src/bin/clear.rs b/src/utils/src/bin/clear.rs new file mode 100644 index 0000000..06804ae --- /dev/null +++ b/src/utils/src/bin/clear.rs @@ -0,0 +1,11 @@ +#![no_std] +#![no_main] + +use swiftlib::io; + +#[no_mangle] +pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { + // ANSI エスケープコード: 画面クリア + カーソルをホームへ + io::print("\x1b[2J\x1b[H"); + 0 +} diff --git a/src/utils/src/bin/cmp.rs b/src/utils/src/bin/cmp.rs new file mode 100644 index 0000000..5e04519 --- /dev/null +++ b/src/utils/src/bin/cmp.rs @@ -0,0 +1,173 @@ +#![no_std] +#![no_main] + +use swiftlib::{io, sys::SyscallNumber}; + +fn syscall1(num: u64, arg1: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn syscall2(num: u64, arg1: u64, arg2: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + in("rsi") arg2, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn syscall3(num: u64, arg1: u64, arg2: u64, arg3: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + in("rsi") arg2, + in("rdx") arg3, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn open(path: &str) -> i64 { + let mut buf = [0u8; 512]; + let bytes = path.as_bytes(); + let len = bytes.len().min(511); + buf[..len].copy_from_slice(&bytes[..len]); + syscall2(SyscallNumber::Open as u64, buf.as_ptr() as u64, 0) as i64 +} + +fn read(fd: u64, buf: &mut [u8]) -> i64 { + syscall3( + SyscallNumber::Read as u64, + fd, + buf.as_mut_ptr() as u64, + buf.len() as u64, + ) as i64 +} + +fn close(fd: u64) { + syscall1(SyscallNumber::Close as u64, fd); +} + +#[no_mangle] +pub extern "C" fn main(argc: i32, argv: *const *const u8) -> i32 { + if argc < 3 { + io::print("Usage: cmp \n"); + return 1; + } + + let (path1, path2) = unsafe { + let ptr1 = *argv.offset(1); + let ptr2 = *argv.offset(2); + + let mut len1 = 0; + while !ptr1.is_null() && *ptr1.offset(len1) != 0 { + len1 += 1; + } + + let mut len2 = 0; + while !ptr2.is_null() && *ptr2.offset(len2) != 0 { + len2 += 1; + } + + let s1 = core::str::from_utf8(core::slice::from_raw_parts(ptr1, len1 as usize)); + let s2 = core::str::from_utf8(core::slice::from_raw_parts(ptr2, len2 as usize)); + + match (s1, s2) { + (Ok(a), Ok(b)) => (a, b), + _ => return 1, + } + }; + + let fd1 = open(path1); + if fd1 < 0 { + io::print("cmp: cannot open: "); + io::print(path1); + io::print("\n"); + return 1; + } + + let fd2 = open(path2); + if fd2 < 0 { + io::print("cmp: cannot open: "); + io::print(path2); + io::print("\n"); + close(fd1 as u64); + return 1; + } + + let mut buf1 = [0u8; 2048]; + let mut buf2 = [0u8; 2048]; + let mut byte_pos = 1u64; + + loop { + let n1 = read(fd1 as u64, &mut buf1); + let n2 = read(fd2 as u64, &mut buf2); + + if n1 != n2 { + io::print(path1); + io::print(" "); + io::print(path2); + io::print(" differ: size\n"); + close(fd1 as u64); + close(fd2 as u64); + return 1; + } + + if n1 <= 0 { + break; + } + + for i in 0..(n1 as usize) { + if buf1[i] != buf2[i] { + io::print(path1); + io::print(" "); + io::print(path2); + io::print(" differ: byte "); + + let mut num_buf = [0u8; 20]; + let mut idx = num_buf.len(); + let mut n = byte_pos + i as u64; + while n > 0 && idx > 0 { + idx -= 1; + num_buf[idx] = b'0' + (n % 10) as u8; + n /= 10; + } + if let Ok(s) = core::str::from_utf8(&num_buf[idx..]) { + io::print(s); + } + io::print("\n"); + + close(fd1 as u64); + close(fd2 as u64); + return 1; + } + } + + byte_pos += n1 as u64; + } + + close(fd1 as u64); + close(fd2 as u64); + 0 +} diff --git a/src/utils/src/bin/cp.rs b/src/utils/src/bin/cp.rs new file mode 100644 index 0000000..88da593 --- /dev/null +++ b/src/utils/src/bin/cp.rs @@ -0,0 +1,154 @@ +#![no_std] +#![no_main] + +use swiftlib::{io, sys::SyscallNumber}; + +fn syscall1(num: u64, arg1: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn syscall2(num: u64, arg1: u64, arg2: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + in("rsi") arg2, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn syscall3(num: u64, arg1: u64, arg2: u64, arg3: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + in("rsi") arg2, + in("rdx") arg3, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn open(path: &str, flags: u64) -> i64 { + let mut buf = [0u8; 512]; + let bytes = path.as_bytes(); + let len = bytes.len().min(511); + buf[..len].copy_from_slice(&bytes[..len]); + syscall2(SyscallNumber::Open as u64, buf.as_ptr() as u64, flags) as i64 +} + +fn read(fd: u64, buf: &mut [u8]) -> i64 { + syscall3( + SyscallNumber::Read as u64, + fd, + buf.as_mut_ptr() as u64, + buf.len() as u64, + ) as i64 +} + +fn write(fd: u64, buf: &[u8]) -> i64 { + syscall3( + SyscallNumber::Write as u64, + fd, + buf.as_ptr() as u64, + buf.len() as u64, + ) as i64 +} + +fn close(fd: u64) { + syscall1(SyscallNumber::Close as u64, fd); +} + +const O_CREAT: u64 = 0x40; +const O_WRONLY: u64 = 0x01; +const O_TRUNC: u64 = 0x200; + +#[no_mangle] +pub extern "C" fn main(argc: i32, argv: *const *const u8) -> i32 { + if argc < 3 { + io::print("Usage: cp \n"); + return 1; + } + + let (src_path, dest_path) = unsafe { + let src_ptr = *argv.offset(1); + let dest_ptr = *argv.offset(2); + + let mut src_len = 0; + while !src_ptr.is_null() && *src_ptr.offset(src_len) != 0 { + src_len += 1; + } + + let mut dest_len = 0; + while !dest_ptr.is_null() && *dest_ptr.offset(dest_len) != 0 { + dest_len += 1; + } + + let src = core::str::from_utf8(core::slice::from_raw_parts(src_ptr, src_len as usize)); + let dest = core::str::from_utf8(core::slice::from_raw_parts(dest_ptr, dest_len as usize)); + + match (src, dest) { + (Ok(s), Ok(d)) => (s, d), + _ => { + io::print("cp: invalid path\n"); + return 1; + } + } + }; + + let src_fd = open(src_path, 0); + if src_fd < 0 { + io::print("cp: cannot open source: "); + io::print(src_path); + io::print("\n"); + return 1; + } + + let dest_fd = open(dest_path, O_CREAT | O_WRONLY | O_TRUNC); + if dest_fd < 0 { + io::print("cp: cannot create destination: "); + io::print(dest_path); + io::print("\n"); + close(src_fd as u64); + return 1; + } + + let mut buf = [0u8; 4096]; + loop { + let n = read(src_fd as u64, &mut buf); + if n <= 0 { + break; + } + + let written = write(dest_fd as u64, &buf[..n as usize]); + if written != n { + io::print("cp: write error\n"); + close(src_fd as u64); + close(dest_fd as u64); + return 1; + } + } + + close(src_fd as u64); + close(dest_fd as u64); + 0 +} diff --git a/src/utils/src/bin/cut.rs b/src/utils/src/bin/cut.rs new file mode 100644 index 0000000..0f2c4ee --- /dev/null +++ b/src/utils/src/bin/cut.rs @@ -0,0 +1,79 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use swiftlib::io; + +fn parse_number(s: &str) -> Option { + let mut result = 0usize; + for &b in s.as_bytes() { + if b >= b'0' && b <= b'9' { + result = result * 10 + (b - b'0') as usize; + } else { + return None; + } + } + Some(result) +} + +#[no_mangle] +pub extern "C" fn main(argc: i32, argv: *const *const u8) -> i32 { + if argc < 3 { + io::print("Usage: cut -f \n"); + return 1; + } + + let field_arg = unsafe { + let arg_ptr = *argv.offset(1); + if arg_ptr.is_null() { + return 1; + } + let mut len = 0; + while *arg_ptr.offset(len) != 0 { + len += 1; + } + match core::str::from_utf8(core::slice::from_raw_parts(arg_ptr, len as usize)) { + Ok(s) => s, + Err(_) => return 1, + } + }; + + if !field_arg.starts_with("-f") { + io::print("cut: usage: -f\n"); + return 1; + } + + let field_num = match parse_number(&field_arg[2..]) { + Some(n) if n > 0 => n - 1, + _ => { + io::print("cut: invalid field number\n"); + return 1; + } + }; + + let text = unsafe { + let arg_ptr = *argv.offset(2); + if arg_ptr.is_null() { + return 1; + } + let mut len = 0; + while *arg_ptr.offset(len) != 0 { + len += 1; + } + match core::str::from_utf8(core::slice::from_raw_parts(arg_ptr, len as usize)) { + Ok(s) => s, + Err(_) => return 1, + } + }; + + // スペース区切りでフィールド分割 + let fields: alloc::vec::Vec<&str> = text.split(' ').collect(); + + if field_num < fields.len() { + io::print(fields[field_num]); + io::print("\n"); + } + + 0 +} diff --git a/src/utils/src/bin/date.rs b/src/utils/src/bin/date.rs new file mode 100644 index 0000000..1c78cf5 --- /dev/null +++ b/src/utils/src/bin/date.rs @@ -0,0 +1,46 @@ +#![no_std] +#![no_main] + +use swiftlib::{io, sys::SyscallNumber}; + +fn syscall0(num: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn print_number(n: u64) { + let mut buf = [0u8; 20]; + let mut i = buf.len(); + let mut num = n; + + if num == 0 { + io::print("0"); + return; + } + + while num > 0 && i > 0 { + i -= 1; + buf[i] = b'0' + (num % 10) as u8; + num /= 10; + } + + if let Ok(s) = core::str::from_utf8(&buf[i..]) { + io::print(s); + } +} + +#[no_mangle] +pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { + let ticks = syscall0(SyscallNumber::GetTicks as u64); + print_number(ticks); + io::print(" ticks\n"); + 0 +} diff --git a/src/utils/src/bin/dirname.rs b/src/utils/src/bin/dirname.rs new file mode 100644 index 0000000..7a6e5f1 --- /dev/null +++ b/src/utils/src/bin/dirname.rs @@ -0,0 +1,44 @@ +#![no_std] +#![no_main] + +use swiftlib::io; + +#[no_mangle] +pub extern "C" fn main(argc: i32, argv: *const *const u8) -> i32 { + if argc < 2 { + io::print("Usage: dirname \n"); + return 1; + } + + let path = unsafe { + let arg_ptr = *argv.offset(1); + if arg_ptr.is_null() { + return 1; + } + let mut len = 0; + while *arg_ptr.offset(len) != 0 { + len += 1; + } + match core::str::from_utf8(core::slice::from_raw_parts(arg_ptr, len as usize)) { + Ok(s) => s, + Err(_) => return 1, + } + }; + + // 末尾の / を削除 + let path = path.trim_end_matches('/'); + + // 最後の / まで取得 + if let Some(pos) = path.rfind('/') { + if pos == 0 { + io::print("/\n"); + } else { + io::print(&path[..pos]); + io::print("\n"); + } + } else { + io::print(".\n"); + } + + 0 +} diff --git a/src/utils/src/bin/echo.rs b/src/utils/src/bin/echo.rs new file mode 100644 index 0000000..f82d16e --- /dev/null +++ b/src/utils/src/bin/echo.rs @@ -0,0 +1,35 @@ +#![no_std] +#![no_main] + +use swiftlib::io; + +#[no_mangle] +pub extern "C" fn main(argc: i32, argv: *const *const u8) -> i32 { + if argc <= 1 { + io::print("\n"); + return 0; + } + + let mut first = true; + for i in 1..argc { + if !first { + io::print(" "); + } + first = false; + + unsafe { + let arg_ptr = *argv.offset(i as isize); + if !arg_ptr.is_null() { + let mut len = 0; + while *arg_ptr.offset(len) != 0 { + len += 1; + } + if let Ok(s) = core::str::from_utf8(core::slice::from_raw_parts(arg_ptr, len as usize)) { + io::print(s); + } + } + } + } + io::print("\n"); + 0 +} diff --git a/src/utils/src/bin/false.rs b/src/utils/src/bin/false.rs new file mode 100644 index 0000000..06bbdeb --- /dev/null +++ b/src/utils/src/bin/false.rs @@ -0,0 +1,9 @@ +#![no_std] +#![no_main] + +use swiftlib as _; + +#[no_mangle] +pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { + 1 +} diff --git a/src/utils/src/bin/grep.rs b/src/utils/src/bin/grep.rs new file mode 100644 index 0000000..21ffb37 --- /dev/null +++ b/src/utils/src/bin/grep.rs @@ -0,0 +1,187 @@ +#![no_std] +#![no_main] + +extern crate alloc; +use alloc::vec::Vec; +use swiftlib::{io, sys::SyscallNumber}; + +fn syscall1(num: u64, arg1: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn syscall2(num: u64, arg1: u64, arg2: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + in("rsi") arg2, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn syscall3(num: u64, arg1: u64, arg2: u64, arg3: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + in("rsi") arg2, + in("rdx") arg3, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn open(path: &str) -> i64 { + let mut buf = [0u8; 512]; + let bytes = path.as_bytes(); + let len = bytes.len().min(511); + buf[..len].copy_from_slice(&bytes[..len]); + syscall2(SyscallNumber::Open as u64, buf.as_ptr() as u64, 0) as i64 +} + +fn read(fd: u64, buf: &mut [u8]) -> i64 { + syscall3( + SyscallNumber::Read as u64, + fd, + buf.as_mut_ptr() as u64, + buf.len() as u64, + ) as i64 +} + +fn close(fd: u64) { + syscall1(SyscallNumber::Close as u64, fd); +} + +fn contains_pattern(line: &[u8], pattern: &[u8]) -> bool { + if pattern.is_empty() { + return true; + } + + if line.len() < pattern.len() { + return false; + } + + for i in 0..=(line.len() - pattern.len()) { + let mut matches = true; + for j in 0..pattern.len() { + if line[i + j] != pattern[j] { + matches = false; + break; + } + } + if matches { + return true; + } + } + false +} + +#[no_mangle] +pub extern "C" fn main(argc: i32, argv: *const *const u8) -> i32 { + if argc < 2 { + io::print("Usage: grep [file...]\n"); + return 1; + } + + let pattern = unsafe { + let arg_ptr = *argv.offset(1); + if arg_ptr.is_null() { + return 1; + } + let mut len = 0; + while *arg_ptr.offset(len) != 0 { + len += 1; + } + core::slice::from_raw_parts(arg_ptr, len as usize) + }; + + if argc < 3 { + io::print("grep: no files specified\n"); + return 1; + } + + for i in 2..argc { + let path = unsafe { + let arg_ptr = *argv.offset(i as isize); + if arg_ptr.is_null() { + continue; + } + let mut len = 0; + while *arg_ptr.offset(len) != 0 { + len += 1; + } + match core::str::from_utf8(core::slice::from_raw_parts(arg_ptr, len as usize)) { + Ok(s) => s, + Err(_) => continue, + } + }; + + let fd = open(path); + if fd < 0 { + io::print("grep: cannot open: "); + io::print(path); + io::print("\n"); + continue; + } + + let mut content = Vec::new(); + let mut buf = [0u8; 2048]; + + loop { + let n = read(fd as u64, &mut buf); + if n <= 0 { + break; + } + content.extend_from_slice(&buf[..n as usize]); + } + + close(fd as u64); + + // 行ごとに検索 + let mut line_start = 0; + for (idx, &byte) in content.iter().enumerate() { + if byte == b'\n' { + let line = &content[line_start..idx]; + if contains_pattern(line, pattern) { + if let Ok(s) = core::str::from_utf8(line) { + io::print(s); + io::print("\n"); + } + } + line_start = idx + 1; + } + } + + // 最後の行(改行なし) + if line_start < content.len() { + let line = &content[line_start..]; + if contains_pattern(line, pattern) { + if let Ok(s) = core::str::from_utf8(line) { + io::print(s); + io::print("\n"); + } + } + } + } + + 0 +} diff --git a/src/utils/src/bin/head.rs b/src/utils/src/bin/head.rs new file mode 100644 index 0000000..e190c8e --- /dev/null +++ b/src/utils/src/bin/head.rs @@ -0,0 +1,138 @@ +#![no_std] +#![no_main] + +use swiftlib::{io, sys::SyscallNumber}; + +fn syscall1(num: u64, arg1: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn syscall2(num: u64, arg1: u64, arg2: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + in("rsi") arg2, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn syscall3(num: u64, arg1: u64, arg2: u64, arg3: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + in("rsi") arg2, + in("rdx") arg3, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn open(path: &str) -> i64 { + let mut buf = [0u8; 512]; + let bytes = path.as_bytes(); + let len = bytes.len().min(511); + buf[..len].copy_from_slice(&bytes[..len]); + syscall2(SyscallNumber::Open as u64, buf.as_ptr() as u64, 0) as i64 +} + +fn read(fd: u64, buf: &mut [u8]) -> i64 { + syscall3( + SyscallNumber::Read as u64, + fd, + buf.as_mut_ptr() as u64, + buf.len() as u64, + ) as i64 +} + +fn close(fd: u64) { + syscall1(SyscallNumber::Close as u64, fd); +} + +#[no_mangle] +pub extern "C" fn main(argc: i32, argv: *const *const u8) -> i32 { + let lines = 10; + let start_idx = 1; + + if argc <= start_idx { + io::print("Usage: head ...\n"); + return 1; + } + + for i in start_idx..argc { + let path = unsafe { + let arg_ptr = *argv.offset(i as isize); + if arg_ptr.is_null() { + continue; + } + let mut len = 0; + while *arg_ptr.offset(len) != 0 { + len += 1; + } + match core::str::from_utf8(core::slice::from_raw_parts(arg_ptr, len as usize)) { + Ok(s) => s, + Err(_) => continue, + } + }; + + let fd = open(path); + if fd < 0 { + io::print("head: "); + io::print(path); + io::print(": cannot open\n"); + continue; + } + + let fd_u = fd as u64; + let mut buf = [0u8; 2048]; + let mut line_count = 0; + + loop { + let n = read(fd_u, &mut buf); + if n <= 0 { + break; + } + + for &byte in &buf[..n as usize] { + if byte == b'\n' { + line_count += 1; + if line_count >= lines { + break; + } + } + let ch = [byte]; + if let Ok(s) = core::str::from_utf8(&ch) { + io::print(s); + } + } + + if line_count >= lines { + break; + } + } + + close(fd_u); + } + + 0 +} diff --git a/src/utils/src/bin/ls.rs b/src/utils/src/bin/ls.rs new file mode 100644 index 0000000..ab3aced --- /dev/null +++ b/src/utils/src/bin/ls.rs @@ -0,0 +1,222 @@ +#![no_std] +#![no_main] + +use swiftlib::{io, sys::SyscallNumber}; + +// ANSI カラーコード +const COLOR_RESET: &str = "\x1b[0m"; +const COLOR_BLUE: &str = "\x1b[34m"; // ディレクトリ +const COLOR_GREEN: &str = "\x1b[32m"; // 実行可能ファイル +const COLOR_CYAN: &str = "\x1b[36m"; // シンボリックリンク + +// Low-level syscalls +fn syscall1(num: u64, arg1: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn syscall2(num: u64, arg1: u64, arg2: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + in("rsi") arg2, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn syscall3(num: u64, arg1: u64, arg2: u64, arg3: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + in("rsi") arg2, + in("rdx") arg3, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn open(path: &str) -> i64 { + let mut buf = [0u8; 512]; + let bytes = path.as_bytes(); + let len = bytes.len().min(511); + buf[..len].copy_from_slice(&bytes[..len]); + syscall2(SyscallNumber::Open as u64, buf.as_ptr() as u64, 0) as i64 +} + +fn stat(path: &str) -> Result<(u16, u64), i64> { + let mut buf = [0u8; 512]; + let bytes = path.as_bytes(); + let len = bytes.len().min(511); + buf[..len].copy_from_slice(&bytes[..len]); + + let result = syscall1(SyscallNumber::Stat as u64, buf.as_ptr() as u64); + + // stat は mode を下位 16 ビット、size を上位 48 ビットに詰めて返す + // または負の値でエラー + if (result as i64) < 0 { + return Err(result as i64); + } + + let mode = (result & 0xFFFF) as u16; + let size = (result >> 16) as u64; + Ok((mode, size)) +} + +fn readdir(fd: u64, buf: &mut [u8]) -> u64 { + syscall3( + SyscallNumber::Readdir as u64, + fd, + buf.as_mut_ptr() as u64, + buf.len() as u64, + ) +} + +fn close(fd: u64) -> u64 { + syscall1(SyscallNumber::Close as u64, fd) +} + +fn is_directory(mode: u16) -> bool { + (mode & 0xF000) == 0x4000 +} + +fn is_executable(mode: u16) -> bool { + // 実行権限ビット (user/group/other のいずれか) + (mode & 0o111) != 0 +} + +fn is_symlink(mode: u16) -> bool { + (mode & 0xF000) == 0xA000 +} + +fn get_color_for_file(path: &str) -> &'static str { + match stat(path) { + Ok((mode, _)) => { + if is_directory(mode) { + COLOR_BLUE + } else if is_symlink(mode) { + COLOR_CYAN + } else if is_executable(mode) { + COLOR_GREEN + } else { + COLOR_RESET + } + } + Err(_) => COLOR_RESET, + } +} + +#[no_mangle] +pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { + let path = if _argc > 1 { + unsafe { + let arg_ptr = *_argv.offset(1); + if arg_ptr.is_null() { + "." + } else { + let mut len = 0; + while *arg_ptr.offset(len) != 0 { + len += 1; + } + match core::str::from_utf8(core::slice::from_raw_parts(arg_ptr, len as usize)) { + Ok(s) => s, + Err(_) => ".", + } + } + } + } else { + "." + }; + + let fd = open(path); + if fd < 0 { + io::print("ls: cannot open directory: "); + io::print(path); + io::print("\n"); + return 1; + } + + let fd_u = fd as u64; + let mut buf = [0u8; 2048]; + + // パスの末尾に / を追加するかチェック + let needs_slash = !path.ends_with('/'); + + loop { + let n = readdir(fd_u, &mut buf); + if n == 0 || n > 0xFFFF_FFFF_0000_0000 { + break; + } + + // Parse null-terminated strings + let data = &buf[..n as usize]; + let mut pos = 0; + while pos < data.len() { + if data[pos] == 0 { + break; + } + let start = pos; + while pos < data.len() && data[pos] != 0 { + pos += 1; + } + if pos > start { + if let Ok(name) = core::str::from_utf8(&data[start..pos]) { + // フルパスを構築して stat + let mut full_path_buf = [0u8; 256]; + + // path をコピー + let path_bytes = path.as_bytes(); + let path_copy_len = path_bytes.len().min(200); + full_path_buf[..path_copy_len].copy_from_slice(&path_bytes[..path_copy_len]); + let mut full_len = path_copy_len; + + // / を追加(必要なら) + if needs_slash && full_len < 255 { + full_path_buf[full_len] = b'/'; + full_len += 1; + } + + // name を追加 + let name_bytes = name.as_bytes(); + let name_copy_len = name_bytes.len().min(255 - full_len); + full_path_buf[full_len..full_len + name_copy_len].copy_from_slice(&name_bytes[..name_copy_len]); + full_len += name_copy_len; + + if let Ok(full_path) = core::str::from_utf8(&full_path_buf[..full_len]) { + let color = get_color_for_file(full_path); + io::print(color); + io::print(name); + io::print(COLOR_RESET); + io::print("\n"); + } else { + io::print(name); + io::print("\n"); + } + } + } + pos += 1; // skip null terminator + } + } + + close(fd_u); + 0 +} diff --git a/src/utils/src/bin/mkdir.rs b/src/utils/src/bin/mkdir.rs new file mode 100644 index 0000000..9f4a721 --- /dev/null +++ b/src/utils/src/bin/mkdir.rs @@ -0,0 +1,63 @@ +#![no_std] +#![no_main] + +use swiftlib::{io, sys::SyscallNumber}; + +fn syscall2(num: u64, arg1: u64, arg2: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + in("rsi") arg2, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn mkdir(path: &str, mode: u32) -> i64 { + let mut buf = [0u8; 512]; + let bytes = path.as_bytes(); + let len = bytes.len().min(511); + buf[..len].copy_from_slice(&bytes[..len]); + syscall2(SyscallNumber::Mkdir as u64, buf.as_ptr() as u64, mode as u64) as i64 +} + +#[no_mangle] +pub extern "C" fn main(argc: i32, argv: *const *const u8) -> i32 { + if argc <= 1 { + io::print("Usage: mkdir ...\n"); + return 1; + } + + let mut ret = 0; + for i in 1..argc { + let path = unsafe { + let arg_ptr = *argv.offset(i as isize); + if arg_ptr.is_null() { + continue; + } + let mut len = 0; + while *arg_ptr.offset(len) != 0 { + len += 1; + } + match core::str::from_utf8(core::slice::from_raw_parts(arg_ptr, len as usize)) { + Ok(s) => s, + Err(_) => continue, + } + }; + + let result = mkdir(path, 0o755); + if result < 0 { + io::print("mkdir: cannot create directory '"); + io::print(path); + io::print("'\n"); + ret = 1; + } + } + + ret +} diff --git a/src/utils/src/bin/ps.rs b/src/utils/src/bin/ps.rs new file mode 100644 index 0000000..735d289 --- /dev/null +++ b/src/utils/src/bin/ps.rs @@ -0,0 +1,73 @@ +#![no_std] +#![no_main] + +use swiftlib::{io, sys::SyscallNumber}; + +fn syscall2(num: u64, arg1: u64, arg2: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + in("rsi") arg2, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn print_number(n: u64) { + let mut buf = [0u8; 20]; + let mut i = buf.len(); + let mut num = n; + if num == 0 { + io::print("0"); + return; + } + while num > 0 && i > 0 { + i -= 1; + buf[i] = b'0' + (num % 10) as u8; + num /= 10; + } + if let Ok(s) = core::str::from_utf8(&buf[i..]) { + io::print(s); + } +} + +#[no_mangle] +pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { + const RECORD_SIZE: usize = 88; + let mut buf = [0u8; 4096]; + let ret = syscall2(SyscallNumber::ListProcesses as u64, buf.as_mut_ptr() as u64, buf.len() as u64); + if ret == u64::MAX { io::print("ps: error\n"); return 1; } + let entries = ret as usize; + for i in 0..entries { + let off = i * RECORD_SIZE; + let tid = u64::from_ne_bytes(match buf[off..off+8].try_into() { Ok(a)=>a, Err(_) => [0u8;8] }); + let pid = u64::from_ne_bytes(match buf[off+8..off+16].try_into() { Ok(a)=>a, Err(_) => [0u8;8] }); + let state = u64::from_ne_bytes(match buf[off+16..off+24].try_into() { Ok(a)=>a, Err(_) => [0u8;8] }); + let name_bytes = &buf[off+32..off+96]; + let mut name_len = 0usize; + while name_len < name_bytes.len() && name_bytes[name_len] != 0 { name_len += 1; } + let name = core::str::from_utf8(&name_bytes[..name_len]).unwrap_or(""); + let state_str = match state { + 0 => "Ready", + 1 => "Running", + 2 => "Blocked", + 3 => "Sleeping", + 4 => "Terminated", + _ => "Unknown", + }; + print_number(pid); + io::print(" "); + print_number(tid); + io::print(" "); + io::print(state_str); + io::print(" "); + io::print(name); + io::print("\n"); + } + 0 +} diff --git a/src/utils/src/bin/pwd.rs b/src/utils/src/bin/pwd.rs new file mode 100644 index 0000000..139be86 --- /dev/null +++ b/src/utils/src/bin/pwd.rs @@ -0,0 +1,44 @@ +#![no_std] +#![no_main] + +use swiftlib::{io, sys::SyscallNumber}; + +fn syscall2(num: u64, arg1: u64, arg2: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + in("rsi") arg2, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +#[no_mangle] +pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { + let mut buf = [0u8; 512]; + let ret = syscall2( + SyscallNumber::Getcwd as u64, + buf.as_mut_ptr() as u64, + buf.len() as u64, + ); + + if ret == 0 || ret > 0xFFFF_FFFF_0000_0000 { + io::print("pwd: error\n"); + return 1; + } + + let len = buf.iter().position(|&b| b == 0).unwrap_or(buf.len()); + if let Ok(path) = core::str::from_utf8(&buf[..len]) { + io::print(path); + io::print("\n"); + 0 + } else { + io::print("pwd: invalid path\n"); + 1 + } +} diff --git a/src/utils/src/bin/rm.rs b/src/utils/src/bin/rm.rs new file mode 100644 index 0000000..d598d34 --- /dev/null +++ b/src/utils/src/bin/rm.rs @@ -0,0 +1,97 @@ +#![no_std] +#![no_main] + +use swiftlib::{io, sys::SyscallNumber}; + +fn syscall1(num: u64, arg1: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn unlink(path: &str) -> i64 { + let mut buf = [0u8; 512]; + let bytes = path.as_bytes(); + let len = bytes.len().min(511); + buf[..len].copy_from_slice(&bytes[..len]); + syscall1(SyscallNumber::Unlink as u64, buf.as_ptr() as u64) as i64 +} + +fn rmdir(path: &str) -> i64 { + let mut buf = [0u8; 512]; + let bytes = path.as_bytes(); + let len = bytes.len().min(511); + buf[..len].copy_from_slice(&bytes[..len]); + syscall1(SyscallNumber::Rmdir as u64, buf.as_ptr() as u64) as i64 +} + +#[no_mangle] +pub extern "C" fn main(argc: i32, argv: *const *const u8) -> i32 { + if argc <= 1 { + io::print("Usage: rm [-r] ...\n"); + return 1; + } + + let mut is_recursive = false; + let mut start_idx = 1; + + // -r オプションチェック + if argc > 1 { + unsafe { + let arg_ptr = *argv.offset(1); + if !arg_ptr.is_null() { + let mut len = 0; + while *arg_ptr.offset(len) != 0 && len < 10 { + len += 1; + } + if let Ok(s) = core::str::from_utf8(core::slice::from_raw_parts(arg_ptr, len as usize)) { + if s == "-r" { + is_recursive = true; + start_idx = 2; + } + } + } + } + } + + let mut ret = 0; + for i in start_idx..argc { + let path = unsafe { + let arg_ptr = *argv.offset(i as isize); + if arg_ptr.is_null() { + continue; + } + let mut len = 0; + while *arg_ptr.offset(len) != 0 { + len += 1; + } + match core::str::from_utf8(core::slice::from_raw_parts(arg_ptr, len as usize)) { + Ok(s) => s, + Err(_) => continue, + } + }; + + let mut result = unlink(path); + if result < 0 && is_recursive { + // unlink が失敗したらディレクトリかもしれないので rmdir を試す + result = rmdir(path); + } + + if result < 0 { + io::print("rm: cannot remove '"); + io::print(path); + io::print("'\n"); + ret = 1; + } + } + + ret +} diff --git a/src/utils/src/bin/seq.rs b/src/utils/src/bin/seq.rs new file mode 100644 index 0000000..f158918 --- /dev/null +++ b/src/utils/src/bin/seq.rs @@ -0,0 +1,119 @@ +#![no_std] +#![no_main] + +use swiftlib::io; + +fn parse_number(s: &str) -> Option { + let mut result = 0i64; + let mut negative = false; + let bytes = s.as_bytes(); + let mut i = 0; + + if !bytes.is_empty() && bytes[0] == b'-' { + negative = true; + i = 1; + } + + while i < bytes.len() { + if bytes[i] >= b'0' && bytes[i] <= b'9' { + result = result * 10 + (bytes[i] - b'0') as i64; + } else { + return None; + } + i += 1; + } + + Some(if negative { -result } else { result }) +} + +fn print_number(n: i64) { + if n < 0 { + io::print("-"); + print_number(-n); + return; + } + + let mut buf = [0u8; 20]; + let mut i = buf.len(); + let mut num = n; + + if num == 0 { + io::print("0"); + return; + } + + while num > 0 && i > 0 { + i -= 1; + buf[i] = b'0' + (num % 10) as u8; + num /= 10; + } + + if let Ok(s) = core::str::from_utf8(&buf[i..]) { + io::print(s); + } +} + +#[no_mangle] +pub extern "C" fn main(argc: i32, argv: *const *const u8) -> i32 { + if argc < 2 { + io::print("Usage: seq or seq \n"); + return 1; + } + + let (start, end) = if argc == 2 { + let end_str = unsafe { + let arg_ptr = *argv.offset(1); + if arg_ptr.is_null() { + return 1; + } + let mut len = 0; + while *arg_ptr.offset(len) != 0 { + len += 1; + } + match core::str::from_utf8(core::slice::from_raw_parts(arg_ptr, len as usize)) { + Ok(s) => s, + Err(_) => return 1, + } + }; + (1, parse_number(end_str).unwrap_or(1)) + } else { + let (start_str, end_str) = unsafe { + let start_ptr = *argv.offset(1); + let end_ptr = *argv.offset(2); + + let mut start_len = 0; + while !start_ptr.is_null() && *start_ptr.offset(start_len) != 0 { + start_len += 1; + } + + let mut end_len = 0; + while !end_ptr.is_null() && *end_ptr.offset(end_len) != 0 { + end_len += 1; + } + + let s = core::str::from_utf8(core::slice::from_raw_parts(start_ptr, start_len as usize)); + let e = core::str::from_utf8(core::slice::from_raw_parts(end_ptr, end_len as usize)); + + match (s, e) { + (Ok(a), Ok(b)) => (a, b), + _ => return 1, + } + }; + + (parse_number(start_str).unwrap_or(1), parse_number(end_str).unwrap_or(1)) + }; + + if start <= end { + for i in start..=end { + print_number(i); + io::print("\n"); + } + } else { + for i in (end..=start).rev() { + print_number(i); + io::print("\n"); + } + } + + 0 +} diff --git a/src/utils/src/bin/sleep.rs b/src/utils/src/bin/sleep.rs new file mode 100644 index 0000000..272d37e --- /dev/null +++ b/src/utils/src/bin/sleep.rs @@ -0,0 +1,61 @@ +#![no_std] +#![no_main] + +use swiftlib::{io, sys::SyscallNumber}; + +fn syscall1(num: u64, arg1: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn parse_number(s: &str) -> Option { + let mut result = 0u64; + for &b in s.as_bytes() { + if b >= b'0' && b <= b'9' { + result = result * 10 + (b - b'0') as u64; + } else { + return None; + } + } + Some(result) +} + +#[no_mangle] +pub extern "C" fn main(argc: i32, argv: *const *const u8) -> i32 { + if argc < 2 { + io::print("Usage: sleep \n"); + return 1; + } + + let secs = unsafe { + let arg_ptr = *argv.offset(1); + if arg_ptr.is_null() { + return 1; + } + let mut len = 0; + while *arg_ptr.offset(len) != 0 { + len += 1; + } + match core::str::from_utf8(core::slice::from_raw_parts(arg_ptr, len as usize)) { + Ok(s) => s, + Err(_) => return 1, + } + }; + + if let Some(n) = parse_number(secs) { + syscall1(SyscallNumber::Sleep as u64, n * 1000); + 0 + } else { + io::print("sleep: invalid number\n"); + 1 + } +} diff --git a/src/utils/src/bin/tail.rs b/src/utils/src/bin/tail.rs new file mode 100644 index 0000000..24b39de --- /dev/null +++ b/src/utils/src/bin/tail.rs @@ -0,0 +1,144 @@ +#![no_std] +#![no_main] + +extern crate alloc; +use alloc::vec::Vec; +use swiftlib::{io, sys::SyscallNumber}; + +fn syscall1(num: u64, arg1: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn syscall2(num: u64, arg1: u64, arg2: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + in("rsi") arg2, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn syscall3(num: u64, arg1: u64, arg2: u64, arg3: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + in("rsi") arg2, + in("rdx") arg3, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn open(path: &str) -> i64 { + let mut buf = [0u8; 512]; + let bytes = path.as_bytes(); + let len = bytes.len().min(511); + buf[..len].copy_from_slice(&bytes[..len]); + syscall2(SyscallNumber::Open as u64, buf.as_ptr() as u64, 0) as i64 +} + +fn read(fd: u64, buf: &mut [u8]) -> i64 { + syscall3( + SyscallNumber::Read as u64, + fd, + buf.as_mut_ptr() as u64, + buf.len() as u64, + ) as i64 +} + +fn close(fd: u64) { + syscall1(SyscallNumber::Close as u64, fd); +} + +#[no_mangle] +pub extern "C" fn main(argc: i32, argv: *const *const u8) -> i32 { + let lines_to_show = 10; + let start_idx = 1; + + if argc <= start_idx { + io::print("Usage: tail ...\n"); + return 1; + } + + for i in start_idx..argc { + let path = unsafe { + let arg_ptr = *argv.offset(i as isize); + if arg_ptr.is_null() { + continue; + } + let mut len = 0; + while *arg_ptr.offset(len) != 0 { + len += 1; + } + match core::str::from_utf8(core::slice::from_raw_parts(arg_ptr, len as usize)) { + Ok(s) => s, + Err(_) => continue, + } + }; + + let fd = open(path); + if fd < 0 { + io::print("tail: "); + io::print(path); + io::print(": cannot open\n"); + continue; + } + + let fd_u = fd as u64; + let mut content = Vec::new(); + let mut buf = [0u8; 2048]; + + // ファイル全体を読み込む + loop { + let n = read(fd_u, &mut buf); + if n <= 0 { + break; + } + content.extend_from_slice(&buf[..n as usize]); + } + + close(fd_u); + + // 改行位置を見つける + let mut newline_positions = Vec::new(); + for (idx, &byte) in content.iter().enumerate() { + if byte == b'\n' { + newline_positions.push(idx); + } + } + + // 最後の N 行を表示 + let start_pos = if newline_positions.len() > lines_to_show { + newline_positions[newline_positions.len() - lines_to_show] + 1 + } else { + 0 + }; + + if let Ok(s) = core::str::from_utf8(&content[start_pos..]) { + io::print(s); + } + } + + 0 +} diff --git a/src/utils/src/bin/touch.rs b/src/utils/src/bin/touch.rs new file mode 100644 index 0000000..d902d6a --- /dev/null +++ b/src/utils/src/bin/touch.rs @@ -0,0 +1,69 @@ +#![no_std] +#![no_main] + +use swiftlib::{io, sys::SyscallNumber}; + +fn syscall2(num: u64, arg1: u64, arg2: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + in("rsi") arg2, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn open(path: &str, flags: u64) -> i64 { + let mut buf = [0u8; 512]; + let bytes = path.as_bytes(); + let len = bytes.len().min(511); + buf[..len].copy_from_slice(&bytes[..len]); + syscall2(SyscallNumber::Open as u64, buf.as_ptr() as u64, flags) as i64 +} + +const O_CREAT: u64 = 0x40; +const O_RDWR: u64 = 0x02; + +#[no_mangle] +pub extern "C" fn main(argc: i32, argv: *const *const u8) -> i32 { + if argc <= 1 { + io::print("Usage: touch ...\n"); + return 1; + } + + let mut ret = 0; + for i in 1..argc { + let path = unsafe { + let arg_ptr = *argv.offset(i as isize); + if arg_ptr.is_null() { + continue; + } + let mut len = 0; + while *arg_ptr.offset(len) != 0 { + len += 1; + } + match core::str::from_utf8(core::slice::from_raw_parts(arg_ptr, len as usize)) { + Ok(s) => s, + Err(_) => continue, + } + }; + + let fd = open(path, O_CREAT | O_RDWR); + if fd < 0 { + io::print("touch: cannot touch '"); + io::print(path); + io::print("'\n"); + ret = 1; + } else { + // すぐに閉じる + syscall2(SyscallNumber::Close as u64, fd as u64, 0); + } + } + + ret +} diff --git a/src/utils/src/bin/tr.rs b/src/utils/src/bin/tr.rs new file mode 100644 index 0000000..f704ff6 --- /dev/null +++ b/src/utils/src/bin/tr.rs @@ -0,0 +1,78 @@ +#![no_std] +#![no_main] + +use swiftlib::io; + +#[no_mangle] +pub extern "C" fn main(argc: i32, argv: *const *const u8) -> i32 { + if argc < 2 { + io::print("Usage: tr \n"); + return 1; + } + + let (from_str, to_str) = if argc >= 3 { + unsafe { + let from_ptr = *argv.offset(1); + let to_ptr = *argv.offset(2); + + let mut from_len = 0; + while !from_ptr.is_null() && *from_ptr.offset(from_len) != 0 { + from_len += 1; + } + + let mut to_len = 0; + while !to_ptr.is_null() && *to_ptr.offset(to_len) != 0 { + to_len += 1; + } + + let f = core::str::from_utf8(core::slice::from_raw_parts(from_ptr, from_len as usize)); + let t = core::str::from_utf8(core::slice::from_raw_parts(to_ptr, to_len as usize)); + + match (f, t) { + (Ok(a), Ok(b)) => (a, b), + _ => return 1, + } + } + } else { + return 1; + }; + + if argc >= 4 { + let text = unsafe { + let arg_ptr = *argv.offset(3); + if arg_ptr.is_null() { + return 1; + } + let mut len = 0; + while *arg_ptr.offset(len) != 0 { + len += 1; + } + match core::str::from_utf8(core::slice::from_raw_parts(arg_ptr, len as usize)) { + Ok(s) => s, + Err(_) => return 1, + } + }; + + // 文字変換 + for ch in text.chars() { + if let Some(pos) = from_str.chars().position(|c| c == ch) { + if let Some(replacement) = to_str.chars().nth(pos) { + let mut buf = [0u8; 4]; + let s = replacement.encode_utf8(&mut buf); + io::print(s); + } else { + let mut buf = [0u8; 4]; + let s = ch.encode_utf8(&mut buf); + io::print(s); + } + } else { + let mut buf = [0u8; 4]; + let s = ch.encode_utf8(&mut buf); + io::print(s); + } + } + io::print("\n"); + } + + 0 +} diff --git a/src/utils/src/bin/true.rs b/src/utils/src/bin/true.rs new file mode 100644 index 0000000..8105a49 --- /dev/null +++ b/src/utils/src/bin/true.rs @@ -0,0 +1,9 @@ +#![no_std] +#![no_main] + +use swiftlib as _; + +#[no_mangle] +pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { + 0 +} diff --git a/src/utils/src/bin/uniq.rs b/src/utils/src/bin/uniq.rs new file mode 100644 index 0000000..ed4e7d9 --- /dev/null +++ b/src/utils/src/bin/uniq.rs @@ -0,0 +1,146 @@ +#![no_std] +#![no_main] + +extern crate alloc; +use alloc::vec::Vec; +use swiftlib::{io, sys::SyscallNumber}; + +fn syscall1(num: u64, arg1: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn syscall2(num: u64, arg1: u64, arg2: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + in("rsi") arg2, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn syscall3(num: u64, arg1: u64, arg2: u64, arg3: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + in("rsi") arg2, + in("rdx") arg3, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn open(path: &str) -> i64 { + let mut buf = [0u8; 512]; + let bytes = path.as_bytes(); + let len = bytes.len().min(511); + buf[..len].copy_from_slice(&bytes[..len]); + syscall2(SyscallNumber::Open as u64, buf.as_ptr() as u64, 0) as i64 +} + +fn read(fd: u64, buf: &mut [u8]) -> i64 { + syscall3( + SyscallNumber::Read as u64, + fd, + buf.as_mut_ptr() as u64, + buf.len() as u64, + ) as i64 +} + +fn close(fd: u64) { + syscall1(SyscallNumber::Close as u64, fd); +} + +#[no_mangle] +pub extern "C" fn main(argc: i32, argv: *const *const u8) -> i32 { + if argc < 2 { + io::print("Usage: uniq \n"); + return 1; + } + + let path = unsafe { + let arg_ptr = *argv.offset(1); + if arg_ptr.is_null() { + return 1; + } + let mut len = 0; + while *arg_ptr.offset(len) != 0 { + len += 1; + } + match core::str::from_utf8(core::slice::from_raw_parts(arg_ptr, len as usize)) { + Ok(s) => s, + Err(_) => return 1, + } + }; + + let fd = open(path); + if fd < 0 { + io::print("uniq: cannot open: "); + io::print(path); + io::print("\n"); + return 1; + } + + let mut content = Vec::new(); + let mut buf = [0u8; 2048]; + + loop { + let n = read(fd as u64, &mut buf); + if n <= 0 { + break; + } + content.extend_from_slice(&buf[..n as usize]); + } + + close(fd as u64); + + // 行ごとに処理 + let mut last_line = Vec::new(); + let mut current_line = Vec::new(); + + for &byte in &content { + if byte == b'\n' { + if current_line != last_line { + if let Ok(s) = core::str::from_utf8(¤t_line) { + io::print(s); + io::print("\n"); + } + last_line.clear(); + last_line.extend_from_slice(¤t_line); + } + current_line.clear(); + } else { + current_line.push(byte); + } + } + + // 最後の行 + if !current_line.is_empty() && current_line != last_line { + if let Ok(s) = core::str::from_utf8(¤t_line) { + io::print(s); + io::print("\n"); + } + } + + 0 +} diff --git a/src/utils/src/bin/wc.rs b/src/utils/src/bin/wc.rs new file mode 100644 index 0000000..0501ee8 --- /dev/null +++ b/src/utils/src/bin/wc.rs @@ -0,0 +1,175 @@ +#![no_std] +#![no_main] + +use swiftlib::{io, sys::SyscallNumber}; + +fn syscall1(num: u64, arg1: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn syscall2(num: u64, arg1: u64, arg2: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + in("rsi") arg2, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn syscall3(num: u64, arg1: u64, arg2: u64, arg3: u64) -> u64 { + let result: u64; + unsafe { + core::arch::asm!( + "syscall", + inlateout("rax") num => result, + in("rdi") arg1, + in("rsi") arg2, + in("rdx") arg3, + lateout("rcx") _, + lateout("r11") _, + ); + } + result +} + +fn open(path: &str) -> i64 { + let mut buf = [0u8; 512]; + let bytes = path.as_bytes(); + let len = bytes.len().min(511); + buf[..len].copy_from_slice(&bytes[..len]); + syscall2(SyscallNumber::Open as u64, buf.as_ptr() as u64, 0) as i64 +} + +fn read(fd: u64, buf: &mut [u8]) -> i64 { + syscall3( + SyscallNumber::Read as u64, + fd, + buf.as_mut_ptr() as u64, + buf.len() as u64, + ) as i64 +} + +fn close(fd: u64) { + syscall1(SyscallNumber::Close as u64, fd); +} + +fn print_number(n: u64) { + let mut buf = [0u8; 20]; + let mut i = buf.len(); + let mut num = n; + + if num == 0 { + io::print("0"); + return; + } + + while num > 0 && i > 0 { + i -= 1; + buf[i] = b'0' + (num % 10) as u8; + num /= 10; + } + + if let Ok(s) = core::str::from_utf8(&buf[i..]) { + io::print(s); + } +} + +#[no_mangle] +pub extern "C" fn main(argc: i32, argv: *const *const u8) -> i32 { + if argc <= 1 { + io::print("Usage: wc ...\n"); + return 1; + } + + for i in 1..argc { + let path = unsafe { + let arg_ptr = *argv.offset(i as isize); + if arg_ptr.is_null() { + continue; + } + let mut len = 0; + while *arg_ptr.offset(len) != 0 { + len += 1; + } + match core::str::from_utf8(core::slice::from_raw_parts(arg_ptr, len as usize)) { + Ok(s) => s, + Err(_) => continue, + } + }; + + let fd = open(path); + if fd < 0 { + io::print("wc: "); + io::print(path); + io::print(": cannot open\n"); + continue; + } + + let fd_u = fd as u64; + let mut buf = [0u8; 2048]; + let mut lines = 0u64; + let mut words = 0u64; + let mut bytes = 0u64; + let mut in_word = false; + + loop { + let n = read(fd_u, &mut buf); + if n <= 0 { + break; + } + + bytes += n as u64; + + for &byte in &buf[..n as usize] { + if byte == b'\n' { + lines += 1; + } + + let is_whitespace = byte == b' ' || byte == b'\t' || byte == b'\n' || byte == b'\r'; + if is_whitespace { + if in_word { + words += 1; + in_word = false; + } + } else { + in_word = true; + } + } + } + + if in_word { + words += 1; + } + + close(fd_u); + + // 出力 + io::print(" "); + print_number(lines); + io::print(" "); + print_number(words); + io::print(" "); + print_number(bytes); + io::print(" "); + io::print(path); + io::print("\n"); + } + + 0 +} diff --git a/src/utils/src/bin/which.rs b/src/utils/src/bin/which.rs new file mode 100644 index 0000000..7e03bbc --- /dev/null +++ b/src/utils/src/bin/which.rs @@ -0,0 +1,73 @@ +#![no_std] +#![no_main] + +use swiftlib::io; + +#[no_mangle] +pub extern "C" fn main(argc: i32, argv: *const *const u8) -> i32 { + if argc < 2 { + io::print("Usage: which \n"); + return 1; + } + + let cmd = unsafe { + let arg_ptr = *argv.offset(1); + if arg_ptr.is_null() { + return 1; + } + let mut len = 0; + while *arg_ptr.offset(len) != 0 { + len += 1; + } + match core::str::from_utf8(core::slice::from_raw_parts(arg_ptr, len as usize)) { + Ok(s) => s, + Err(_) => return 1, + } + }; + + // PATH はデフォルトで /bin + let path = "/bin"; + + // コマンドのフルパスを構築 + let mut full_path_buf = [0u8; 256]; + let path_bytes = path.as_bytes(); + let cmd_bytes = cmd.as_bytes(); + + let mut pos = 0; + for &b in path_bytes { + if pos >= 255 { + break; + } + full_path_buf[pos] = b; + pos += 1; + } + + if pos < 255 { + full_path_buf[pos] = b'/'; + pos += 1; + } + + for &b in cmd_bytes { + if pos >= 255 { + break; + } + full_path_buf[pos] = b; + pos += 1; + } + + // .elf 拡張子を追加 + if pos + 4 < 256 { + full_path_buf[pos] = b'.'; + full_path_buf[pos + 1] = b'e'; + full_path_buf[pos + 2] = b'l'; + full_path_buf[pos + 3] = b'f'; + pos += 4; + } + + if let Ok(full_path) = core::str::from_utf8(&full_path_buf[..pos]) { + io::print(full_path); + io::print("\n"); + } + + 0 +} diff --git a/src/utils/src/bin/yes.rs b/src/utils/src/bin/yes.rs new file mode 100644 index 0000000..d387220 --- /dev/null +++ b/src/utils/src/bin/yes.rs @@ -0,0 +1,32 @@ +#![no_std] +#![no_main] + +use swiftlib::io; + +#[no_mangle] +pub extern "C" fn main(argc: i32, argv: *const *const u8) -> i32 { + let text = if argc > 1 { + unsafe { + let arg_ptr = *argv.offset(1); + if !arg_ptr.is_null() { + let mut len = 0; + while *arg_ptr.offset(len) != 0 { + len += 1; + } + match core::str::from_utf8(core::slice::from_raw_parts(arg_ptr, len as usize)) { + Ok(s) => s, + Err(_) => "y", + } + } else { + "y" + } + } + } else { + "y" + }; + + loop { + io::print(text); + io::print("\n"); + } +} diff --git a/src/x86_64-mochios.json b/src/x86_64-mochios.json new file mode 100644 index 0000000..8460959 --- /dev/null +++ b/src/x86_64-mochios.json @@ -0,0 +1,21 @@ +{ + "llvm-target": "x86_64-unknown-linux-musl", + "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", + "arch": "x86_64", + "target-endian": "little", + "target-pointer-width": 64, + "target-c-int-width": 32, + "os": "linux", + "env": "musl", + "target-family": ["unix"], + "vendor": "unknown", + "linker-flavor": "ld.lld", + "linker": "rust-lld", + "executables": true, + "panic-strategy": "abort", + "disable-redzone": true, + "features": "-mmx,-avx", + "relocation-model": "static", + "code-model": "small", + "no-default-libraries": true +} \ No newline at end of file diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..fdf7e69 --- /dev/null +++ b/todo.md @@ -0,0 +1,113 @@ +# とぅーどぅー + +## セキュリティてきなとぅーどぅー +#### 最優先 +- [ ] サービス監視・自動復旧を実装 + - 対象: `src/services/core/src/main.rs`, `src/core/kernel.rs`, `src/services/index.toml` + - 内容: + - `core.service` に子サービスの生死監視(`wait`/状態監視)を実装 + - クラッシュ時の自動再起動(指数バックオフ、再起動上限、無限ループ防止) + - 重要サービス(`disk.service`, `fs.service`)の復旧ポリシーを明示 + - 完了条件: + - 重要サービスを強制終了しても自動復旧し、システム全体が停止しない + +- [ ] カーネル側にもウォッチドッグを追加する(core.service障害時の最終防衛) + - 対象: `src/core/kernel.rs`, `src/core/task/scheduler.rs` + - 内容: + - カーネルスレッドで heartbeat 監視 + - `core.service`無応答時の再起動/縮退モード移行 + - 完了条件: + - `core.service`が停止しても監視機構が生き残り、復旧動作が走る + +- [ ] ブート信頼連鎖(Secure Boot相当)を作る + - 対象: `src/boot/loader.rs`, `build.rs`, `builders/fs_image.rs` + - 内容: + - kernelとinitfsの署名検証(少なくともハッシュ+署名) + - リリース成果物の改ざん検出、ロールバック防止 + - 完了条件: + - 改ざんしたイメージ/バイナリを起動前に拒否できる + - ブートローダーは信頼しよう。じゃないとどうにもならねぇ。 + +- [ ] 実ユーザー権限モデル(UID/GID)を作る + - 対象: `src/core/syscall/pgroup.rs`, `src/core/syscall/fs.rs`, `src/core/task/process.rs` + - 内容: + - `getuid/getgid/geteuid/getegid` の常時 `0` を廃止 + - プロセスに資格情報を保持し、FSアクセス判定に反映 + - 完了条件: + - 非特権プロセスが root 専用操作へ到達できない + +- [ ] DoS耐性のためプロセスごとのリソース上限を作る + - 対象: `src/core/task/process.rs`, `src/core/syscall/process.rs`, `src/core/syscall/pgroup.rs` + - 内容: + - メモリ・スレッド・FD の上限を導入 + - `getrlimit/prlimit`無制限スタブを実装に置換 + - 完了条件: + - 悪性/暴走プロセスが単独でシステム全体を枯渇させられない + +#### 高優先 +- [ ] シグナル復帰アドレスを安全にする + - 対象: `src/core/syscall/signal.rs` + - 内容: + - `sa_restorer` を無検証で信頼しない + - カーネル生成の固定 `sigreturn` 経路へ寄せる + - 完了条件: + - 任意 `restorer` 指定で制御フローを奪えない + +- [ ] ELFロード後の W^X を徹底する + - 対象: `src/core/task/elf.rs`, `src/core/syscall/exec.rs`, `src/core/mem/paging.rs` + - 内容: + - ロード中のみRW、実行時は`PF_W`に応じて最終保護へ変更 + - 実行可能ページの不要な書き込み権限を除去 + - 完了条件: + - 実行セグメントが常時writableにならない + +- [ ] 例外経路のKPTI/SMAP適用を統一する + - 対象: `src/core/interrupt/idt.rs`, `src/core/syscall/syscall_entry.rs`, `src/core/syscall/mod.rs` + - 内容: + - SYSCALL経路と同等に、例外/IRQでもCR3切替とユーザメモリアクセス制御を統一 + - 完了条件: + - エントリ種別(syscall/exception/irq)で保護レベル差が残らない + +- [ ] **IPCのアクセス制御を強化する** + - 対象: `src/core/syscall/ipc.rs`, `src/services/fs/src/main.rs`, `src/services/disk/src/main.rs` + - 内容: + - サービス別 ACL(送信元権限/送信元PID/操作種別) + - 重要操作はcapabilityトークンを要求 + - 完了条件: + - 非許可主体からのIPCリクエストが拒否される + +- [ ] `mprotect` を実装し、メモリ保護変更を本物にする + - 対象: `src/core/syscall/pgroup.rs`, `src/core/mem/paging.rs` + - 内容: + - 現在のスタブ (`SUCCESS`返却中心) を廃止 + - ユーザ空間ページに対する保護更新と検証を追加 + - 完了条件: + - `mprotect`が実際にページ属性を変える + +#### 中優先 +- [ ] クラッシュテレメトリの永続化 + - 対象: `src/core/panic.rs`, `src/core/interrupt/idt.rs`, `src/services/fs/src/main.rs` + - 内容: + - クラッシュ情報をリングバッファ化し、再起動後に回収可能にする + - 完了条件: + - 再起動後に直前クラッシュ情報を取得できる + +- [ ] 監査ログ/セキュリティイベントログを整備する + - 対象: `src/core/util/log.rs`, `src/core/syscall/*`, `src/services/*` + - 内容: + - 認可失敗、異常IPC、再起動理由、例外統計を記録 + - 現状はログの出力先と粒度が不統一で、障害解析に必要な情報が追跡しづらい + - 完了条件: + - 重大イベントの追跡が可能 + +- [ ] 安全な更新/ロールバック基盤を設計して作る + - 対象: `build.rs`, `builders/*`, `fs/`, `ramfs/` + - 内容: + - A/B的な更新、検証失敗時の既知良好版復帰を用意 + - 完了条件: + - 不正/破損更新で起動不能にならない + +#### 今の実装 +- `core.service`は監視ループのみで、再起動監督機構は未実装 +- UID/GID系syscallは現状すべて`0`を返す +- カーネル panic は停止(`hlt` ループ)で、自己復旧経路は未実装 \ No newline at end of file diff --git a/tools/msig/.cargo/config.toml b/tools/msig/.cargo/config.toml new file mode 100644 index 0000000..1fc5ddf --- /dev/null +++ b/tools/msig/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "x86_64-unknown-linux-gnu" \ No newline at end of file diff --git a/tools/msig/Cargo.toml b/tools/msig/Cargo.toml new file mode 100644 index 0000000..d7ca749 --- /dev/null +++ b/tools/msig/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "msig" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/tools/msig/src/main.rs b/tools/msig/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/tools/msig/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/x86_64-mochios.json b/x86_64-mochios.json new file mode 100644 index 0000000..39df612 --- /dev/null +++ b/x86_64-mochios.json @@ -0,0 +1,25 @@ +{ + "llvm-target": "x86_64-unknown-linux-musl", + "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", + "arch": "x86_64", + "target-endian": "little", + "target-pointer-width": 64, + "target-c-int-width": 32, + "os": "linux", + "env": "musl", + "target-family": ["unix"], + "vendor": "unknown", + "linker-flavor": "ld.lld", + "linker": "rust-lld", + "executables": true, + "panic-strategy": "abort", + "disable-redzone": true, + "features": "-mmx,-avx", + "relocation-model": "static", + "code-model": "small", + "no-default-libraries": true, + "pre-link-args": { + "ld.lld": ["-e", "_start"] + } +} +