|
|
nono 1.3.0
//
// nono
// Copyright (C) 2022 nono project
// Licensed under nono-license.txt
//
//
// X68030 の ROM30 (SCSI ROM) エミュレーション
//
// 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_x68k.h"
#include "mainbus.h"
#include "mainram.h"
#include "memorystream.h"
#include "mpu680x0.h"
#include "scsidev.h"
#include "scsidomain.h"
#include "spc.h"
#include "sram.h"
// コンストラクタ
ROM30EmuDevice::ROM30EmuDevice()
: inherited(OBJ_IPLROM2)
{
}
// デストラクタ
ROM30EmuDevice::~ROM30EmuDevice()
{
}
// 初期化
bool
ROM30EmuDevice::Init()
{
if (AllocROM(0x020000, 0xff) == false) {
return false;
}
mainbus = GetMainbusDevice();
spc = GetSPCDevice();
MemoryStreamBE roms(imagebuf.get());
// fc0000.L 〜 fc001c.L: SCSI ブートアドレス
for (int i = 0; i < 8; i++) {
roms.Write4(0x00fc0100);
}
// fc0020.L: SCSI 初期化アドレス
roms.Write4(0x00fc0200);
// fc0024.B: マジック
roms.Write4(('S' << 24) | ('C' << 16) | ('S' << 8) | 'I');
roms.Write2(('I' << 8) | 'N');
roms.Write1(0);
roms.Write1(0);
//
// SCSI ブート
//
roms.SetOffset(0x100); //_SCSIBOOT:
roms.Write2(0x6100); // bsr $fc0200 ; SCSIIOCS 初期化
roms.Write2(0x0200 - roms.GetOffset());
roms.Write2(0x2039); // move.l (ROMIO_BOOT),d0
roms.Write4(ROMIO_BOOT);
roms.Write2(0x6602); // bne _go ; 起動先があればGO
//_bootfail:
roms.Write2(0x4e75); // rts
//_go:
roms.Write2(0x2040); // move.l d0,a0 ; ジャンプ先を a0 にセット
roms.Write2(0x4ed0); // jmp (a0) ; で、ジャンプ
//
// SCSIIOCS 初期化
//
roms.SetOffset(0x200); //_SCSIINIT:
roms.Write4(0x323c01f5); // move.w #$01f5,d1
roms.Write2(0x43f9); // lea.l $00fc0300,a1
roms.Write4(0x00fc0300);
roms.Write4(0x70804e4f); // IOCS _B_INTVCS
// ; 戻り値(旧アドレス)は破棄
roms.Write2(0x4e75); // rts
//
// SCSIIOCS エントリ
//
roms.SetOffset(0x300); //_SCSIIOCS:
roms.Write2(0x2039); // move.l (ROMIO_SCSI),d0
roms.Write4(ROMIO_SCSI);
roms.Write2(0x4e75); // rts
return true;
}
uint64
ROM30EmuDevice::ReadROMIO(busaddr addr)
{
// IPLROM2 領域からのロングワードアクセスでだけ謎の I/O 空間が見える。
if ((mpu->GetPPC() & 0xfffe0000) == baseaddr && addr.GetSize() == 4) {
switch (addr.Addr()) {
case ROMIO_SCSI:
return SCSIIOCS();
case ROMIO_BOOT:
return SCSIBoot();
default:
break;
}
}
return (uint64)-1;
}
// SRAM で指定された SCSI ID のデバイスからブートブロックを読み込む。
// 成功すればジャンプ先アドレスを、失敗すれば 0 を返す。
uint32
ROM30EmuDevice::SCSIBoot() const
{
auto mainram = GetMainRAMDevice();
auto sram = GetSRAMDevice();
auto mpu680x0 = GetMPU680x0Device(mpu);
m68kreg& reg = mpu680x0->reg;
// SRAM の ROM 起動アドレスから SCSI ID を取得。
uint32 romaddr = sram->GetROMAddr();
if ((romaddr & 0xffff00) != 0xfc0000) {
return 0;
}
uint id = (romaddr & 0x1f) >> 2;
// ターゲットディスク
SCSITarget *target = spc->GetDomain()->GetTarget(id);
if (target == NULL) {
putlog(1, "%s: SCSI ID %u not found", __func__, id);
return 0;
}
SCSIDisk *disk = dynamic_cast<SCSIDisk*>(target);
// Human68k は 1024 byte/sector を「セクタ」と呼んでいるが、色々紛らわしい
// ことになるので、ここではメディアのブロックサイズのほうを「セクタ」、
// Human68k の 1024 byte/sector のほうを「ブロック」と勝手に呼び分ける。
//
// セクタサイズが 512 byte/sector なら1ブロックを 1024 byte、
// セクタサイズが 2048 byte/sector なら1ブロックを 2048 byte とするようだ。
// セクタサイズを取得 (ただしこれが SCSI 側用語でブロックサイズ orz)
uint32 sectsize = disk->GetBlocksize();
uint32 blocksize;
if (sectsize == 512) {
blocksize = 1024;
} else if (sectsize == 2048) {
blocksize = sectsize;
} else {
putlog(1, "%s: SCSI ID %u: sectsize %u not supported", __func__,
id, sectsize);
return 0;
}
putlog(2, "%s: SCSI ID %u: sectsize=%u, blocksize=%u", __func__, id,
sectsize, blocksize);
// +0 ブロック目先頭8バイトのマジックを確認
std::vector<uint8> block(blocksize);
if (disk->Peek(&block[0], 0, 8) == false) {
putlog(1, "%s: SCSI ID %u: Reading magic failed", __func__, id);
return 0;
}
if (memcmp(&block[0], "X68SCSI1", 8) != 0) {
putlog(1, "%s: SCSI ID %u: invalid magic", __func__, id);
return 0;
}
// +1 ブロック目から1ブロックを読んで...
if (disk->Peek(&block[0], blocksize, blocksize) == false) {
putlog(1, "%s: SCSI ID %u: Reading boot block failed", __func__, id);
return 0;
}
// 先頭バイトが 0x60 (BRA.[BW] 命令の1バイト目) であること
if (block[0] != 0x60) {
putlog(1, "%s: SCSI ID %u: no branch instruction", __func__, id);
return 0;
}
// メモリの 0x2000 番地に置く
mainram->WriteMem(0x2000, &block[0], blocksize);
// D4 にはこの時の SCSI ID が置いてある(のが見えている)
reg.D[4] = id;
// XXX D2 は最後に _S_READ を発行した LBA を指しているようだ。
// なので +1 ブロックの LBA になる。
reg.D[2] = blocksize / sectsize;
// 成功したのでジャンプ先アドレスを返す
putlog(1, "%s succeeded", __func__);
return 0x2000;
}
// SCSIIOCS のなんちゃってエミュレーション
uint32
ROM30EmuDevice::SCSIIOCS()
{
auto mpu680x0 = GetMPU680x0Device(mpu);
m68kreg& reg = mpu680x0->reg;
uint32 scsicall = reg.D[1];
switch (scsicall) {
case 0x20: // _S_INQUIRY
return SCSI_S_INQUIRY(scsicall);
case 0x21: // _S_READ
case 0x26: // _S_READEXT
// XXX 今はどちらも Read(10) で処理している
return SCSI_S_READEXT(scsicall);
case 0x24: // _S_TESTUNIT
return SCSI_S_TESTUNIT(scsicall);
case 0x25: // _S_READCAP
return SCSI_S_READCAP(scsicall);
default:
putlog(0, "SCSIIOCS $%02x (NOT IMPLEMENTED)", scsicall);
break;
}
return 0xffffffff;
}
#define PRE \
auto mpu680x0 = GetMPU680x0Device(mpu); \
m68kreg& reg = mpu680x0->reg; \
uint32 d4 = reg.D[4]; \
uint32 id = d4 & 0x0000ffff; \
uint32 lun = (d4 >> 16) & 0x0000ffff; \
std::string scsiname = GetSCSICallName(scsicall); \
/* ターゲットディスク */ \
SCSITarget *target = spc->GetDomain()->GetTarget(id); \
if (target == NULL) { \
putlog(1, "%s ID%u not found", scsiname.c_str(), id); \
return -1; \
} \
SCSIDisk *disk __unused = dynamic_cast<SCSIDisk*>(target); \
/* ログ表示用 */ \
std::string loghdr; \
if (loglevel >= 1) { \
loghdr = string_format("%s ID%u", scsiname.c_str(), id); \
if (lun != 0) { \
loghdr + string_format(":LUN%u", lun); \
} \
} \
/*end*/
uint32
ROM30EmuDevice::SCSI_S_INQUIRY(uint32 scsicall)
{
PRE;
uint32 a1 = reg.A[1];
uint32 reqlen = reg.D[3];
putlog(1, "%s a1=$%06x", loghdr.c_str(), a1);
std::vector<uint8> cmdseq(6);
cmdseq[0] = SCSI::Command::Inquiry;
cmdseq[1] = lun << 5;
cmdseq[4] = reqlen;
SCSICmd *cmd = disk->SelectCommand(cmdseq);
auto phase = cmd->ExecCommand(cmdseq);
if (phase != SCSI::XferPhase::DataIn) {
putlog(0, "%s moved to %s (NOT IMPLEMENTED)",
loghdr.c_str(), SCSI::GetPhaseName(phase));
return -1;
}
const std::vector<uint8>& res = cmd->buf;
// アロケーションサイズ(reqlen)と結果(cmd->buf)の小さい方まで転送
int len = std::min((int)reqlen, (int)res.size());
for (int i = 0; i < len; i++) {
busdata bd = mainbus->HVWrite1(a1++, res[i]);
if (bd.IsBusErr()) {
return -1;
}
}
return 0;
}
uint32
ROM30EmuDevice::SCSI_S_READEXT(uint32 scsicall)
{
PRE;
uint32 a1 = reg.A[1];
uint32 lba = reg.D[2];
uint32 blkcount = reg.D[3];
uint32 blkshift = reg.D[5];
uint32 blkbytes = 0x100 << blkshift;
uint32 bytes = blkcount * blkbytes;
putlog(1, "%s a1=$%06x lba=$%08x bytes=%u d5=%u",
loghdr.c_str(), a1, lba, bytes, blkshift);
std::vector<uint8> cmdseq(10);
cmdseq[0] = SCSI::Command::Read10;
cmdseq[1] = lun << 5;
cmdseq[2] = lba >> 24;
cmdseq[3] = lba >> 16;
cmdseq[4] = lba >> 8;
cmdseq[5] = lba;
cmdseq[7] = blkcount >> 8;
cmdseq[8] = blkcount;
SCSICmd *cmd = disk->SelectCommand(cmdseq);
auto phase = cmd->ExecCommand(cmdseq);
if (phase != SCSI::XferPhase::DataIn) {
// XXX 本当はステータスコードかメッセージコードを返す?
putlog(0, "%s moved to %s (NOT IMPLEMENTED)",
loghdr.c_str(), SCSI::GetPhaseName(phase));
return -1;
}
const std::vector<uint8>& res = cmd->buf;
if (res.size() != bytes) {
putlog(1, "%s res.size=%u mismatch with bytes=%u",
loghdr.c_str(), (uint)res.size(), bytes);
// SCSIIOCS ブロックサイズとディスクのセクタサイズが不整合の
// 場合、転送は DMAC か SPC のいずれかが打ち切る。
// XXX そのときも何らかのエラーが発生しているはずだが?
if (res.size() < bytes) {
bytes = res.size();
}
}
for (int i = 0; i < bytes; i++) {
busdata bd = mainbus->HVWrite1(a1++, res[i]);
if (bd.IsBusErr()) {
return -1;
}
}
return 0;
}
uint32
ROM30EmuDevice::SCSI_S_TESTUNIT(uint32 scsicall)
{
PRE;
putlog(1, "%s", loghdr.c_str());
return 0;
}
uint32
ROM30EmuDevice::SCSI_S_READCAP(uint32 scsicall)
{
PRE;
uint32 a1 = reg.A[1];
putlog(1, "%s a1=$%06x", loghdr.c_str(), a1);
std::vector<uint8> cmdseq(10);
cmdseq[0] = SCSI::Command::ReadCapacity;
cmdseq[1] = lun << 5;
SCSICmd *cmd = disk->SelectCommand(cmdseq);
auto phase = cmd->ExecCommand(cmdseq);
if (phase != SCSI::XferPhase::DataIn) {
putlog(0, "%s moved to %s (NOT IMPLEMENTED)",
loghdr.c_str(), SCSI::GetPhaseName(phase));
return -1;
}
const std::vector<uint8>& res = cmd->buf;
assert(res.size() == 8);
for (auto data : res) {
busdata bd = mainbus->HVWrite1(a1++, data);
if (bd.IsBusErr()) {
return -1;
}
}
return 0;
}
// SCSIIOCS コール名を返す
/*static*/ std::string
ROM30EmuDevice::GetSCSICallName(uint number)
{
if (number < countof(x68k_scsicall)) {
const char *name = x68k_scsicall[number];
if (name) {
return std::string(name);
}
}
return string_format("undefined SCSIIOCS $%02x", number);
}
/*static*/ const char * const
ROM30EmuDevice::x68k_scsicall[0x40] = {
"_S_RESET", // $00
"_S_SELECT", // $01
"_S_SELECTA", // $02
"_S_CMDOUT", // $03
"_S_DATAIN", // $04
"_S_DATAOUT", // $05
"_S_STSIN", // $06
"_S_MSGIN", // $07
"_S_MSGOUT", // $08
"_S_PHASE", // $09
"_S_LEVEL", // $0a
"_S_DATAINI", // $0b
"_S_DATAOUTI", // $0c
"_S_MSGOUTEXT", // $0d
NULL, // $0e
NULL, // $0f
NULL, // $10
NULL, // $11
NULL, // $12
NULL, // $13
NULL, // $14
NULL, // $15
NULL, // $16
NULL, // $17
NULL, // $18
NULL, // $19
NULL, // $1a
NULL, // $1b
NULL, // $1c
NULL, // $1d
NULL, // $1e
NULL, // $1f
"_S_INQUIRY", // $20
"_S_READ", // $21
"_S_WRITE", // $22
"_S_FORMAT", // $23
"_S_TESTUNIT", // $24
"_S_READCAP", // $25
"_S_READEXT", // $26
"_S_WRITEEXT", // $27
"_S_VERIFYEXT", // $28
"_S_MODESENSE", // $29
"_S_MODESELECT",// $2a
"_S_REZEROUNIT",// $2b
"_S_REQUEST", // $2c
"_S_SEEK", // $2d
"_S_READI", // $2e
"_S_STARTSTOP", // $2f
"_S_EJECT6MO1", // $30
"_S_REASSIGN", // $31
"_S_PAMEDIUM", // $33
NULL, // $34
NULL, // $35
"_b_dskini", // $36
"_b_format", // $37
"_b_badfmt", // $38
"_b_assign", // $39
};
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.