BYB

低レイヤ好き学生エンジニアによる備忘録

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さんからいただいたものを改造して作った。(ありがとうございます。)

大まかな流れとしては、

という流れである。
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;
 }

実験

2台のサーバをLANケーブルで繋いだのち、片方にUbuntuをインストールして適当なIPアドレスを設定する。
もう片方にはmikanos-netをUSBブートで立ち上げて、こちらもIPアドレスを設定する。
その後、mikanos-net側でtcp echoサーバを立ち上げ、Ubuntu側からncコマンドで文字列をパケットに乗せて送信する。

TCP echoサーバ(mikanos-net)
クライアント側(Ubuntu)

実機を用いても、e1000のデバイスドライバを通じてパケットのやり取りができることが分かった。