--- nono/vm/fdc.cpp 2026/04/29 17:04:32 1.1.1.3 +++ nono/vm/fdc.cpp 2026/04/29 17:05:33 1.1.1.14 @@ -1,23 +1,105 @@ // // nono -// Copyright (C) 2017 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt // +// +// FDC (uPD72065B) + I/O コントローラ +// + +// MOTOR ON 信号 +// +// X68030 の回路図によれば、簡略すると以下のように接続されている。 +// +// PEDEC SED9420A FDD +// -------+ +---------------+ +----------- +// ~TRIG |-----| TRIG TMOUT |------| ~MOTOR ON +// +// SED9420A の TRIG は内蔵単安定マルチバイブレータのトリガ端子となっている。 +// TRIG がアサートされている間 TMOUT をアサートし、TRIG がネゲートされると +// 一定時間後に TMOUT をネゲートする。 +// その時間は SED9420A の CR 端子に接続された CR 回路によって決まってくるが +// データシートには時間の計算方法が記されていない。X68030 の回路図では +// +// SED9420A +// ---------+ +// CR |-----+---820kOhm---VCC1 +// | +// 47uF +// | +// GND +// +// となっているので時定数は 38.54 秒である。 +// この時定数と TMOUT の維持時間の正確な関係は分からないが、他の単安定マルチ +// では時定数時間とパルス幅が同じになるように調整されたもの(74HC123)もある +// ので、この時定数時間を TMOUT (MOTOR ON) 信号の維持時間と考えることにする。 +// +// 参考文献 +// 最新フロッピ・ディスク装置とその応用ノウハウ 高橋昇司 p.175 +// https://archive.org/details/recent_floppy_disk_system_and_know_how/ +// page/n175 +// +// +// DRIVE SEL.x 信号とアクセスドライブセレクトレジスタ +// +// アクセスドライブセレクトレジスタ($e94007) の MOTOR_ON ビットが 1 の +// ときは、アクセスドライブ番号フィールドにより指定された DRIVE SEL.x 信号が +// アサートされる。MOTOR_ON ビットが 0 のときは、すべての DRIVE SEL.x 信号は +// ネゲートされて、どのドライブも選択されていない状態になる。 +// 「ドライブセレクトイネーブル」とあるのは、こういう意味である。 +// +// +// DRIVE SEL.x 信号と各信号の関係 +// +// MOTOR ON 信号と DRIVE SEL.x 信号の考察を踏まえると、MOTOR ON 信号は +// ドライブが選択されているかどうかに関わらず、全ドライブが受け取らなければ +// ならない。そうでないとタイマーを入れている意味がない。 +// FDD は自分が選択されているかどうかに関係なく、MOTOR ON 信号がアサート +// されればモータを回そうとし、ネゲートされればモータを止める。 +// また、アクセスドライブセレクトレジスタの 2HD/2DD ビット、およびその +// 関係する信号である DISK TYPE SEL, MIN/STD, MFM/FM 周りも同様と考えられる。 +// これらの信号は SED9420A に接続されるため、ドライブごとに扱うように +// なっていないからである。 +// また、FDDINT についても、どこかのドライブで状態が変わったことを CPU に +// 通知する、という機能が要求されるべきであるから、OP.SEL にもよらず +// 割り込みがかかる、と推定される。 +// +// DRIVE SEL.x でドライブごとに有効になる信号 +// READY, INDEX, READ DATA, WRITE PROTECT, TRK00, WRITE GATE, WRITE DATA, +// STEP, DIRECTION +// ドライブに共通の信号 +// MOTOR ON, DISK TYPE SEL, FDDINT +// どちらでも良い信号 +// SIDE SELECT (たぶんドライブごと有効と思われる) +// OP.SEL.x でドライブごとに有効になる信号 +// EJECT, EJECT MASK, LED BLINK, DISK IN, ERR DISK + #include "fdc.h" -#include "iocon.h" +#include "bitops.h" +#include "config.h" +#include "dmac.h" +#include "fdd.h" +#include "monitor.h" +#include "pedec.h" #include "scheduler.h" +// time [nsec] 後に呼び出す phase_event のコールバックをセットする。 +// func の書式が面倒なのを省略して書きたいため。 +#define CallAfter(func_, time_) do { \ + phase_event.func = ToEventCallback(&FDCDevice::func_); \ + phase_event.time = time_; \ + scheduler->RestartEvent(phase_event); \ +} while (0) + + // コンストラクタ FDCDevice::FDCDevice() + : inherited(OBJ_FDC) { - logname = "fdc"; - devname = "FDC"; - devaddr = baseaddr; - devlen = 0x2000; - - event.dev = this; - event.func = (DeviceCallback_t)&FDCDevice::Callback; - event.SetName("FDC"); + monitor = gMonitorManager->Regist(ID_MONITOR_FDC, this); + monitor->func = ToMonitorCallback(&FDCDevice::MonitorUpdate); + // サイズは Create() 時に確定する } // デストラクタ @@ -25,134 +107,652 @@ FDCDevice::~FDCDevice() { } +bool +FDCDevice::Create() +{ + // ドライブ数。 + // fdd_list はランダムアクセスできる配列なので、歯抜け (#0 がなくて + // #1 があるみたいなの) もサポートするが、今の所設定方法がない。 + const ConfigItem& item = gConfig->Find("fd-drive"); + ndrive = item.AsInt(); + + // X68k では上限4台。(LUNA はひとまず放置) + if (ndrive > 4) { + item.Err(); + return false; + } + + // モニタサイズはここで決定。FDD は横に2台で、それ以上は縦に伸ばす。 + monitor->SetSize(80, 15 + (ndrive / 2) * 10); + + // vector は面倒なので一旦 unique_ptr[] に確保して + // 生ポインタの vector を作る。うーん。 + for (int i = 0; i < ndrive; i++) { + fdd_list[i].reset(new FDDDevice(i)); + } + for (int i = 0; i < ndrive; i++) { + if ((bool)fdd_list[i]) { + fdd_vector.push_back(fdd_list[i].get()); + } else { + fdd_vector.push_back(NULL); + } + } + + return true; +} + +// 初期化 +bool +FDCDevice::Init() +{ + if (inherited::Init() == false) { + return false; + } + + dmac = GetDMACDevice(); + pedec = GetPEDECDevice(); + + phase_event.SetName("FDC Phase"); + scheduler->RegistEvent(phase_event); + + poll_event.func = ToEventCallback(&FDCDevice::PollEventCallback); + poll_event.time = 1024_usec; + poll_event.SetName("FDC Polling"); + scheduler->RegistEvent(poll_event); + + motor_event.func = ToEventCallback(&FDCDevice::MotorEventCallback); + motor_event.time = 38.54_sec; + motor_event.SetName("FDC SED9420A TMOUT"); + scheduler->RegistEvent(motor_event); + + return true; +} + +// リセット void -FDCDevice::ResetHard() +FDCDevice::ResetHard(bool poweron) { - // XXX 他には - idle_phase(); + if (poweron) { + // READY ポーリングはリセットの影響を受けないはず + initialized = false; + scheduler->StopEvent(motor_event); + } + + // uPD765 マニュアルによると、リセット信号によるリセットでは + // SRT, HUT, HLT は影響を受けない。 + + // シリンダ位置 pcn はとりあえずわからないので放置。 + + // US0,US1 を low (FDD0) に。 + us = 0; + // 割り込み (と DRQ) をネゲート。 + msr.ndm = false; + st0.ic = ST0_IC_IC; + st0.se = false; + st0.ec = false; + st0.nr = false; + st0.hd = false; + st0.us = 0; + ChangeInterrupt(); + + scheduler->StopEvent(phase_event); + scheduler->StopEvent(poll_event); + CommandPhase(); } -uint64 -FDCDevice::Read8(uint32 addr) +busdata +FDCDevice::ReadPort(uint32 offset) { - uint32 offset = addr - baseaddr; + busdata data; + switch (offset) { + case 0: + if (dack) { + // DACK が立っているときは A0 によらずデータレジスタをアクセス + data = ReadData(); + } else { + data = GetMSR(); + putlog(4, "STAT -> $%02x", data.Data()); + } + break; case 1: - return ReadStatus(); + data = ReadData(); + break; + case 2: + data = ReadDriveStatus(); + break; case 3: - return ReadData(); - case 5: - return ReadDriveStatus(); - default: + data = 0xff; break; + + default: + VMPANIC("corrupted offset=%u", offset); } - PANIC("not impl"); -} -uint64 -FDCDevice::Read16(uint32 addr) -{ - return 0xff00 | Read8(addr + 1); + // InsideOut p.135 + const busdata wait = busdata::Wait(19 * 40_nsec); + data |= wait; + + data |= BusData::Size1; + return data; } -uint64 -FDCDevice::Write8(uint32 addr, uint32 data) +busdata +FDCDevice::WritePort(uint32 offset, uint32 data) { - uint32 offset = addr - baseaddr; switch (offset) { - case 3: + case 0: + if (dack) { + // DACK が立っているときは A0 によらずデータレジスタをアクセス + WriteDataReg(data); + break; + } + + // ここはフェーズに関わらず 0x34, 0x35, 0x36 のコマンドだけを + // いつでも受け付ける。RQM などの信号制御とは関係なく受け付ける、 + // となっているはずなので直接ディスパッチする。 + switch (data) { + case 0x34: + CmdResetStandby(); + break; + case 0x35: + CmdSetStandby(); + break; + case 0x36: + CmdSoftwareReset(); + break; + default: + putlog(0, "Write DATA: command $%02x (NOT IMPLEMENTED)", + data); + break; + } + break; + + case 1: WriteCmd(data); - return 0; - case 5: - if ((data & 0x0f) != 0) { - fdc.drivesel = data & 0x0f; - putlog(1, "ドライブ選択%s%s%s%s", - (fdc.drivesel & 1) ? " #0" : "", - (fdc.drivesel & 2) ? " #1" : "", - (fdc.drivesel & 4) ? " #2" : "", - (fdc.drivesel & 8) ? " #3" : ""); - } - if ((data & 0xe0) != 0) { - putlog(0, "未実装ビット書き込み $%06x <- $%02x", addr, data); - } - return 0; - - case 7: - fdc.is2DD = ((data & 0x10) != 0); - fdc.accdrive = data & 3; - putlog(1, "アクセスドライブ %s #%d", - fdc.is2DD ? "2DD" : "2HD", - fdc.accdrive); - fdd[fdc.accdrive].motor = (data & 0x80); - if (fdd[fdc.accdrive].motor) { - putlog(1, "ドライブ #%d モーターオン", fdc.accdrive); + break; + + case 2: + { + // ドライブコントロールレジスタ + putlog(1, "Drive Control <- $%02x", data); + + // FDD 割り込みクリア(要実機確認) + pedec->NegateINT(fdd_vector[0]); + + uint8 bit = 0x01; + for (const auto f : fdd_vector) { + if (f && bit_falling(drivectrl, data, bit)) { + f->SetDriveCtrl(data & 0xe0); + } + bit <<= 1; + } + drivectrl = data; + break; + } + + case 3: + { + bool is2DD = ((data & 0x10) != 0); + uint accdrive = data & 3; + bool motor_on_bit_new = data & 0x80; + + if (motor_on_bit_new) { + putlog(1, "Access Drive <- $%02x (MotorOn %s #%u)", data, + is2DD ? "2DD" : "2HD", + accdrive); + + if (motor_on_bit == false) { + // MOTOR_ON ビットが立ち上がったら、全ドライブに通知 + // motor_event は止める。 + scheduler->StopEvent(motor_event); + for (const auto f : fdd_vector) { + if (f) { + f->SetMotorOn(true); + } + } + } } else { - putlog(1, "ドライブ #%d モーターオフ", fdc.accdrive); + putlog(1, "Access Drive <- $%02x (MotorOff %s)", data, + is2DD ? "2DD" : "2HD"); + + // motor_on_bit はドライブセレクトイネーブルの意味も持つ + + // 全ドライブの選択を解除 + accdrive = MAX_DRIVE; + + if (motor_on_bit) { + // MOTOR_ON ビットが立ち下がったら、一定時間後に MOTOR_ON + // 信号をネゲートする。 + scheduler->RestartEvent(motor_event); + } } - return 0; - default: + // FDD 割り込みクリア(要実機確認) + pedec->NegateINT(fdd_vector[0]); + + motor_on_bit = motor_on_bit_new; + + DriveSelect(accdrive); + // 全ドライブに通知 + for (const auto f : fdd_vector) { + if (f) { + f->SetDriveSelect(accdrive, is2DD); + } + } break; + } + default: + VMPANIC("corrupted offset=%u", offset); } - PANIC("FDC 未実装書き込み $%06x <- $%02x", addr, data); - return 0; + + // InsideOut p.135 + const busdata wait = busdata::Wait(19 * 40_nsec); + + busdata r = wait; + r |= BusData::Size1; + return r; } -uint64 -FDCDevice::Write16(uint32 addr, uint32 data) +busdata +FDCDevice::PeekPort(uint32 offset) { - return Write8(addr + 1, data); + switch (offset) { + case 0: + if (dack == false) { + return GetMSR(); + } + FALLTHROUGH; + + case 1: + { + // 副作用なくデータを読み出す + switch (phase) { + case PHASE_COMMAND: + return 0xff; + + case PHASE_EXEC: + if (dreg.Empty()) { + return 0xff; // XXX 何が読めるか + } + return dreg.Peek(0); + + case PHASE_RESULT: + return resbuf.Peek(); + + default: + return 0xff; + } + } + + case 2: + return GetDriveStatus(); + + case 3: + return 0xff; + + default: + return 0xff; + } } -uint64 -FDCDevice::Peek8(uint32 addr) +bool +FDCDevice::PokePort(uint32 offset, uint32 data) { - return 0xff; + return false; } -// FDC ステータスレジスタ読み込み -uint32 -FDCDevice::ReadStatus() +// モニタ更新 +void +FDCDevice::MonitorUpdate(Monitor *, TextScreen& screen) +{ + int x; + int y; + uint8 local_msr; + uint8 local_st0; + uint8 local_st1; + uint8 local_st2; + uint8 drivestat; + FDDDevice *f; + + local_msr = GetMSR(); + local_st0 = GetST0(); + local_st1 = GetST1(); + local_st2 = GetST2(); + drivestat = GetDriveStatus(); + + screen.Clear(); + y = 0; + + // 0 1 2 3 4 5 6 + // 01234567890123456789012345678901234567890123456789012345678901234567890 + // $ee99xx MSR:$00 -- + // ST0: + // Command: $00 $11 $22 $33 $44 $55 $66 $77 $88 $99 (READ DATA) + + // MSR + screen.Print(0, y, "$%06x MSR:$%02x", (baseaddr + 1), local_msr); + static const char * const msr_bits[] = { + "RQM", "DIO", "NDM", "CB", "D3B", "D2B", "D1B", "D0B" + }; + MonitorReg(screen, 17, y, local_msr, msr_bits); + y++; + + // ST0 + screen.Print(9, y, "ST0:$%02x", local_st0); + static const char * const st0_bits[] = { + "", "", "SE", "EC", "NR", "HD", "US1", "US0" + }; + MonitorReg(screen, 17, y, local_st0, st0_bits); + static const char * const ic_str[] = { + "NT", + "AT", + "IC", + "AI", + }; + screen.Print(17, y, "IC=%s", ic_str[local_st0 >> 6]); + y++; + + // ST1 + screen.Print(9, y, "ST1:$%02x", local_st1); + static const char * const st1_bits[] = { + "EN", "-", "DE", "OR", "-", "ND", "NW", "MA" + }; + MonitorReg(screen, 17, y, local_st1, st1_bits); + y++; + + // ST2 + screen.Print(9, y, "ST2:$%02x", local_st2); + static const char * const st2_bits[] = { + "-", "CM", "DD", "NC", "SH", "SN", "BC", "MD" + }; + MonitorReg(screen, 17, y, local_st2, st2_bits); + y++; + + // ドライブコントロール + screen.Print(0, y, "$e94005 CTRL:$%02x", drivectrl); + static const char * const dctrl_bits[] = { + "LED", "EJM", "EJO", "-", "D#3", "D#2", "D#1", "D#0" + }; + MonitorReg(screen, 17, y, drivectrl, dctrl_bits); + y++; + + // ドライブステータス + screen.Print(0, y, " STAT:$%02x", drivestat); + static const char * const dstat_bits[] = { + "DIN", "ERR", "-", "-", "-", "-", "-", "-" + }; + MonitorReg(screen, 17, y, drivestat, dstat_bits); + y++; + + x = 52; + y = 0; + screen.Print(x, y++, "HD=%u", hd); + screen.Print(x, y++, "US=%u", us); + screen.Print(x, y++, "TC=%u", tc); + screen.Print(x, y++, "DACK=%u", dack); + + x = 60; + y = 0; + screen.Print(x, y++, "srt=%u (seek=%u)", srt, seek_srt); + screen.Print(x, y++, "hut=%u", hut); + screen.Print(x, y++, "hlt=%u", hlt); + screen.Print(x, y++, "nd=%u", nd); + + x = 52; + y = 5; + screen.Puts(x, y, ""); + screen.Print(x + 6, y, TA::OnOff(force_ready), "CT2"); + + // フェーズ、コマンド + x = 0; + y = 7; + static const char * const phase_str[] = { + "Command", + "Execute", + "Result", + }; + screen.Print(x, y++, "Phase: %s", phase_str[(int)phase]); + if (cmdbuf.len > 0) { + screen.Puts(x, y, "Command:"); + for (uint i = 0; i < cmdbuf.len; i++) { + screen.Print(x + 9 + i * 4, y, "$%02x", cmdbuf.buf[i]); + } + screen.Print(x + 49, y, "(%s)", cmd->name); + } + + x = 1; + y = 10; + // 存在するかに関わらず 4台分表示する (LUNA は知らん) + screen.Puts(0, y, " PCN NCN Ready Poll"); + screen.Puts(26, y, "Motor AccLED Eject FMT C H R N"); + y++; + for (int i = 0; i < MAX_DRIVE; i++) { + UnitInfo& u = unitinfo[i]; + + if (i < fdd_vector.size()) { + f = fdd_vector[i]; + } else { + f = NULL; + } + + screen.Print(x, y, "#%u", i); + if (f) { + screen.Print(x + 5, y, "%3d %3u", u.pcn, u.ncn); + screen.Print(x + 13, y, TA::OnOff(u.ready), "Ready"); + static const char * state_str[] = { + "Idle", + "Seek", + "Recal", + }; + if ((uint)u.state < countof(state_str)) { + screen.Puts(x + 19, y, state_str[(int)u.state]); + } else { + screen.Puts(x + 19, y, "state?"); + } + } else { + screen.Puts(x + 5, y, TA::Disable, "Not connected"); + } + + y++; + } + + // FDD の状況も同じモニタ内に表示する。 + for (int i = 0; i < fdd_vector.size(); i++) { + f = fdd_vector[i]; + if (f) { + f->MonitorUpdateFDD(screen, hd); + } + } +} + +// レジスタをビットごとに表示する。MonitorUpdate の下請け。 +void +FDCDevice::MonitorReg(TextScreen& screen, + int x, int y, uint8 reg, const char * const *names) +{ + for (uint i = 0; i < 8; i++) { + if (names[i][0] == '-') { + screen.Puts(x + i * 4, y, TA::Disable, "---"); + } else { + bool b = reg & (1 << (7 - i)); + screen.Puts(x + i * 4, y, TA::OnOff(b), names[i]); + } + } +} + +// メインステータスレジスタ(MSR)取得 +uint8 +FDCDevice::GetMSR() const { uint8 stat = 0; - if (fdc.RQM) { - stat |= FDC::STAT_RQM; + if (msr.rqm) { + stat |= MSR_RQM; } - if (fdc.DIO == DIOtoHost) { - stat |= FDC::STAT_DIO; + if (msr.dio == DIOtoHost) { + stat |= MSR_DIO; } - if (fdc.NDM) { - stat |= FDC::STAT_NDM; + if (msr.ndm) { + stat |= MSR_NDM; } - if (fdc.CB) { - stat |= FDC::STAT_CB; + if (msr.cb) { + stat |= MSR_CB; } - for (int i = 0; i < 4; i++) { - if (fdd[i].seek) { - stat |= (1 << i); - } + for (int i = 0; i < msr.db.size(); i++) { + stat |= (msr.db[i] ? 1 : 0) << i; } - putlog(2, "STAT -> $%02x", stat); return stat; } +// ST0 レジスタ取得 +uint8 +FDCDevice::GetST0() const +{ + uint8 data; + + data = st0.ic; + if (st0.se) { + data |= ST0_SE; + } + if (st0.ec) { + data |= ST0_EC; + } + if (st0.nr) { + data |= ST0_NR; + } + if (st0.hd) { + data |= ST0_HD; + } + data |= st0.us; + + return data; +} + +// ST1 レジスタ取得 +uint8 +FDCDevice::GetST1() const +{ + uint8 data = 0; + + if (st1.en) { + data |= ST1_EN; + } + if (st1.de) { + data |= ST1_DE; + } + if (st1.overrun) { + data |= ST1_OR; + } + if (st1.nd) { + data |= ST1_ND; + } + if (st1.nw) { + data |= ST1_NW; + } + if (st1.ma) { + data |= ST1_MA; + } + return data; +} + +// ST2 レジスタ取得 +uint8 +FDCDevice::GetST2() const +{ + uint8 data = 0; + + if (st2.cm) { + data |= ST2_CM; + } + if (st2.dd) { + data |= ST2_DD; + } + if (st2.nc) { + data |= ST2_NC; + } + if (st2.sh) { + data |= ST2_SH; + } + if (st2.sn) { + data |= ST2_SN; + } + if (st2.bc) { + data |= ST2_BC; + } + if (st2.md) { + data |= ST2_MD; + } + return data; +} + // FDC データレジスタ読み込み uint32 FDCDevice::ReadData() { uint8 data; - data = 0xff; - if (fdc.respos < fdc.reslen) { - data = fdc.resbuf[fdc.respos++]; + // TODO: RQM, DIO 処理 + + switch (phase) { + case PHASE_COMMAND: + // 規定フェーズ外の読み込み + // XXX ほんと? + putlog(1, "Read DATA: in irregular phase"); + data = 0xff; + return data; + + case PHASE_EXEC: + if (dreg.Dequeue(&data) == false) { + // エラー + return 0xff; + } + putlog(4, "DATA -> $%02x", data); + return data; + + case PHASE_RESULT: + data = resbuf.Take(); + putlog(2, "DATA -> $%02x", data); + if (resbuf.pos == 1) { + // 1 バイトを読んだところで + // ステータスをレポートするとクリアされるようだ + st0.ic = ST0_IC_IC; + st0.se = false; + st0.ec = false; + st0.nr = false; + st0.hd = false; + st0.us = 0; + ChangeInterrupt(); + } + if (resbuf.pos == resbuf.len) { + CommandPhase(); + } + return data; + + default: + VMPANIC("corrupted phase=$%x", (uint)phase); } - putlog(2, "DATA -> $%02x", data); +} - if (fdc.respos == fdc.reslen) { - idle_phase(); +// ドライブステータスレジスタ取得 +uint32 +FDCDevice::GetDriveStatus() const +{ + uint8 data = 0; + + // ドライブコントロールのドライブ選択が %1 になっているドライブを調べる。 + // 誤挿入は未対応。 + uint8 bit = 0x01; + for (const auto f : fdd_vector) { + if ((drivectrl & bit)) { + if (f && f->IsMediumLoaded()) { + data |= 0x80; + } + } + bit <<= 1; } + return data; } @@ -160,11 +760,8 @@ FDCDevice::ReadData() uint32 FDCDevice::ReadDriveStatus() { - uint8 data; - - // ディスク非挿入しかサポートしていない - data = 0x00; - putlog(1, "ドライブステータスレジスタ読み込み; ディスク非挿入"); + uint8 data = GetDriveStatus(); + putlog(1, "Drive Status -> $%02x", data); return data; } @@ -172,211 +769,955 @@ FDCDevice::ReadDriveStatus() void FDCDevice::WriteCmd(uint32 data) { - // cmdpos のインクリメントはログ表示の後で行なっている - fdc.cmdbuf[fdc.cmdpos] = data; - - // 1バイト目はコマンドで、それによって長さが変わる。 - // コマンド名をログに表示したい。 - if (fdc.cmdpos == 0) { - // コマンドはまず下位5ビットであたりをつける - switch (data & FDC::CMD_MASK) { - case FDC::CMD_SPECIFY: - fdc.cmdlen = 3; - break; - case FDC::CMD_SENSE_DEVICE_STATUS: - fdc.cmdlen = 2; - break; - case FDC::CMD_SENSE_INTERRUPT_STATUS: - fdc.cmdlen = 1; - break; - case FDC::CMD_RECALIBRATE: - fdc.cmdlen = 2; - break; - case FDC::CMD_SEEK: - fdc.cmdlen = 3; + switch (phase) { + case PHASE_COMMAND: + if (msr.rqm == false) { + // リクエストしていないコマンド書き込み + putlog(1, "CMD <- $%02x: not requested", data); break; + } + msr.rqm = false; - default: - // 未実装コマンド - fdc.cmdlen = 1; + putlog(2, "CMD[%u] <- $%02x", cmdbuf.len, data); + cmdbuf.buf[cmdbuf.len++] = data; + // コマンドの1バイト目でディスパッチ + if (cmdbuf.len == 1) { + // 見付からなければリスト末尾の Invalid にマッチする + for (auto& c : cmd_list) { + if ((cmdbuf.code & c.mask) == c.code) { + cmd = &c; + break; + } + } + putlog(1, "Command %s accepted", cmd->name); } + CallAfter(CommandCallback, 2_usec); + break; + + case PHASE_EXEC: + if (msr.dio == DIOtoFDC) { + WriteDataReg(data); + } else { + putlog(1, "CMD <- $%02x: unexpected data write (DIO)", data); + } + break; + + case PHASE_RESULT: + putlog(1, "CMD <- $%02x: unexpected data write (result phase)", data); + break; + + default: + VMPANIC("corrupted phase=$%x", (uint)phase); + } +} + +// FDC データレジスタ書き込み +void +FDCDevice::WriteDataReg(uint32 data) +{ + dreg.EnqueueForce(data); +} + +// Command フェーズに移行 +void +FDCDevice::CommandPhase() +{ + putlog(2, "Command Phase"); + phase = PHASE_COMMAND; + msr.rqm = true; + msr.dio = DIOtoFDC; + msr.ndm = false; + msr.cb = false; + cmdbuf.Clear(); + resbuf.Clear(); +} + +// Exec フェーズに移行 +void +FDCDevice::ExecPhase() +{ + putlog(2, "Exec Phase: %s", cmd->name); + phase = PHASE_EXEC; + msr.cb = true; + if (nd) { + msr.ndm = true; // XXX ? + } +} - const char *cmdname = cmd2name(data & FDC::CMD_MASK); - putlog(2, "CMD[%d] <- $%02x(%s)", fdc.cmdpos, data, cmdname); +// Result フェーズに移行 +void +FDCDevice::ResultPhase() +{ + putlog(2, "Result Phase"); + phase = PHASE_RESULT; + msr.rqm = true; + msr.dio = DIOtoHost; + msr.ndm = false; + msr.cb = true; +} + +void +FDCDevice::ResultCallback(Event& ev) +{ + ResultPhase(); +} + +void +FDCDevice::CommandCallback(Event& ev) +{ + msr.cb = true; + if (cmdbuf.len == cmd->cmdlen) { + putlog(2, "Command %s execute", cmd->name); + (this->*(cmd->func))(); } else { - putlog(2, "CMD[%d] <- $%02x", fdc.cmdpos, data); - // CB は2バイト目から - fdc.CB = true; + msr.rqm = true; } - // ここでインクリメント - fdc.cmdpos++; +} - // 全部受け取ったらフェーズ遷移 - if (fdc.cmdpos >= fdc.cmdlen) { - exec_phase(); +// 割り込み信号線の状態を変える +void +FDCDevice::ChangeInterrupt() +{ + if (GetST0() != 0x80 || (msr.ndm && 0/* XXX DRQ*/)) { + putlog(4, "AssertINT"); + pedec->AssertINT(this); + } else { + putlog(4, "NegateINT"); + pedec->NegateINT(this); } } -// idle フェーズに移行 +// READY 信号が変化したことの通知 (FDD から呼ばれる) void -FDCDevice::idle_phase() +FDCDevice::ReadyChanged(int unit, bool is_ready) { - fdc.phase = PHASE_IDLE; - fdc.RQM = true; - fdc.DIO = DIOtoFDC; - fdc.NDM = false; - fdc.CB = false; - fdc.cmdpos = 0; - fdc.cmdlen = 0; - fdc.respos = 0; - fdc.reslen = 0; - putlog(2, "Idle フェーズ"); + // 実際は READY 信号は SPECIFY コマンドが呼ばれた後 1.024 msec 間隔で + // 常にポーリングしているが、エミュレータでは無駄なので、変化があった + // ことの通知を受けてポーリングループを起動し、どのユニットも変化が + // なくなったらポーリングを止めるようにしておく。 + // uPD765 マニュアル POLLING FEATURE OF THE uPD765 (p.433) + + // SPECIFY コマンドで有効になる + if (initialized) { + StartPoll(); + } + // 転送の同期を取るために、READY になった時刻を覚えておく + if (is_ready) { + unitinfo[unit].ready_time = scheduler->GetVirtTime(); + } } -// Exec フェーズに移行 +// ポーリング開始 void -FDCDevice::exec_phase() +FDCDevice::StartPoll() { - fdc.phase = PHASE_EXEC; - fdc.DIO = DIOtoFDC; - fdc.CB = true; - if (fdc.nd) { - fdc.NDM = true; // XXX ? - } - - switch (fdc.cmdbuf[0] & FDC::CMD_MASK) { - case FDC::CMD_SPECIFY: - putlog(1, "Exec フェーズ: SPECIFY"); - fdc.srt = fdc.cmdbuf[1] >> 4; - fdc.hut = fdc.cmdbuf[1] & 15; - fdc.hlt = fdc.cmdbuf[2] >> 1; - fdc.nd = (fdc.cmdbuf[2] & 1); - idle_phase(); - break; - - case FDC::CMD_SENSE_DEVICE_STATUS: - putlog(1, "Exec フェーズ: SENSE DEVICE STATUS"); - fdc.resbuf[0] = fdc.cmdbuf[1] & 0x07; - fdc.resbuf[0] |= 0x00; - fdc.reslen = 1; - result_phase(); - break; + if (poll_event.IsRunning() == false) { + putlog(3, "Start polling"); + scheduler->StartEvent(poll_event); + } +} - case FDC::CMD_SENSE_INTERRUPT_STATUS: - { - putlog(1, "Exec フェーズ: SENSE INTERRUPT STATUS"); - // XXX まだフロッピー挿入はない - uint8 st0 = 0; - st0 |= FDC::ST0_IC_NT; - st0 |= FDC::ST0_EC; - st0 |= FDC::ST0_NR; - st0 |= fdc.accdrive; - fdc.resbuf[0] = st0; - fdc.resbuf[1] = 0; // XXX PCN - fdc.reslen = 2; - result_phase(); - break; - } +// ポーリングイベントコールバック +void +FDCDevice::PollEventCallback(Event& ev) +{ + bool is_poll = false; - case FDC::CMD_RECALIBRATE: - putlog(1, "Exec フェーズ: RECALIBRATE"); - fdd[fdc.accdrive].seek = true; - // SEEK/RECALIBRATE では CB はセットされない - fdc.CB = false; - idle_phase(); - // XXX ヘッドの位置という概念がまだない - event.time = 0; - event.code = FDC::CMD_RECALIBRATE; // XXX コマンドをメンバ変数にするとか - event.Start(); - break; - - case FDC::CMD_SEEK: - putlog(1, "Exec フェーズ: SEEK (未実装)"); - // SEEK/RECALIBRATE では CB はセットされない - fdc.CB = false; - idle_phase(); - break; + // READY ポーリング。 + // コマンドフェーズにいてコマンド未発行のときだけ実行。 + // Inside で言う「シーク中の E-PHASE」も FDC としては + // コマンドフェーズなので同じ状態になっている。 + if (phase == PHASE_COMMAND && cmdbuf.len == 0) { + for (int unit_no = 0; unit_no < MAX_DRIVE; unit_no++) { + is_poll |= PollReady(unit_no); + } + } - default: - putlog(0, "未実装 Exec フェーズ $%02x", fdc.cmdbuf[0]); - break; + // シーク + seek_srt++; + if (seek_srt == 16) { + seek_srt = srt; + for (int unit_no = 0; unit_no < MAX_DRIVE; unit_no++) { + is_poll |= ExecSeek(unit_no); + } + } else { + is_poll = true; + } + + if (is_poll) { + scheduler->RestartEvent(ev); + } else { + putlog(3, "Stop polling"); } } -// Result フェーズに移行 +// READY ポーリングの実行。 +// ポーリングを継続する必要があるときは true を返す。 +bool +FDCDevice::PollReady(int unit_no) +{ + auto& unit = unitinfo[unit_no]; + + UnitSelect(unit_no); + bool r = IsReady(); + if (unit.ready != r) { + // READY が変化したので、ドライブ状態が Idle でかつ、 + // 割り込み発行中でなければ状態を更新して割り込みを発行 + if (unit.state == UnitState::Idle && st0.ic == ST0_IC_IC) { + unit.ready = r; + st0.ic = ST0_IC_AI; + st0.nr = !r; + st0.us = unit_no; + ChangeInterrupt(); + return false; + } else { + // 割り込み発生をペンディングしているのでポーリング続行 + return true; + } + } + return false; +} + +// リキャリブレートまたはシークの実行。 +// ポーリングを継続する必要があるときは true を返す。 +bool +FDCDevice::ExecSeek(int unit_no) +{ + auto& unit = unitinfo[unit_no]; + + if (unit.state == UnitState::Idle) { + return false; + } + + UnitSelect(unit_no); + + if (IsReady() == false) { + unit.state = UnitState::Idle; + st0.nr = true; + st0.ic = ST0_IC_AT; + st0.hd = false; + st0.us = us; + ChangeInterrupt(); + CommandPhase(); + return false; + } + unit.ready = true; + + if (unit.state == UnitState::Recalibrate) { + if (fdd && fdd->GetTrack0()) { + putlog(2, "Track0 detected"); + unit.pcn = 0; + SeekEnd(); + return false; + } else if (unit.pcn < 0) { + putlog(2, "Track0 not detected"); + st0.ec = true; + SeekEnd(); + return false; + } + } else { + if (unit.pcn == unit.ncn) { + SeekEnd(); + return false; + } + } + + // シークを FDD に発行 + int dir; + if (unit.pcn < unit.ncn) { + dir = 1; + } else { + dir = -1; + } + unit.pcn += dir; + if (fdd) { + fdd->DoSeek(dir); + } + return true; +} + +// 強制レディ信号の設定 (OPM から呼ばれる) void -FDCDevice::result_phase() +FDCDevice::SetForceReady(bool force_ready_) { - putlog(1, "Result フェーズ"); - fdc.phase = PHASE_RESULT; - fdc.RQM = true; - fdc.DIO = DIOtoHost; - fdc.NDM = false; - fdc.CB = true; -} - -// イベントコールバック -void -FDCDevice::Callback(int arg) -{ - switch (arg) { - case FDC::CMD_RECALIBRATE: - fdd[fdc.accdrive].seek = false; - fdc.DIO = DIOtoHost; - Interrupt(); - break; + force_ready = force_ready_; + putlog(2, "Force Ready %s", force_ready ? "On" : "Off"); + if (initialized) { + StartPoll(); } } -// 割り込みを上げる -void -FDCDevice::Interrupt() -{ - gIOCon->IntFDC(); -} - -// コマンド名 -/* static */ const char * -FDCDevice::cmdnames_str[] = { - NULL, // 0x00 - NULL, // 0x01 - "READ DIAGNOSTIC", // 0x02 - "SPECIFY", // 0x03 - "SENSE DEVICE STATUS", // 0x04 - "WRITE DATA", // 0x05 - "READ", // 0x06 - "RECALIBRATE", // 0x07 - "SENSE INTERRUPT STATUS", // 0x08 - "WRITE DELETED DATA", // 0x09 - "READ ID", // 0x0a - NULL, // 0x0b - "READ DELETED DATA", // 0x0c - "WRITE ID", // 0x0d - NULL, // 0x0e - "SEEK", // 0x0f - NULL, // 0x10 - "SCAN EQUAL", // 0x11 - NULL, // 0x12 - NULL, // 0x13 - "RESET STANDBY", // 0x14 - "SET STANDBY", // 0x15 - "SOFTWARE RESET", // 0x16 - NULL, // 0x17 - NULL, // 0x18 - "SCAN LOW OR EQUAL", // 0x19 - NULL, // 0x1a - NULL, // 0x1b - NULL, // 0x1c - "SCAN HIGH OR EQUAL", // 0x1d -}; +// DACK 信号をアサートする (DMAC から呼ばれる) +void +FDCDevice::AssertDACK(bool tc_) +{ + dack = true; + tc = tc_; + // DRQ を下げる + dmac->NegateREQ(0); +} + +// DACK 信号をネゲートする (DMAC から呼ばれる) +void +FDCDevice::NegateDACK() +{ + dack = false; +} + +// 内部データレジスタに data を投入 +void +FDCDevice::PushDreg(uint8 data) +{ + if (dreg.Enqueue(data)) { + // FDC DRQ をアサートして DMAC REQ#0 を送る + RequestDataByte(); + } else { + // OverRun + putlog(1, "PushDreg overrun detected"); + st1.overrun = true; + st0.ic = ST0_IC_AT; + ChangeInterrupt(); + } +} + +// 内部データレジスタから data を取り出す。 +// 空なら (uint32)-1 を返す。 +uint32 +FDCDevice::PopDreg() +{ + uint8 data; + + if (dreg.Dequeue(&data)) { + return data; + } else { + // OverRun + putlog(1, "PopDreg overrun detected"); + st1.overrun = true; + st0.ic = ST0_IC_AT; + ChangeInterrupt(); + return (uint32)-1; + } +} + +// CPU または DMAC に読み書きデータを要求する +void +FDCDevice::RequestDataByte() +{ + if (nd) { + putlog(0, "CPU Transfer mode (NOT IMPLEMENTED)"); + } else { + dmac->AssertREQ(0); + } +} + +// ユニット US0,US1 信号線の状態を変える。 +void +FDCDevice::UnitSelect(uint8 us_) +{ + // こちらは FDC 側の US0, US1 信号線の出力に相当。 + // アクセスドライブは X68k では I/O コントローラ側で決まる。 + // LUNA では XP 側の PSG の I/O と US 信号線の組み合わせで決まる。 + us = us_ & 0x03; + + putlog(3, "UnitSelect US:%u", us); + + // いまのところ LUNA のことは忘れる +} + +// ドライブセレクト信号線の状態を変える。 +void +FDCDevice::DriveSelect(uint8 drive) +{ + // こちらは I/O コントローラにあるアクセスドライブセレクトのほう + if (drive < ndrive) { + fdd = fdd_vector[drive]; + } else { + fdd = NULL; + } + + // いまのところ LUNA のことは忘れる +} + +// 一定時間後に MOTOR_ON 信号をネゲートするイベント。 +// SED9420 の TMOUT 信号に相当。 +void +FDCDevice::MotorEventCallback(Event& ev) +{ + // 全ドライブに通知 + for (const auto f : fdd_vector) { + if (f) { + f->SetMotorOn(false); + } + } +} + +// FDC コマンド + +void +FDCDevice::CmdReadDiagnostic() +{ + CmdNotImplemented(); +} + +void +FDCDevice::CmdSpecify() +{ + srt = cmdbuf.specify.srt_hut >> 4; + hut = cmdbuf.specify.srt_hut & 15; + hlt = cmdbuf.specify.hlt_nd >> 1; + nd = cmdbuf.specify.hlt_nd & 1; + putlog(2, " SRT:%u HUT:%u HLT:%u ND:%u", srt, hut, hlt, nd); + + // 初期化完了、ポーリング開始 + initialized = true; + + CommandPhase(); +} + +void +FDCDevice::CmdSenseDeviceStatus() +{ + UnitSelect(cmdbuf.sense_device_status.hd_us); + + uint8 st3 = 0; + // FT = fdd.GetFault(); + if (fdd && fdd->GetWriteProtect()) { + st3 |= ST3_WP; + } + if (IsReady()) { + st3 |= ST3_RY; + } + if (fdd && fdd->GetTrack0()) { + st3 |= ST3_T0; + } + // TS = fdd.GetTwoSide(); + // HD, US + st3 |= (cmdbuf.sense_device_status.hd_us & 0x07); + + putlog(2, " ST3=$%02x", st3); + resbuf.device_status.st3 = st3; + resbuf.len = sizeof(resbuf.device_status); + // 1.024 msec は READY のポーリング間隔から推定。 + // 正確な数字は実機調査すること。 + CallAfter(ResultCallback, 1024_usec); +} + +void +FDCDevice::CmdWriteData() +{ + UnitSelect(cmdbuf.data.hd_us); + + mt = cmdbuf.code & 0x80; + mf = cmdbuf.code & 0x40; + sk = 0; + + hd = (cmdbuf.data.hd_us & 0x04) >> 2; + + if (loglevel >= 2) { + putlogn(" MT:%u MF:%u SK:%u HD:%u", (uint)mt, (uint)mf, (uint)sk, hd); + putlogn(" C:%2u H:%u R:%2u N:%u", + cmdbuf.data.c, + cmdbuf.data.h, + cmdbuf.data.r, + cmdbuf.data.n); + putlogn(" EOT:%2u GSL:$%02x DTL:$%02x", + cmdbuf.data.eot, + cmdbuf.data.gsl, + cmdbuf.data.dtl); + } + + XferStart(true); +} + +void +FDCDevice::CmdReadData() +{ + UnitSelect(cmdbuf.data.hd_us); + + mt = cmdbuf.code & 0x80; + mf = cmdbuf.code & 0x40; + sk = cmdbuf.code & 0x20; + + hd = (cmdbuf.data.hd_us & 0x04) >> 2; + + if (loglevel >= 2) { + putlogn(" MT:%u MF:%u SK:%u HD:%u", (uint)mt, (uint)mf, (uint)sk, hd); + putlogn(" C:%2u H:%u R:%2u N:%u", + cmdbuf.data.c, + cmdbuf.data.h, + cmdbuf.data.r, + cmdbuf.data.n); + putlogn(" EOT:%2u GSL:$%02x DTL:$%02x", + cmdbuf.data.eot, + cmdbuf.data.gsl, + cmdbuf.data.dtl); + } + + XferStart(false); +} + +void +FDCDevice::XferStart(bool is_write) +{ + resbuf.len = sizeof(resbuf.data); + + if (IsReady() == false) { + st0.nr = true; + st0.ic = ST0_IC_AT; + CallAfter(XferEndCallback, 0); + return; + } + unitinfo[us].ready = true; + assert(fdd); + + tc = false; + dack = false; + dreg.Clear(); + xfer_start = false; + xfer_enable = false; + xfer_write = is_write; + index_detected = 0; + + fdd->ReadTrack(hd); + + ExecPhase(); + + // FDD がバイトを読み書きするちょうど真ん中のタイミングで動くように + // 同期してみる。 + uint64 now = scheduler->GetVirtTime(); + uint64 t = 16_usec - ((now - unitinfo[us].ready_time) % 16_usec); + t += 8_usec; + + CallAfter(XferStartCallback, t); +} + +void +FDCDevice::XferStartCallback(Event& ev) +{ + // マーク位置まで待つ。 + // マークを読んでみてディスパッチ。 + // IAM ならなにもしない。 + // IDAM なら CHRN を読み出す。データ転送が開始されていない時は + // R を検査して、一致したら転送開始状態にしておく。 + // DAM/DDAM のときは、転送開始状態でなければ何もしない。 + // 転送開始状態の時はデータ転送またはエラー処理。 + // 転送が終わったら CHR を進めて EOT になったら + // MT=1 で HD=0 のときは裏に行く。 + // そうでなければ EN エラー。 + + assert(fdd); + + int dist = fdd->GetDistanceToMark(); + if (dist < 0) { + VMPANIC("dist < 0"); + } + if (dist == 0) { + uint8 mark = fdd->ReadByte(); + ev.time = 1 * 16_usec; + if (mark == MARK_IDAM) { + // CHRN と CRC の合計時間を加算 + ev.time += (4 + 2) * 16_usec; + + resbuf.data.c = fdd->PeekByte(1); + resbuf.data.h = fdd->PeekByte(2); + resbuf.data.r = fdd->PeekByte(3); + resbuf.data.n = fdd->PeekByte(4); + putlog(2, "IDAM C:%2u H:%u R:%2u N:%u", + resbuf.data.c, + resbuf.data.h, + resbuf.data.r, + resbuf.data.n); + + // READ ID は最初に発見した ID を返す。 + if ((cmdbuf.code & 0xbf) == 0x0a) { + putlog(2, "READ ID complete"); + st0.ic = ST0_IC_NT; + CallAfter(XferEndCallback, ev.time); + return; + } + + // uPD765 マニュアル pXX に R だけを一致検査すると書いてある。 + // 72065B でどうかは未調査だが、CHRN 4つ比較することにする。 + if ((resbuf.data.c == cmdbuf.data.c) && + (resbuf.data.h == cmdbuf.data.h) && + (resbuf.data.r == cmdbuf.data.r) && + (resbuf.data.n == cmdbuf.data.n)) + { + putlog(3, "Transfer decided"); + xfer_start = true; + sect_remain = 1 << (7 + resbuf.data.n); + if (cmdbuf.data.n == 0) { + xfer_remain = cmdbuf.data.dtl; + if (xfer_remain > 128) { + xfer_remain = 128; + } + } else { + xfer_remain = sect_remain; + } + // CRC のぶん + sect_remain += 2; + } + } else if (mark == MARK_DAM) { + xfer_enable = xfer_start; + if (xfer_enable) { + putlog(3, "DAM detected. Sector data will be transferred"); + if (xfer_write) { + RequestDataByte(); + } + CallAfter(XferDataCallback, ev.time); + return; + } else { + putlog(3, "DAM detected. Sector data will be skipped"); + } + } else if (mark == MARK_DDAM) { + VMPANIC("DDAM (NOT IMPLEMENTED)"); + } else if (mark == MARK_IAM) { + // IAM は何もすることがない + } else { + // インデックスホールを都合3回検出したらエラー終了 + if (fdd->IsIndex()) { + index_detected++; + if (index_detected == 3) { + st0.ic = ST0_IC_AT; + if (xfer_start) { + st1.nd = true; + } else { + st1.ma = true; + } + CallAfter(XferEndCallback, ev.time); + return; + } + } + } + } else { + // 次のマークまで待つ + ev.time = dist * 16_usec; + } + scheduler->StartEvent(ev); +} + +void +FDCDevice::XferDataCallback(Event& ev) +{ + assert(fdd); + + ev.time = 1 * 16_usec; + + // 変態フォーマットをサポートするときはこのへんに追加が必要。 + + if (xfer_remain > 0) { + if (__predict_false(xfer_write)) { + uint32 data = PopDreg(); + if ((int32)data >= 0) { + fdd->WriteByte(data); + } else { + // XXX: こうじゃなくてエラービットだけ立ててセクタ終了時処理かも + return; + } + } else { + PushDreg(fdd->ReadByte()); + } + xfer_remain--; + } + + bool is_eot = false; + + if (sect_remain > 0) { + sect_remain--; + } else { + // セクタデータ転送完了 + putlog(3, "Sector data transfer completed"); + // TODO: Data CRC チェック + + xfer_start = false; + xfer_enable = false; + + is_eot = (resbuf.data.r >= cmdbuf.data.eot); + + // マルチトラックの処理 + if (is_eot) { + if (xfer_write) { + fdd->WriteTrack(); + } + resbuf.data.r = 1; + if (mt && hd == 0) { + is_eot = false; + + // HD=1 の読み込み + fdd->ReadTrack(1); + } else { + resbuf.data.c += 1; + } + if (mt) { + // HD=1 から MT を立てて読み終わると HD=0 になっている + hd ^= 1; + resbuf.data.h = hd; + } + } else { + resbuf.data.r += 1; + } + + // EOT チェック + if (tc == false) { + if (is_eot) { + putlog(3, "EOT reached"); + st1.en = true; + st0.ic = ST0_IC_AT; + CallAfter(XferEndCallback, 16_usec); + return; + } else { + // 次のセクタの ID へ継続 + putlog(3, "Multi sector; continue to transfer"); + cmdbuf.data.c = resbuf.data.c; + cmdbuf.data.h = resbuf.data.h; + cmdbuf.data.r = resbuf.data.r; + cmdbuf.data.n = resbuf.data.n; + CallAfter(XferStartCallback, 16_usec); + return; + } + } + } -// コマンドを名前に変換 -/* static */ const char * -FDCDevice::cmd2name(uint32 cmd) + if (tc) { + putlog(3, "Done by TC signal"); + if (xfer_write) { + fdd->WriteTrack(); + } + st0.ic = ST0_IC_NT; + CallAfter(XferEndCallback, 16_usec); + return; + } + + if (xfer_write && xfer_remain > 0) { + RequestDataByte(); + } + scheduler->StartEvent(ev); +} + +void +FDCDevice::XferEndCallback(Event& ev) +{ + st0.hd = hd; + st0.us = us; + resbuf.data.st0 = GetST0(); + resbuf.data.st1 = GetST1(); + resbuf.data.st2 = GetST2(); + ChangeInterrupt(); + ResultPhase(); +} + +// READY 入力端子 +bool +FDCDevice::IsReady() const +{ + return force_ready || (fdd && fdd->GetReady()); +} + +void +FDCDevice::CmdRecalibrate() +{ + UnitSelect(cmdbuf.recalibrate.us); + + if (IsReady() == false) { + st0.nr = true; + st0.ic = ST0_IC_AT; + st0.hd = false; + st0.us = us; + ChangeInterrupt(); + CommandPhase(); + return; + } + unitinfo[us].ready = true; + + msr.db[us] = true; + unitinfo[us].pcn = 255; + unitinfo[us].ncn = 0; + unitinfo[us].state = UnitState::Recalibrate; + CommandPhase(); + StartPoll(); +} + +// シーク終了処理 +void +FDCDevice::SeekEnd() { - if (cmd < countof(cmdnames_str) && cmdnames_str[cmd]) { - return cmdnames_str[cmd]; + putlog(2, "Seek completed"); + st0.ic = ST0_IC_NT; + st0.se = true; + st0.us = us; + unitinfo[us].state = UnitState::Idle; + ChangeInterrupt(); + CommandPhase(); +} + +void +FDCDevice::CmdSenseInterruptStatus() +{ + // XXX とりあえずここで割り込みを下げる。 + // 実際には割り込み状態をラッチして信号線に出すというコマンドで + // 割り込み要因がオフになっていればそれによってネゲートされる + // ということのようだ。 + + // XXX 割り込みが起きていないときは INVALID と同じらしい + + if (st0.ic != ST0_IC_IC) { + // シーク完了割り込みを発生させた Unit のトラックをレポート + resbuf.interrupt_status.pcn = unitinfo[st0.us].pcn; + msr.db[st0.us] = false; + resbuf.len = sizeof(resbuf.interrupt_status); } else { - return "?"; + resbuf.len = sizeof(resbuf.invalid); + } + resbuf.interrupt_status.st0 = GetST0(); + putlog(2, " ST0=$%02x", resbuf.interrupt_status.st0); + ResultPhase(); +} + +void +FDCDevice::CmdWriteDeletedData() +{ + CmdNotImplemented(); +} + +void +FDCDevice::CmdReadID() +{ + UnitSelect(cmdbuf.data.hd_us); + + mf = cmdbuf.code & 0x40; + hd = (cmdbuf.data.hd_us & 0x04) >> 2; + putlog(2, " MF:%u HD:%u", (uint)mf, hd); + + XferStart(false); +} + +void +FDCDevice::CmdReadDeletedData() +{ + CmdNotImplemented(); +} + +void +FDCDevice::CmdWriteID() +{ + CmdNotImplemented(); +} + +void +FDCDevice::CmdSeek() +{ + putlog(2, "SEEK start"); + + UnitSelect(cmdbuf.seek.us); + + if (IsReady() == false) { + st0.nr = true; + st0.ic = ST0_IC_AT; + st0.hd = false; + st0.us = us; + ChangeInterrupt(); + CommandPhase(); + return; } + unitinfo[us].ready = true; + + msr.db[us] = true; + unitinfo[us].ncn = cmdbuf.seek.ncn; + unitinfo[us].state = UnitState::Seek; + putlog(2, " NCN:%u", unitinfo[us].ncn); + CommandPhase(); + StartPoll(); +} + +void +FDCDevice::CmdVersion() +{ + CmdNotImplemented(); +} + +void +FDCDevice::CmdScanEqual() +{ + CmdNotImplemented(); +} + +void +FDCDevice::CmdScanLowOrEqual() +{ + CmdNotImplemented(); +} + +void +FDCDevice::CmdScanHighOrEqual() +{ + CmdNotImplemented(); +} + +void +FDCDevice::CmdResetStandby() +{ + CmdNotImplemented(); +} + +void +FDCDevice::CmdSetStandby() +{ + CmdNotImplemented(); } + +void +FDCDevice::CmdSoftwareReset() +{ + ResetHard(false); +} + +void +FDCDevice::CmdInvalid() +{ + CmdNotImplemented(); +} + +void +FDCDevice::CmdNotImplemented() +{ + UnitSelect(cmdbuf.data.hd_us); + hd = (cmdbuf.data.hd_us & 0x04) >> 2; + + putlog(0, "Command %s NOT IMPLEMENTED", cmd->name); + // とりあえずエラー + st0.ic = ST0_IC_AT; + st0.hd = hd; + st0.us = us; + ChangeInterrupt(); + CommandPhase(); +} + +// コマンド +#define CMD(x) &FDCDevice::Cmd##x +/*static*/ const std::vector +FDCDevice::cmd_list { + // code mask cmdlen func name + { 0x02, 0xbf, 9, CMD(ReadDiagnostic), "READ DIAGNOSTIC" }, + { 0x03, 0xff, 3, CMD(Specify), "SPECIFY" }, + { 0x04, 0xff, 2, CMD(SenseDeviceStatus), "SENSE DEVICE STATUS" }, + { 0x05, 0x3f, 9, CMD(WriteData), "WRITE DATA" }, + { 0x06, 0x1f, 9, CMD(ReadData), "READ DATA" }, + { 0x07, 0xff, 2, CMD(Recalibrate), "RECALIBRATE" }, + { 0x08, 0xff, 1, CMD(SenseInterruptStatus), "SENSE INTERRUPT STATUS" }, + { 0x09, 0x3f, 9, CMD(WriteDeletedData), "WRITE DELETED DATA" }, + { 0x0a, 0xbf, 2, CMD(ReadID), "READ ID" }, + { 0x0c, 0x1f, 9, CMD(ReadDeletedData), "READ DELETED DATA" }, + { 0x0d, 0xbf, 6, CMD(WriteID), "WRITE ID" }, + { 0x0f, 0xff, 3, CMD(Seek), "SEEK" }, + { 0x10, 0x1f, 1, CMD(Version), "VERSION" }, + { 0x11, 0x1f, 9, CMD(ScanEqual), "SCAN EQUAL" }, + { 0x19, 0x1f, 9, CMD(ScanLowOrEqual), "SCAN LOW OR EQUAL" }, + { 0x1d, 0x1f, 9, CMD(ScanHighOrEqual), "SCAN HIGH OR EQUAL" }, + { 0x34, 0xff, 1, CMD(ResetStandby), "RESET STANDBY" }, + { 0x35, 0xff, 1, CMD(SetStandby), "SET STANDBY" }, + { 0x36, 0xff, 1, CMD(SoftwareReset), "SOFTWARE RESET" }, + + // リストの最後に Invalid を置く。 + // code=0x00, mask=0x00 なので必ず一致する。 + { 0x00, 0x00, 1, CMD(Invalid), "invalid" }, +};