--- nono/vm/ram.cpp 2026/04/29 17:04:28 1.1 +++ nono/vm/ram.cpp 2026/04/29 17:04:42 1.1.1.4 @@ -1,21 +1,31 @@ // // nono -// Copyright (C) 2018 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt // #include "ram.h" -#include "vm.h" +#include "aout.h" +#include "elf.h" +#include "iodevstream.h" +#include "mainapp.h" +#include +#include +#include +#include // // メインメモリ // + +std::unique_ptr gRAM; + // RAM はロングワード単位でホストバイトオーダ配置なので、 // バイトアクセス、ワードアクセス時は HLB(), HLW() マクロを使用のこと。 -// +std::unique_ptr ram; -uint8 *ram; +// RAM 容量(バイト単位)であり ram[] の確保したバイト数 int ram_size; -RAMDevice *gRAM; RAMDevice::RAMDevice() { @@ -24,35 +34,71 @@ RAMDevice::RAMDevice() devaddr = 0; ram_size = 0; - ram = NULL; + + // アクセスウェイト + 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() { - if (ram) { - delete ram; - ram = NULL; - } ram_size = 0; } bool RAMDevice::Init() { - // とりあえず。実際は設定ファイルから - if (gVM->GetType() == VM::TYPE_LUNA) { - ram_size = 16 * 1024 * 1024; - } else { - ram_size = 12 * 1024 * 1024; + int ram_size_MB; + + const ConfigItem& item = gConfig->Find("ram-size"); + ram_size_MB = item.AsInt(); + // XXX 上限は適当。 + // LUNA は空間は 1024MB 分あるが末尾に Null と BusErr 領域が必要なはずで + // メモリの上限は 1023MB くらいのはず。 + if (ram_size_MB < 1 || ram_size_MB > 1023) { + item.Err(); + return false; } - ram = new uint8[ram_size]; + + ram_size = ram_size_MB * 1024 * 1024; + ram.reset(new uint8[ram_size]); return true; } +void +RAMDevice::ResetHard() +{ + // 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 = ram[HLB(addr)]; return data; @@ -61,6 +107,7 @@ RAMDevice::Read8(uint32 addr) uint64 RAMDevice::Read16(uint32 addr) { + gMPU->AddCycle(read_wait); uint32 data; data = *(uint16 *)&ram[HLW(addr)]; return data; @@ -69,6 +116,7 @@ RAMDevice::Read16(uint32 addr) uint64 RAMDevice::Read32(uint32 addr) { + gMPU->AddCycle(read_wait); uint32 data; data = *(uint32 *)&ram[addr]; return data; @@ -77,6 +125,7 @@ RAMDevice::Read32(uint32 addr) uint64 RAMDevice::Write8(uint32 addr, uint32 data) { + gMPU->AddCycle(write_wait); ram[HLB(addr)] = data; return 0; } @@ -84,6 +133,7 @@ RAMDevice::Write8(uint32 addr, uint32 da uint64 RAMDevice::Write16(uint32 addr, uint32 data) { + gMPU->AddCycle(write_wait); *(uint16 *)&ram[HLW(addr)] = data; return 0; } @@ -91,6 +141,7 @@ RAMDevice::Write16(uint32 addr, uint32 d uint64 RAMDevice::Write32(uint32 addr, uint32 data) { + gMPU->AddCycle(write_wait); *(uint32 *)&ram[addr] = data; return 0; } @@ -100,3 +151,322 @@ RAMDevice::Peek8(uint32 addr) { return ram[HLB(addr)]; } + +// アクセスウェイトを設定 (X68030 でシステムポートから呼ばれる) +void +RAMDevice::SetWait(uint32 wait) +{ + read_wait = wait; + write_wait = wait; +} + +// filepath で指定されるホストの実行ファイルをゲストにロードする。 +// ファイルのフォーマットは自動で認識する。 +// target_mid は machine id で、実体は a.out の値を流用してはいるが +// ELF であってもこちらで読み替える。 +// 戻り値はエントリポイントアドレス。エラーなら 0 を返す。 +uint32 +RAMDevice::LoadFile(const char *filepath, uint32 target_mid) +{ + uint8 *file; + struct stat st; + size_t filesize; + uint32 entry; + int fd; + + fd = open(filepath, O_RDONLY); + if (fd == -1) { + warn("LoadFile \"%s\" open failed", filepath); + return 0; + } + + if (fstat(fd, &st) == -1) { + warn("LoadFile \"%s\" fstat failed", filepath); + close(fd); + return 0; + } + + if (!S_ISREG(st.st_mode)) { + warnx("LoadFile \"%s\" not a regular file", filepath); + close(fd); + return 0; + } + + // 今の所マジック判定は最初の4バイトで出来るので + if (st.st_size < 4) { + warnx("LoadFile \"%s\" file too short", filepath); + close(fd); + return 0; + } + filesize = st.st_size; + + file = (uint8 *)mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, fd, 0); + if (file == MAP_FAILED) { + warn("LoadFile \"%s\" mmap failed", filepath); + close(fd); + return 0; + } + + // 先頭3バイトの gzip マジックを調べる + if (file[0] == 0x1f && file[1] == 0x8b && file[2] == 0x08) { + // gzip + entry = LoadGzFile(filepath, file, filesize, target_mid); + } else { + // 通常ファイル + entry = LoadFile(filepath, file, filesize, target_mid); + } + + munmap(file, filesize); + close(fd); + return entry; +} + +// gzip を展開してゲストにロードする。 +uint32 +RAMDevice::LoadGzFile(const char *filepath, + const uint8 *infile, size_t infilesize, uint32 target_mid) +{ + z_stream z {}; + uint32 decompsize; + int rv; + + // gzip なら(?) ファイルの末尾4バイトが (LE で?) 展開後サイズ(?)。 + decompsize = le32toh(*(const uint32 *)&infile[infilesize - 4]); + std::unique_ptr 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("LoadGzFile \"%s\" inflateInit2 failed %d", filepath, rv); + return 0; + } + + z.next_in = const_cast(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("LoadGzFile \"%s\" inflate failed %d", filepath, rv); + return 0; + } + + inflateEnd(&z); + + // 展開できたのでロード + return LoadFile(filepath, dstbuf.get(), decompsize, target_mid); +} + +// file, filesize で示されるバッファを実行ファイルとみなしてゲストにロードする。 +// ファイルのフォーマットは自動で認識する。 +// target_mid は machine id で、実体は a.out の値を流用してはいるが +// ELF であってもこちらで読み替える。 +// name はここではエラーメッセージとかの表示用でしかない。 +// 戻り値はエントリポイントアドレス。エラーなら 0 を返す。 +uint32 +RAMDevice::LoadFile(const char *name, const uint8 *file, size_t filesize, + uint32 target_mid) +{ + 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, target_mid); + } else if ((be32toh(aout->magic) & 0xffff) == AOUT_OMAGIC) { + entry = Load_aout(name, file, filesize, target_mid); + } else { + warnx("LoadFile \"%s\" unknown executable file format", name); + } + + return entry; +} + +// ホストの a.out をゲストの RAM に読み込む。 +// 戻り値はエントリポイントアドレス。エラーなら 0 を返す。 +uint32 +RAMDevice::Load_aout(const char *name, const uint8 *file, size_t filesize, + uint32 target_mid) +{ + aout_header aout; + int copylen; + + if (filesize < sizeof(aout)) { + warnx("Load_aout \"%s\" file too short", 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); + + // マジックをチェック + uint32 mid = AOUT_MID(aout.magic); + if (mid != target_mid) { + warnx("Load_aout \"%s\" machine id mismatch (%03x expected but %03x)", + name, target_mid, mid); + return 0; + } + + putmsg(1, "%s textsize = %08x", __func__, aout.textsize); + putmsg(1, "%s datasize = %08x", __func__, aout.datasize); + putmsg(1, "%s bsssize = %08x", __func__, aout.bsssize); + putmsg(1, "%s symsize = %08x", __func__, aout.symsize); + putmsg(1, "%s entry = %08x", __func__, aout.entry); + putmsg(1, "%s textrelsize = %08x", __func__, aout.textrelsize); + putmsg(1, "%s datarelsize = %08x", __func__, aout.datarelsize); + + // とりあえず再配置は無視 + if (aout.textrelsize != 0 || aout.datarelsize != 0) { + warnx("Load_aout \"%s\" relocation not supported", 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: Load_aout \"%s\" coruppted? " + "(text=%u + data=%u, filesize=%d); loading %d bytes", + name, aout.textsize, aout.datasize, (int)filesize, copylen); + /* FALLTHROUGH */ + } + + // 再配置不要なので、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)) + +// ホストの a.out をゲストの RAM に読み込む。 +// target_aout_mid は a.out での machine id (機種情報) で、こっちで ELF の +// machine type に読み替える。 +// 戻り値はエントリポイントアドレス。エラーなら 0 を返す。 +uint32 +RAMDevice::Load_elf32(const char *name, const uint8 *file, size_t filesize, + uint32 target_aout_mid) +{ + uint16 target_elf_mid; + + // a.out machine id から ELF machine type への読み替え + switch (target_aout_mid) { + case AOUT_MID_M68K: + target_elf_mid = EM_68K; + break; + case AOUT_MID_M88K: + target_elf_mid = EM_88K; + break; + default: + warnx("Load_elf32 unknown target_aout_mid $%x", target_aout_mid); + return 0; + } + + // ヘッダをチェック + const Elf32_Ehdr *ehdr = (const Elf32_Ehdr *)file; + if (filesize < sizeof(*ehdr)) { + warnx("Load_elf32 \"%s\" file too short", name); + return 0; + } + uint16 e_type = elf16toh(ehdr->e_type); + uint16 e_machine = elf16toh(ehdr->e_machine); + uint32 e_entry = elf32toh(ehdr->e_entry); + putlog(1, "EI_CLASS = $%02x", ehdr->e_ident[EI_CLASS]); + putlog(1, "EI_DATA = $%02x", ehdr->e_ident[EI_DATA]); + putlog(1, "e_type = $%02x", e_type); + putlog(1, "e_machine = $%02x", e_machine); + putlog(1, "e_entry = $%08x", e_entry); + + if (ehdr->e_ident[EI_CLASS] != ELFCLASS32) { + warnx("Load_elf32 \"%s\" not ELF32 format", name); + return 0; + } + if (e_type != ET_EXEC) { + warnx("Load_elf32 \"%s\" not executable file", name); + return 0; + } + if (e_machine != target_elf_mid) { + warnx("Load_elf32 \"%s\" machine type mismatch " + "($%02x expected but $%02x)", + name, target_elf_mid, e_machine); + return 0; + } + + // プログラムヘッダに読み込むべき領域が示されている + // 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); + putlog(1, "e_phoff = $%08x", e_phoff); + putlog(1, "e_phentsize = $%04x", e_phentsize); + putlog(1, "e_phnum = %d", e_phnum); + if (e_phentsize != sizeof(Elf32_Phdr)) { + warnx("Load_elf32 \"%s\" unknown program header size 0x%x", + name, e_phentsize); + return 0; + } + + 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); // 書き込み先サイズ + putlog(1, "[%d] p_type = $%08x", j, p_type); + putlog(1, "[%d] p_offset = $%08x", j, p_offset); + putlog(1, "[%d] p_vaddr = $%08x", j, p_vaddr); + putlog(1, "[%d] p_filesz = $%08x", j, p_filesz); + putlog(1, "[%d] p_memsz = $%08x", j, p_memsz); + + if (p_type == PT_LOAD) { + 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 == 0x65a3dbe6) { // PT_OPENBSD_RANDOMIZE + // XXX どうする? + continue; + + } else { + warnx("Load_elf32 \"%s\" p_type %d not supported", name, p_type); + return 0; + } + } + + return e_entry; +}