MikanOSのUSBブートの方法とmikanos-netの実機での通信実験
概要
MikanOSをUSBブートの方法についてまとめる。
また後半では、mikanos-netのe1000のデバイスドライバが実機でも使えることを確かめる実験を行っている様子を載せてている。
行った環境
Dellのデスクトップを使用した。
CPUはintel製で64ビットである。
(型番は忘れてしまった。)
そこにUSB端子でキーボードとマウスを接続した。
なおPanasonic製のノートパソコンであるLet't noteで試したところ、内臓キーボードとUSBマウスを認識しなかった。
方法
まずマシンのBIOS画面に入り、セキュアブートをOFFにしたのち、UEFIブートの優先順位のトップをUSBブートにする。
そして、USBメモリの適切な場所にブートローダ・カーネルコード・アプリのコードのバイナリを配置したのちマシンを起動すると、MikanOSが実機で立ち上がる。
コード修正
qiita.com
このサイトを参考に、2箇所の修正を行った。
エントリポイント
メモリマップを確認したところ、kernelのmain関数がロードされるアドレスが予約済みの領域だったため、Makefileを修正した。
kernel/Makefile
#LDFLAGS += --entry KernelMain -z norelro --image-base 0x100000 --static LDFLAGS += --entry KernelMain -z norelro --image-base 0x110000 --static
USBマウスの認識
上記のサイトを参考にして、以下の点の修正を行った。
詳細はリンクを見てほしい。
kernel/usb/memory.hpp
// [org] static const size_t kMemoryPoolSize = 4096 * 32 static const size_t kMemoryPoolSize = 4096 * 64;
USBメモリへのコピー
上記の点を修正してBuildしたら、次はその内容をUSBメモリに格納する。
一つ一つコマンドを打っていくと長くなるので、シェルスクリプトを利用した。
ゼロからのOS自作入門メモ: 実機で動かすためのメモzenn.dev
このシェルスクリプトは、こちらのブログを執筆されているmaeharinさんからいただいたものを改造して作った。(ありがとうございます。)
大まかな流れとしては、
- mkfsコマンドでUSBメモリにFATファイルシステムを構築する
- USBメモリをマウントする
- その中にカーネルコード・ブートローダ・アプリケーションのバイナリファイルをコピーしてくる
- アンマウントする
という流れである。
UEFIの仕様により、ブートローダは/EFI/BOOT/BOOTX64.EFIに配置することとする。
ゼロからのOS自作入門メモ: 第1章
#!/bin/bash set -ex # # usbを差し込んでdmsgコマンドを実行した結果、/dev/sda1になっていることを確認のうえ実行 #/path_to_mikanosのところは、自分の環境のものに置き換える # USB=/dev/sda1 KERNEL_ELF=/path_to_mikanos/mikanos/kernel/kernel.elf LOADER_EFI=$HOME/edk2/Build/MikanLoaderX64/DEBUG_CLANG38/X64/Loader.efi APPS_DIR_IN_DEV=/path_to_mikanos/mikanos/apps MNT_DIR=/mnt/usbmem umount $USB mkfs.fat $USB mkdir -p $MNT_DIR mount $USB $MNT_DIR mkdir -p $MNT_DIR/EFI/BOOT cp $KERNEL_ELF $MNT_DIR/kernel.elf cp $LOADER_EFI $MNT_DIR/EFI/BOOT/BOOTX64.EFI #アプリをすべてコピー for APP in $(ls $APPS_DIR_IN_DEV) do if [ -f "$APPS_DIR_IN_DEV/$APP/$APP" ] then cp "$APPS_DIR_IN_DEV/$APP/$APP" $MNT_DIR fi done umount $USB
mikanos-netを用いたUbuntuマシンとの通信の実験
MikanOSにNIC であるe1000のデバイスドライバとTCP/IPプロトコルを入れたものを、mikanos-netという名前で公開してくださっている方がいらっしゃる。
github.com
このコードを利用して、mikanos-net搭載のマシンとUbuntuマシン間で通信を行う実験を行った。
ソースコードの改変
PCIバスからNICを識別するところで、ベンダIDを指定する部分の処理を省いた。
この操作が必要であるかどうかは、どのマシンを用いて実験するかによると思う。
for (int i = 0; i < pci::num_device; ++i) { //if (pci::ReadVendorId(pci::devices[i]) != 0x8086 || pci::ReadDeviceId(pci::devices[i]) != 0x10d3) { if ( !(pci::devices[i].class_code.Match(0x02u, 0x00u, 0x00u))) { //変更箇所 continue; }
加えて、デバイスドライバ内の送信処理の中のbusy waitの部分にデバッグプリントを加えた。
空のwhile文をやめたのだが、なぜこれでうまくいったかは分かっていないが、最適化がかかってbusy waitの部分がバイナリから省かれたのかもしれない。
static ssize_t e1000_write(struct net_device *dev, const uint8_t *data, size_t len) { struct e1000 *adapter = (struct e1000 *)dev->priv; uint32_t tail = e1000_reg_read(adapter, E1000_TDT); struct tx_desc *desc = &adapter->tx_ring[tail]; desc->addr = (uintptr_t)data; desc->length = len; desc->status = 0; desc->cmd = (E1000_TXD_CMD_EOP | E1000_TXD_CMD_RS); debugf("%s: %u bytes data transmit", adapter->dev->name, desc->length); e1000_reg_write(adapter, E1000_TDT, (tail + 1) % TX_RING_SIZE); while(!(desc->status & 0x0f)) { // busy wait debugf("busy wait");//追加分:ここを抜くと固まる } return len; }