|
|
nono 0.4.0
//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//
//
// メインメモリ
//
#include "ram.h"
#include "aout.h"
#include "autofd.h"
#include "config.h"
#include "elf.h"
#include "iodevstream.h"
#include "mainapp.h"
#include "mpu.h"
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <zlib.h>
// グローバル参照用
RAMDevice *gRAM;
// RAM はロングワード単位でホストバイトオーダ配置なので、
// バイトアクセス、ワードアクセス時は HLB(), HLW() マクロを使用のこと。
std::unique_ptr<uint8[]> mainram;
// コンストラクタ
RAMDevice::RAMDevice()
: inherited("RAM")
{
devaddr = 0;
// アクセスウェイト
switch (gMainApp.GetVMType()) {
case VMType::LUNA88K:
// 取扱説明書p.21 に、CMMU からのアクセスタイムが
// リード(4byte)で 200ns、バーストリード(16byte)で 320ns
// ライト(4byte)で 160ns、バーストライト(16byte)で 280ns とある。
// 25MHz は 40ns/clockで、この数値は CMMU からのアクセス全体の時間
// らしいので、ここで加算するウェイトはそれから 2クロック引いたもの。
read_wait = 3;
write_wait = 2;
break;
case VMType::LUNA1:
// LUNA-I は1ウェイト。
read_wait = 1;
write_wait = 1;
break;
case VMType::X68030:
default:
// X68030 ではシステムポートが設定するのでここでは不要。
break;
}
}
// デストラクタ
RAMDevice::~RAMDevice()
{
gRAM = NULL;
}
bool
RAMDevice::Init()
{
int ram_size_MB;
const ConfigItem& item = gConfig->Find("ram-size");
ram_size_MB = item.AsInt();
if (ram_size_MB < 1) {
item.Err();
return false;
}
// 下限以外の制約は機種による
switch (gMainApp.GetVMType()) {
case VMType::X68030:
// メインメモリは 12MB まで。
// 内部は 8KB 単位で処理できるが設定が 1MB 単位。
if (ram_size_MB > 12) {
item.Err("configurable maximum main memory size is 12 [MB]");
return false;
}
break;
case VMType::LUNA1:
// 16MB までは、実機に合わせて 4MB 単位とする。
// see http://wiki.netbsd.org/ports/luna68k/luna68k_info/#hardware
//
// 16MB を超える増設 (or 魔改造) 部分は 1MB 単位で 255MB までとする。
// 実 ROM のメモリチェックは 32KB だかそのくらいずつ行われ、
// 最後に番兵として Null 領域 (BusError ではなく) が必要。
// 現行の構成をあまり変えない範囲なら厳密には (256MB - 32KB) が
// 上限だが、設定ファイルからの指定が 1MB 単位なので 255MB。
if (ram_size_MB <= 16) {
if (ram_size_MB % 4 != 0) {
item.Err("for 16MB or less, must be specified in 4 [MB] unit");
return false;
}
} else if (ram_size_MB > 255) {
item.Err("configurable maximum RAM size is 255 [MB]");
return false;
}
break;
case VMType::LUNA88K:
// 64MB までは、実機に合わせて 16MB 単位とする。
//
// 64MB を超える増設 (or 魔改造) 部分は未確認。
if (ram_size_MB <= 64) {
if (ram_size_MB % 16 != 0) {
item.Err("for 64MB or less, must be specified in 16 [MB] unit");
return false;
}
} else if (ram_size_MB > 255) {
item.Err("configurable maximum RAM size is 255 [MB]");
return false;
}
break;
default:
__unreachable();
}
ram_size = ram_size_MB * 1024 * 1024;
mainram.reset(new uint8[ram_size]);
// devtable[] への RAM デバイスの配置はこの後 VM::Init 側で行っている
return true;
}
// 電源オン/リセット
void
RAMDevice::ResetHard(bool poweron)
{
if (poweron) {
// XXX ノイズを用意する
}
// X68030 ではリセットで戻るはず。(LUNA は固定なので関係ない)
if (gMainApp.GetVMType() == VMType::X68030) {
read_wait = 0;
write_wait = 0;
}
}
uint64
RAMDevice::Read8(uint32 addr)
{
gMPU->AddCycle(read_wait);
uint32 data;
data = mainram[HLB(addr)];
putlog(4, "$%08x.B -> $%02x", addr, data);
return data;
}
uint64
RAMDevice::Read16(uint32 addr)
{
gMPU->AddCycle(read_wait);
uint32 data;
data = *(uint16 *)&mainram[HLW(addr)];
putlog(4, "$%08x.W -> $%04x", addr, data);
return data;
}
uint64
RAMDevice::Read32(uint32 addr)
{
gMPU->AddCycle(read_wait);
uint32 data;
data = *(uint32 *)&mainram[addr];
putlog(4, "$%08x.L -> $%08x", addr, data);
return data;
}
uint64
RAMDevice::Write8(uint32 addr, uint32 data)
{
gMPU->AddCycle(write_wait);
mainram[HLB(addr)] = data;
putlog(3, "$%08x.B <- $%02x", addr, data);
return 0;
}
uint64
RAMDevice::Write16(uint32 addr, uint32 data)
{
gMPU->AddCycle(write_wait);
*(uint16 *)&mainram[HLW(addr)] = data;
putlog(3, "$%08x.W <- $%04x", addr, data);
return 0;
}
uint64
RAMDevice::Write32(uint32 addr, uint32 data)
{
gMPU->AddCycle(write_wait);
*(uint32 *)&mainram[addr] = data;
putlog(3, "$%08x.L <- $%08x", addr, data);
return 0;
}
uint64
RAMDevice::Peek8(uint32 addr)
{
return mainram[HLB(addr)];
}
// アクセスウェイトを設定 (X68030 でシステムポートから呼ばれる)
void
RAMDevice::SetWait(uint32 wait)
{
read_wait = wait;
write_wait = wait;
}
// filepath で指定されるホストの実行ファイルをゲストにロードする。
// ファイルのフォーマットは自動で認識する。
// 戻り値はエントリポイントアドレス。エラーなら 0 を返す。
uint32
RAMDevice::LoadFile(const char *filepath)
{
uint8 *file;
struct stat st;
size_t filesize;
uint32 entry;
autofd fd;
fd = open(filepath, O_RDONLY);
if (fd == -1) {
warn("%s \"%s\" open failed", __func__, filepath);
return 0;
}
if (fstat(fd, &st) == -1) {
warn("%s \"%s\" fstat failed", __func__, filepath);
return 0;
}
if (!S_ISREG(st.st_mode)) {
warnx("%s \"%s\" not a regular file", __func__, filepath);
return 0;
}
// 今の所マジック判定は最初の4バイトで出来るので
if (st.st_size < 4) {
warnx("%s \"%s\" file too short", __func__, filepath);
return 0;
}
filesize = st.st_size;
file = (uint8 *)mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, fd, 0);
if (file == MAP_FAILED) {
warn("%s \"%s\" mmap failed", __func__, filepath);
return 0;
}
// 先頭3バイトの gzip マジックを調べる
if (file[0] == 0x1f && file[1] == 0x8b && file[2] == 0x08) {
// gzip
entry = LoadGzFile(filepath, file, filesize);
} else {
// 通常ファイル
entry = LoadFile(filepath, file, filesize);
}
munmap(file, filesize);
return entry;
}
// gzip を展開してゲストにロードする。
uint32
RAMDevice::LoadGzFile(const char *filepath, const uint8 *infile,
size_t infilesize)
{
z_stream z {};
uint32 decompsize;
int rv;
// gzip なら(?) ファイルの末尾4バイトが (LE で?) 展開後サイズ(?)。
decompsize = le32toh(*(const uint32 *)&infile[infilesize - 4]);
std::unique_ptr<uint8[]> dstbuf(new uint8[decompsize]);
// gzip 展開
// (uncompress() の方が楽そうに見えるが gzip 形式に対応してないらしい)
z.zalloc = Z_NULL;
z.zfree = Z_NULL;
z.opaque = Z_NULL;
rv = inflateInit2(&z, 47);
if (rv != Z_OK) {
warnx("%s \"%s\" inflateInit2 failed %d", __func__, filepath, rv);
return 0;
}
z.next_in = const_cast<Bytef *>(infile);
z.avail_in = infilesize - 4;
z.next_out = dstbuf.get();
z.avail_out = decompsize;
rv = inflate(&z, Z_NO_FLUSH);
if (rv != Z_OK) {
warnx("%s \"%s\" inflate failed %d", __func__, filepath, rv);
return 0;
}
inflateEnd(&z);
// 展開できたのでロード
return LoadFile(filepath, dstbuf.get(), decompsize);
}
// file, filesize で示されるバッファを実行ファイルとみなしてゲストにロードする。
// ファイルのフォーマットは自動で認識する。
// name はここではエラーメッセージとかの表示用でしかない。
// 戻り値はエントリポイントアドレス。エラーなら 0 を返す。
uint32
RAMDevice::LoadFile(const char *name, const uint8 *file, size_t filesize)
{
uint32 entry = 0;
// フォーマットだけ判定して分岐
const Elf32_Ehdr *ehdr = (const Elf32_Ehdr *)file;
const aout_header *aout = (const aout_header *)file;
if (memcmp(ehdr->e_ident, "\177ELF", 4) == 0) {
entry = Load_elf32(name, file, filesize);
} else if (AOUT_MAGIC(be32toh(aout->magic)) == AOUT_OMAGIC) {
entry = Load_aout(name, file, filesize);
} else {
warnx("%s \"%s\" unknown executable file format", __func__, name);
}
return entry;
}
// ホストの a.out をゲストの RAM に読み込む。
// 戻り値はエントリポイントアドレス。エラーなら 0 を返す。
uint32
RAMDevice::Load_aout(const char *name, const uint8 *file, size_t filesize)
{
aout_header aout;
int copylen;
if (filesize < sizeof(aout)) {
warnx("%s \"%s\" file too short", __func__, name);
return 0;
}
// ヘッダを読み込む
// BigEndian しかターゲットにしてないので最初から全部変換しとく
aout.magic = be32toh(((const aout_header *)file)->magic);
aout.textsize = be32toh(((const aout_header *)file)->textsize);
aout.datasize = be32toh(((const aout_header *)file)->datasize);
aout.bsssize = be32toh(((const aout_header *)file)->bsssize);
aout.symsize = be32toh(((const aout_header *)file)->symsize);
aout.entry = be32toh(((const aout_header *)file)->entry);
aout.textrelsize = be32toh(((const aout_header *)file)->textrelsize);
aout.datarelsize = be32toh(((const aout_header *)file)->datarelsize);
if (loglevel >= 1) {
const char *mstr;
switch (AOUT_MAGIC(aout.magic)) {
case AOUT_OMAGIC: mstr = "OMAGIC"; break;
case AOUT_NMAGIC: mstr = "NMAGIC"; break;
case AOUT_ZMAGIC: mstr = "ZMAGIC"; break;
default:
mstr = "unsupported magic";
break;
}
putmsgn("%s magic = %08x (MID=0x%03x, %s)", __func__,
aout.magic, AOUT_MID(aout.magic), mstr);
putmsgn("%s textsize = %08x", __func__, aout.textsize);
putmsgn("%s datasize = %08x", __func__, aout.datasize);
putmsgn("%s bsssize = %08x", __func__, aout.bsssize);
putmsgn("%s symsize = %08x", __func__, aout.symsize);
putmsgn("%s entry = %08x", __func__, aout.entry);
putmsgn("%s textrelsize = %08x", __func__, aout.textrelsize);
putmsgn("%s datarelsize = %08x", __func__, aout.datarelsize);
}
// MID はチェックしない。
// CPU アーキテクチャが一致するかくらいはチェックしたいところだが、
// MID は CPU ごとではなく OS(?) ごとに異なっているのと、MID の
// 一元管理された一次情報が存在しないので、正解リストを作るのが困難。
// とりあえず再配置は無視
if (aout.textrelsize != 0 || aout.datarelsize != 0) {
warnx("%s \"%s\" relocation not supported", __func__, name);
return 0;
}
// OpenBSD の boot は text+data が filesize より大きくて何かおかしいが
// OMAGIC の a.out は text+data が連続しているだけなので、とりあえず
// 何も考えずにファイルサイズ分ロードしておく。
copylen = aout.textsize + aout.datasize;
if (copylen >= filesize) {
copylen = filesize;
warnx("warning: %s \"%s\" corrupted? "
"(text=%u + data=%u, filesize=%d); loading %d bytes",
__func__,
name, aout.textsize, aout.datasize, (int)filesize, copylen);
/* FALLTHROUGH */
}
if (aout.entry + copylen > GetSize()) {
warnx("%s \"%s\" out of memory in VM", __func__, name);
return 0;
}
// 再配置不要なので、text + data を entry に置いて entry に飛ぶだけ
IODeviceStream ds(this, aout.entry);
const uint8 *src = file + sizeof(aout);
for (int i = 0; i < copylen; i++) {
ds.Write8(*src++);
}
return aout.entry;
}
// ELF フォーマットのエンディアンからホストエンディアンに変換
#define elf16toh(x) ((ehdr->e_ident[EI_DATA] == ELFDATA2MSB) ? \
be16toh(x) : le16toh(x))
#define elf32toh(x) ((ehdr->e_ident[EI_DATA] == ELFDATA2MSB) ? \
be32toh(x) : le32toh(x))
// XXX このへん、そのうちきれいにする
#define GetElfStr1(array, v) GetElfStr(array, countof(array), v)
static const char *
GetElfStr(const std::pair<uint, const char *> *map, uint mapcount, uint v)
{
for (int i = 0; i < mapcount; i++) {
if (map[i].first == v) {
return map[i].second;
}
}
return "?";
}
static std::pair<uint, const char *> class_str[] = {
{ ELFCLASSNONE, "NONE" },
{ ELFCLASS32, "32bit" },
{ ELFCLASS64, "64bit" },
};
#define GetElfClassStr(x) GetElfStr1(class_str, (x))
static std::pair<uint, const char *> data_str[] = {
{ ELFDATANONE, "NONE" },
{ ELFDATA2LSB, "LE" },
{ ELFDATA2MSB, "BE" },
};
#define GetElfDataStr(x) GetElfStr1(data_str, (x))
static std::pair<uint, const char *> etype_str[] = {
{ ET_NONE, "ET_NONE" },
{ ET_REL, "ET_REL" },
{ ET_EXEC, "ET_EXEC" },
{ ET_DYN, "ET_DYN" },
{ ET_CORE, "ET_CORE" },
};
#define GetElfETypeStr(x) GetElfStr1(etype_str, (x))
static std::pair<uint, const char *> machine_str[] = {
// 多いので関係分のみ
{ EM_NONE, "NONE" },
{ EM_68K, "Motorola 68000" },
{ EM_88K, "Motorola 88000" },
};
#define GetElfMachineStr(x) GetElfStr1(machine_str, (x))
static std::pair<uint, const char *> ptype_str[] = {
{ PT_NULL, "PT_NULL" },
{ PT_LOAD, "PT_LOAD" },
{ PT_DYNAMIC, "PT_DYNAMIC" },
{ PT_INTERP, "PT_INTERP" },
{ PT_NOTE, "PT_NOTE" },
{ PT_SHLIB, "PT_SHLIB" },
{ PT_PHDR, "PT_PHDR" },
{ PT_GNU_RELRO, "PT_GNU_RELRO" }, // GNU specific
{ PT_OPENBSD_RANDOMIZE, "PT_OPENBSD_RANDOMIZE" },
{ PT_OPENBSD_WXNEEDED, "PT_OPENBSD_WXNEEDED" },
{ PT_OPENBSD_BOOTDATA, "PT_OPENBSD_BOOTDATA" },
};
#define GetElfPTypeStr(x) GetElfStr1(ptype_str, (x))
static std::pair<uint, const char *> shtype_str[] = {
{ SHT_NULL, "SHT_NULL" },
{ SHT_PROGBITS, "SHT_PROGBITS" },
{ SHT_RELA, "SHT_RELA" },
{ SHT_NOBITS, "SHT_NOBITS" },
{ SHT_REL, "SHT_REL" },
};
#define GetElfShTypeStr(x) GetElfStr1(shtype_str, (x))
// ホストの ELF をゲストの RAM に読み込む。
// 戻り値はエントリポイントアドレス。エラーなら 0 を返す。
uint32
RAMDevice::Load_elf32(const char *name, const uint8 *file, size_t filesize)
{
uint16 target_elf_mid;
if (gMainApp.Has(VMCap::M68K)) {
target_elf_mid = EM_68K;
} else if (gMainApp.Has(VMCap::M88K)) {
target_elf_mid = EM_88K;
} else {
PANIC("Unknown CPU?");
}
// ヘッダをチェック
const Elf32_Ehdr *ehdr = (const Elf32_Ehdr *)file;
if (filesize < sizeof(*ehdr)) {
warnx("%s \"%s\" file too short", __func__, name);
return 0;
}
uint16 e_type = elf16toh(ehdr->e_type);
uint16 e_machine = elf16toh(ehdr->e_machine);
uint32 e_entry = elf32toh(ehdr->e_entry);
if (loglevel >= 1) {
uint n;
n = ehdr->e_ident[EI_CLASS];
putlogn("EI_CLASS = $%02x (%s)", n, GetElfClassStr(n));
n = ehdr->e_ident[EI_DATA];
putlogn("EI_DATA = $%02x (%s)", n, GetElfDataStr(n));
putlogn("e_type = $%02x (%s)", e_type, GetElfETypeStr(e_type));
putlogn("e_machine = $%02x (%s)", e_machine,
GetElfMachineStr(e_machine));
putlogn("e_entry = $%08x", e_entry);
}
if (ehdr->e_ident[EI_CLASS] != ELFCLASS32) {
warnx("%s \"%s\" not ELF32 format", __func__, name);
return 0;
}
if (e_machine != target_elf_mid) {
warnx("%s \"%s\" machine type mismatch "
"($%02x expected but $%02x)", __func__,
name, target_elf_mid, e_machine);
return 0;
}
switch (e_type) {
case ET_EXEC:
if (Load_elf32_exec(name, file, filesize)) {
return e_entry;
}
break;
case ET_REL:
return Load_elf32_rel(name, file, filesize);
default:
warnx("%s \"%s\" unsupported file type", __func__, name);
break;
}
return 0;
}
// ホストの ELF 実行形式ファイルをゲストの RAM に読み込む。
// 読み込めたら true、エラーなら false を返す。
// エントリポイントは呼び出し元が知っているのでこっちでは関与しない。
bool
RAMDevice::Load_elf32_exec(const char *name, const uint8 *file, size_t filesize)
{
const Elf32_Ehdr *ehdr = (const Elf32_Ehdr *)file;
// プログラムヘッダに読み込むべき領域が示されている
// phoff がプログラムヘッダのファイル先頭からのオフセット
// phentsize がプログラムヘッダ1つの大きさ
// phnum がプログラムヘッダの個数
uint32 e_phoff = elf32toh(ehdr->e_phoff);
uint16 e_phentsize = elf16toh(ehdr->e_phentsize);
uint16 e_phnum = elf16toh(ehdr->e_phnum);
if (loglevel >= 1) {
putlogn("e_phoff = $%08x", e_phoff);
putlogn("e_phentsize = $%04x", e_phentsize);
putlogn("e_phnum = %d", e_phnum);
}
if (e_phentsize != sizeof(Elf32_Phdr)) {
warnx("%s \"%s\" unknown program header size 0x%x", __func__,
name, e_phentsize);
return false;
}
IODeviceStream ds(this);
for (int j = 0; j < e_phnum; j++) {
const Elf32_Phdr *phdr = &((const Elf32_Phdr*)(file + e_phoff))[j];
// p_type が PT_LOAD なところをロードする。
// その際 p_memsz > p_filesz なら差が BSS 領域。
uint32 p_type = elf32toh(phdr->p_type); // タイプ
uint32 p_offset = elf32toh(phdr->p_offset); // ファイル上のオフセット
uint32 p_vaddr = elf32toh(phdr->p_vaddr); // 仮想アドレス
uint32 p_filesz = elf32toh(phdr->p_filesz); // 読み込み元ファイルサイズ
uint32 p_memsz = elf32toh(phdr->p_memsz); // 書き込み先サイズ
if (loglevel >= 1) {
putlogn("[%d] p_type = $%08x (%s)", j,
p_type, GetElfPTypeStr(p_type));
putlogn(" p_offset = $%08x", p_offset);
putlogn(" p_vaddr = $%08x", p_vaddr);
putlogn(" p_filesz = $%08x", p_filesz);
putlogn(" p_memsz = $%08x", p_memsz);
}
if (p_type == PT_LOAD) {
if (p_vaddr + p_memsz > GetSize()) {
warnx("%s \"%s\" out of memory in VM", __func__, name);
return false;
}
ds.SetAddr(p_vaddr);
const uint8 *src = file + p_offset;
int i = 0;
for (; i < p_filesz; i++) {
ds.Write8(*src++);
}
for (; i < p_memsz; i++) {
ds.Write8(0);
}
} else if (p_type == PT_NOTE) {
// ベンダー文字列みたいなのらしいので無視してよい
continue;
} else if (p_type == PT_OPENBSD_RANDOMIZE) {
// XXX どうする?
continue;
} else {
warnx("%s \"%s\" p_type 0x%x(%s) not supported", __func__,
name, p_type, GetElfPTypeStr(p_type));
return false;
}
}
return true;
}
// ホストの ELF オブジェクトの text セクションをゲストの RAM に読み込む。
// .o(オブジェクトファイル) 用。
// 読み込めたら true、エラーなら false を返す。
// 戻り値はエントリポイントアドレス。エラーなら 0 を返す。
uint32
RAMDevice::Load_elf32_rel(const char *name, const uint8 *file, size_t filesize)
{
const Elf32_Ehdr *ehdr = (const Elf32_Ehdr *)file;
uint32 entry = 0x20000;
uint32 e_shoff = elf32toh(ehdr->e_shoff);
uint16 e_shentsize = elf16toh(ehdr->e_shentsize);
uint16 e_shnum = elf16toh(ehdr->e_shnum);
if (loglevel >= 1) {
putlogn("e_shoff = $%08x", e_shoff);
putlogn("e_shentsize = $%04x", e_shentsize);
putlogn("e_shnum = %d", e_shnum);
}
if (e_shentsize != sizeof(Elf32_Shdr)) {
warnx("%s \"%s\" unknown section header size 0x%x", __func__,
name, e_shentsize);
return 0;
}
IODeviceStream ds(this);
ds.SetAddr(entry);
for (int j = 0; j < e_shnum; j++) {
const Elf32_Shdr *shdr = &((const Elf32_Shdr*)(file + e_shoff))[j];
// sh_type
uint32 sh_type = elf32toh(shdr->sh_type); // タイプ
uint32 sh_addr = elf32toh(shdr->sh_addr); // アドレス
uint32 sh_offset = elf32toh(shdr->sh_offset); // ファイル先頭から
uint32 sh_size = elf32toh(shdr->sh_size); // サイズ
if (loglevel >= 1) {
putlogn("[%d] sh_type = $%08x (%s)", j,
sh_type, GetElfShTypeStr(sh_type));
putlogn(" sh_addr = $%08x", sh_addr);
putlogn(" sh_offset = $%08x", sh_offset);
putlogn(" sh_size = $%08x", sh_size);
}
if (sh_type == SHT_PROGBITS) {
const uint8 *src = file + sh_offset;
int i = 0;
for (; i < sh_size; i++) {
ds.Write8(*src++);
}
} else if (sh_type == SHT_RELA || sh_type == SHT_REL) {
// 再配置情報があるのは .R 形式ではない
warnx("%s \"%s\" has relocation section", __func__, name);
return 0;
} else {
// ?
}
}
return entry;
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.