|
|
nono 1.7.0
//
// nono
// Copyright (C) 2024 nono project
// Licensed under nono-license.txt
//
//
// virt-m68k の IPLROM 相当の何か
//
// IODevice
// |
// +- ROMDevice (LoadROM()、ウェイト、マスク等を持つ)
// | +- PROMDevice (LUNA* の PROM)
// | +- IPLROM1Device (X680x0 の IPLROM 後半)
// | +- IPLROM2Device (X680x0 の IPLROM 前半)
// | +- CGROMDevice (X680x0 の CGROM)
// | |
// | +- ROMEmuDevice
// | +- LunaPROMEmuDevice (LUNA PROM エミュレーションの共通部分)
// | | +- Luna1PROMEmuDevice (LUNA-I の PROM エミュレーション)
// | | +- Luna88kPROMEmuDevice (LUNA-88K の PROM エミュレーション)
// | +- NewsROMEmuDevice (NEWS の ROM エミュレーション)
// | +- ROM30EmuDevice (X68030 の ROM30 エミュレーション)
// | |
// | | +---------------------+
// | +-| Virt68kROMEmuDevice | (virt-m68k の IPLROM 相当の何か)
// | +---------------------+
// |
// +- PROM0Device (LUNA* のブートページ切り替え用プロキシ)
// +- IPLROM0Device (X680x0 のブートページ切り替え用プロキシ)
#include "romemu_virt68k.h"
#include "autofd.h"
#include "config.h"
#include "iodevstream.h"
#include "mainapp.h"
#include "mainram.h"
#include "memorystream.h"
#include "mpu680x0.h"
#include "power.h"
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <random>
// コンストラクタ
Virt68kROMEmuDevice::Virt68kROMEmuDevice()
: inherited(OBJ_PROM)
{
}
// デストラクタ
Virt68kROMEmuDevice::~Virt68kROMEmuDevice()
{
}
// 初期化
bool
Virt68kROMEmuDevice::Init()
{
// ROM 領域を用意。サイズは適当。
if (AllocROM(2048, 0) == false) {
// エラーメッセージは表示済み。
return false;
}
mainram = GetMainRAMDevice();
MemoryStreamBE roms(imagebuf.get());
roms.Write4(0x00001000); // +00: リセットベクタ SP
roms.Write4(0xff000400); // +04: リセットベクタ PC
// ベクタ
for (int i = 2; i < 256; i++) {
roms.Write4(0);
}
// リセット時に実行する命令
roms.SetOffset(0x400);
roms.Write2(0x2039); // move.l (ROMIO_INIT),d0
roms.Write4(ROMIO_INIT);
roms.Write2(0x0c80); // cmpi.l #0xffffffff,d0
roms.Write4(0xffffffff);
roms.Write2(0x6704); // beq @f
roms.Write2(0x2040); // move.l d0,a0 ; ジャンプ先を a0 にセット
roms.Write2(0x4ed0); // jmp (a0) ; ジャンプ
//@@:
roms.Write4(0x4e722700); // stop #0x2700
roms.Write2(0x67fc); // bra @b
return true;
}
void
Virt68kROMEmuDevice::ResetHard(bool poweron)
{
// 起動時に RAM にリセットベクタを書き込んでおく。(手抜き)
mainram->WriteMem(0, &imagebuf[0], 8);
}
// ROMIO から読み出す。
// ROMIO が応答すべきでなければ (uint64)-1 を返す。
uint64
Virt68kROMEmuDevice::ReadROMIO(busaddr addr)
{
if (addr.GetSize() == 4) {
switch (addr.Addr()) {
case ROMIO_INIT:
return ROM_Init();
default:
break;
}
}
return (uint64)-1;
}
uint32
Virt68kROMEmuDevice::ROM_Init()
{
// -X file で指定されたホストファイルを読み込んでエントリポイント
// アドレスをこの Read アクセスの読み出し値として返す。
LoadInfo info(gMainApp.exec_file);
// QEMU がシンボルテーブルをロードしないので、
// カーネル側もそのように想定しているようだ。
info.loadsym = false;
if (BootLoader::LoadExec(mainram, &info) == false) {
// エラーメッセージは表示済み。
auto power = GetPowerDevice();
power->PushPowerButton();
return -1;
}
if (MakeBootInfo(&info) == false) {
return -1;
}
putmsg(1, "Host program loaded. Entry point = $%08x", info.entry);
return info.entry;
}
// ブート情報を便利に扱うためのクラス。
class BootInfo
{
public:
uint16 bi_tag {}; // タグ
std::vector<uint8> bi_data {}; // タグごと決まる可変のデータ
public:
BootInfo();
void Init(uint16 tag_);
void Init(uint16 tag_, uint32 val_);
void AddWord(uint32 val_);
void AddLong(uint32 val_);
void AddString(const char *val_);
uint32 GetWord(uint32 offset) const;
uint32 GetLong(uint32 offset) const;
const char *GetString() const { return (const char *)bi_data.data(); }
};
// コンストラクタ
BootInfo::BootInfo()
{
}
// タグだけ指定して初期化。
void
BootInfo::Init(uint16 tag_)
{
bi_tag = tag_;
bi_data.clear();
}
// タグと整数1つを指定して初期化。
void
BootInfo::Init(uint16 tag_, uint32 val_)
{
Init(tag_);
AddLong(val_);
}
// データに 16 ビット整数を1つ追加。
void
BootInfo::AddWord(uint32 val_)
{
// bi_data はビッグエンディアン
bi_data.push_back(val_ >> 8);
bi_data.push_back(val_);
}
// データに 32 ビット整数を1つ追加。
void
BootInfo::AddLong(uint32 val_)
{
// bi_data はビッグエンディアン
bi_data.push_back(val_ >> 24);
bi_data.push_back(val_ >> 16);
bi_data.push_back(val_ >> 8);
bi_data.push_back(val_);
}
// データに文字列を追加する。
void
BootInfo::AddString(const char *val_)
{
for (const char *s = val_; *s; s++) {
bi_data.push_back(*s);
}
bi_data.push_back('\0');
}
// offset の位置からの 16 ビット整数を取得。
uint32
BootInfo::GetWord(uint32 offset) const
{
if (offset + 2 > bi_data.size()) {
return -1;
}
uint32 data;
data = bi_data[offset + 0] << 8;
data |= bi_data[offset + 1];
return data;
}
// offset の位置からの 32 ビット整数を取得。
uint32
BootInfo::GetLong(uint32 offset) const
{
if (offset + 4 > bi_data.size()) {
return -1;
}
uint32 data;
data = GetWord(offset + 0) << 16;
data |= GetWord(offset + 2);
return data;
}
// BootInfo について。
//
// 伝統的な(?) NetBSD アーキテクチャは、ブートローダからカーネルへの
// パラメータはレジスタ渡しかスタック渡しでアーキテクチャごとに固定の
// フォーマットになっている。少なくともいくつかの m68k では。
// NetBSD/virt68k では Linux/m68k の方式を採用しているようだが、
// これは virt-m68k がそういう仕様だから。
//
// NetBSD/virt68k のブートパラメータ BootInfo は、カーネルがロードされた
// セクションの末尾 (リンカによってアドレスが求まる) 以降の次のロングワード
// 境界から連続して配置する。BootInfo はいわゆる Type-Length-Value (TLV)
// 形式のデータで、基本フォーマットは次の通り。
//
// struct {
// uint16 bi_tag;
// uint16 bi_size;
// uint8[] bi_data;
// };
//
// bi_tag がこのパラメータの種別を表し、BI_LAST(=0) のタグで終了する。
// bi_size はタグとサイズフィールドも含めたバイト数。
// bi_data は 0 バイト以上の可変長データで、内容はタグによって決まっている。
// 各パラメータはロングワード境界に整列していること。
//
// これによって、ブートローダというかエミュレータ本体から自由度の高い
// パラメータを受け渡すことが出来る。例えば通常の m68k カーネルが自力で
// 判別している CPU 種別や FPU 種別、デバイスがどのアドレス・IRQ にあるか
// といったハードウェア構成情報もすべてブートパラメータで渡す。
// ブートパラメータを用意。
bool
Virt68kROMEmuDevice::MakeBootInfo(LoadInfo *info)
{
IODeviceStream ds(mainram, info->end);
BootInfo bi;
uint32 cpu_type;
uint32 mmu_type;
uint32 fpu_type;
auto mpu680x0 = GetMPU680x0Device(mpu);
switch (mpu680x0->GetMPUType()) {
case m680x0MPUType::M68030:
cpu_type = BI_CPU_68030;
mmu_type = BI_MMU_68030;
if (mpu680x0->HasFPU()) {
fpu_type = BI_FPU_68881;
} else {
fpu_type = 0;
}
break;
case m680x0MPUType::M68040:
cpu_type = BI_CPU_68040;
mmu_type = BI_MMU_68040;
fpu_type = BI_FPU_68040;
break;
default:
assert(false);
}
bi.Init(BI_MACHTYPE, BI_MACH_VIRT);
WriteBootInfo(ds, bi);
bi.Init(BI_CPUTYPE, cpu_type);
WriteBootInfo(ds, bi);
bi.Init(BI_MMUTYPE, mmu_type);
WriteBootInfo(ds, bi);
if (fpu_type != 0) {
bi.Init(BI_FPUTYPE, fpu_type);
WriteBootInfo(ds, bi);
}
uint32 ramsize = (uint32)mainram->GetSize();
// RAMDISK は (あれば) initrd イメージ置き場で、
// メモリセグメントとは別セグメントにする必要がある。
// そのためここでは MainRAM の末尾に置く。
const ConfigItem& initrd_item = gConfig->Find("exec-initrd");
const std::string& initrd = initrd_item.AsString();
if (initrd.empty() == false) {
std::string path = gMainApp.NormalizePath(initrd);
const char *cpath = path.c_str();
autofd fd;
struct stat st;
uint8 *mdata;
size_t rdsize;
fd = open(cpath, O_RDONLY);
if (fd == -1) {
warn("%s \"%s\" open failed", __method__, cpath);
return false;
}
if (fstat(fd, &st) == -1) {
warn("%s \"%s\" fstat failed", __method__, cpath);
return false;
}
if (S_ISDIR(st.st_mode)) {
warnx("%s \"%s\" is a directory", __method__, cpath);
return false;
}
rdsize = st.st_size;
mdata = (uint8 *)mmap(NULL, rdsize, PROT_READ, MAP_PRIVATE, fd, 0);
if (mdata == MAP_FAILED) {
warnx("%s \"%s\" mmap failed", __method__, cpath);
return false;
}
// ロード。
LoadInfo rdinfo(cpath, mdata, rdsize);
rdinfo.start = (ramsize - rdsize) & ~0xfff;
bool rv = BootLoader::LoadData(mainram, &rdinfo);
munmap(mdata, rdsize);
if (rv == false) {
warnx("** Couldn't load specified initrd.");
return false;
}
bi.Init(BI_RAMDISK);
bi.AddLong(rdinfo.start); // bi_mem_info.addr
bi.AddLong(rdsize); // bi_mem_info.size
WriteBootInfo(ds, bi);
// ここをメモリ上限にする。
ramsize = rdinfo.start;
}
// MEMCHUNK は RAM 配置情報。
bi.Init(BI_MEMCHUNK);
bi.AddLong(0); // bi_mem_info.addr
bi.AddLong(ramsize); // bi_mem_info.size
WriteBootInfo(ds, bi);
// 乱数シード。QEMU は 32 バイト渡してるようだ。
std::random_device rng;
const uint32 rng_bytes = 32;
bi.Init(BI_RNG_SEED);
bi.AddWord(rng_bytes); // bi_data.data_length
for (int i = 0; i < rng_bytes / 4; i++) {
bi.AddLong(rng()); // bi_data.data_bytes
}
WriteBootInfo(ds, bi);
// GFPIC
// 先頭のデバイスのアドレスとベース IRQ だけ指定する。
// アドレスを +$1000 番地、IRQ は +1 ずつずらして計 6個あるというのは
// カーネル側にハードコードしてあるようだ。
// $ff01'1000 IRQ=1 : GFPIC1
// $ff01'2000 IRQ=2 : GFPIC2
// $ff01'3000 IRQ=3 : GFPIC3
// $ff01'4000 IRQ=4 : GFPIC4
// $ff01'5000 IRQ=5 : GFPIC5
// $ff01'6000 IRQ=6 : GFPIC6
bi.Init(BI_GFPIC);
bi.AddLong(0xff011000); // addr
bi.AddLong(1); // irq
WriteBootInfo(ds, bi);
// GFRTC
// 先頭アドレスと IRQ だけ指定する。
// アドレスを +$1000 番地したところに GFRTC があるというのは
// カーネル側にハードコードしてあるようだ。
// $ff02'0000 IRQ=PIC6:IRQ1 : GFTimer
// $ff02'1000 : GFRTC
bi.Init(BI_GFRTC);
bi.AddLong(0xff020000); // addr
bi.AddLong(8 + (6 - 1) * 32 + 0); // irq
WriteBootInfo(ds, bi);
// GFTTY
// IRQ は PIC1:IRQ32
bi.Init(BI_GFTTY);
bi.AddLong(0xff030000); // addr
bi.AddLong(8 + (1 - 1) * 32 + 31); // irq
WriteBootInfo(ds, bi);
// QemuSysCtlr
bi.Init(BI_CTRL);
bi.AddLong(0xff040000); // addr
bi.AddLong(0); // irq
WriteBootInfo(ds, bi);
// VirtIO
bi.Init(BI_VIRTIO);
bi.AddLong(0xff070000); // addr
bi.AddLong(8 + (2 - 1) * 32 + 0); // base irq
WriteBootInfo(ds, bi);
// ブートパラメータ
const ConfigItem& bootparam_item = gConfig->Find("exec-bootparam");
const std::string& bootparam = bootparam_item.AsString();
if (bootparam.empty() == false) {
bi.Init(BI_COMMANDLINE);
bi.AddString(bootparam.c_str());
WriteBootInfo(ds, bi);
}
bi.Init(BI_LAST);
WriteBootInfo(ds, bi);
return true;
}
#define ENTRY(x) { (x), #x }
/*static*/ const std::pair<uint32, const char *>
Virt68kROMEmuDevice::tag_str[] = {
ENTRY(BI_LAST),
ENTRY(BI_MACHTYPE),
ENTRY(BI_CPUTYPE),
ENTRY(BI_FPUTYPE),
ENTRY(BI_MMUTYPE),
ENTRY(BI_MEMCHUNK),
ENTRY(BI_RAMDISK),
ENTRY(BI_COMMANDLINE),
ENTRY(BI_RNG_SEED),
ENTRY(BI_QEMU_VER),
ENTRY(BI_GFPIC),
ENTRY(BI_GFRTC),
ENTRY(BI_GFTTY),
ENTRY(BI_VIRTIO),
ENTRY(BI_CTRL),
};
/*static*/ const std::pair<uint32, const char *>
Virt68kROMEmuDevice::mach_str[] = {
ENTRY(BI_MACH_VIRT),
};
/*static*/ const std::pair<uint32, const char *>
Virt68kROMEmuDevice::cpu_str[] = {
ENTRY(BI_CPU_68020),
ENTRY(BI_CPU_68030),
ENTRY(BI_CPU_68040),
ENTRY(BI_CPU_68060),
};
/*static*/ const std::pair<uint32, const char *>
Virt68kROMEmuDevice::fpu_str[] = {
ENTRY(BI_FPU_68881),
ENTRY(BI_FPU_68882),
ENTRY(BI_FPU_68040),
ENTRY(BI_FPU_68060),
};
/*static*/ const std::pair<uint32, const char *>
Virt68kROMEmuDevice::mmu_str[] = {
ENTRY(BI_MMU_68851),
ENTRY(BI_MMU_68030),
ENTRY(BI_MMU_68040),
ENTRY(BI_MMU_68060),
};
#undef ENTRY
#define GetStr(array, v) GetStr1(array, countof(array), v)
static std::string
GetStr1(const std::pair<uint32, const char *> *map, uint mapcount, uint32 v)
{
for (int i = 0; i < mapcount; i++) {
if (map[i].first == v) {
return map[i].second;
}
}
return string_format("(%u)", v);
}
// BootInfo を stream に書き出す。
void
Virt68kROMEmuDevice::WriteBootInfo(IODeviceStream& stream, BootInfo& bi)
{
// ロングワード境界にパディング。
uint16 size = 2 + 2 + bi.bi_data.size();
for (; (size % 4) != 0; size++) {
bi.bi_data.push_back(0);
}
// デバッグ表示。
if (__predict_false(loglevel >= 1)) {
std::string tagname = GetStr(tag_str, bi.bi_tag);
std::string buf;
switch (bi.bi_tag) {
case BI_LAST:
break;
case BI_MACHTYPE:
buf = GetStr(mach_str, bi.GetLong(0));
break;
case BI_CPUTYPE:
buf = GetStr(cpu_str, bi.GetLong(0));
break;
case BI_FPUTYPE:
buf = GetStr(fpu_str, bi.GetLong(0));
break;
case BI_MMUTYPE:
buf = GetStr(mmu_str, bi.GetLong(0));
break;
case BI_MEMCHUNK:
case BI_RAMDISK:
buf = string_format("addr=$%08x, len=$%08x",
bi.GetLong(0), bi.GetLong(4));
break;
case BI_GFPIC:
case BI_GFRTC:
case BI_GFTTY:
case BI_VIRTIO:
case BI_CTRL:
buf = string_format("addr=$%08x, irq=$%08x",
bi.GetLong(0), bi.GetLong(4));
break;
case BI_RNG_SEED:
buf = string_format("len=%u", bi.GetWord(0));
break;
case BI_COMMANDLINE:
buf = string_format("\"%s\"", bi.GetString());
break;
}
putmsg(1, "BootInfo $%08x: %-14s: %s",
stream.GetAddr(), tagname.c_str(), buf.c_str());
}
// 出力。
stream.Write2(bi.bi_tag);
stream.Write2(size);
for (const auto c : bi.bi_data) {
stream.Write1(c);
}
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.