--- nono/vm/mk48t02.cpp 2026/04/29 17:04:32 1.1.1.3 +++ nono/vm/mk48t02.cpp 2026/04/29 17:05:33 1.1.1.16 @@ -1,124 +1,149 @@ // // nono -// Copyright (C) 2018 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt // -#include "mk48t02.h" -#include "mainapp.h" -#include "config.h" -#include -#include -#include - // -// MK48T02 +// NVRAM/RTC (MK48T02) // + // SRAM 部分はビッグエンディアンのままメモリに置かれるので、 // 必要ならアクセス時に都度変換すること。 // // 仕様は 0x45000000 から 2KB だが、 -// 0x44000000 から 0x48000000 の手前まで 2KB 単位でミラーが見える。 +// LUNA-I では 0x44000000 から 0x47ffffff まで 2KB 単位でミラーが見える。 +// LUNA-88K では 0x44000000 から 0x45ffffff までが +// (こちらはロングワード配置なので) 8KB 単位のミラーに見える。 + +#include "mk48t02.h" +#include "bitops.h" +#include "config.h" +#include "mainapp.h" +#include "monitor.h" +#include "mpu.h" +#include +#include +#include +#include +// コンストラクタ MK48T02Device::MK48T02Device() + : inherited() { - logname = "mk48t02"; - devname = "MK48T02"; - devaddr = 0x45000000; - monitor.Init(25, 2); + SetName("MK48T02"); + + AddAlias("MK48T02"); + AddAlias("NVRAM"); - fd = -1; + // 今の所 UTC として使うケースだけのようだ。 + use_localtime = false; + + monitor = gMonitorManager->Regist(ID_MONITOR_MK48T02, this); + monitor->func = ToMonitorCallback(&MK48T02Device::MonitorUpdate); + monitor->SetSize(40, 13); } +// デストラクタ MK48T02Device::~MK48T02Device() { - if (mem) { - munmap(mem, memlen); - mem = NULL; - } - if (fd >= 0) { - close(fd); - fd = -1; - } } +// 初期化 bool MK48T02Device::Init() { + if (inherited::Init() == false) { + return false; + } + struct stat st; - // ファイルのロード - filename = gMainApp.GetVMDir() + "NVRAM.DAT"; - memlen = 2040; + // NetBSD/luna68k, OpenBSD/luna88k ともに epoch を 1970年としている + // (2021/07 現在) 一方、4.4BSD (luna68k 版?) は 1900年としている。 + // MK48T02 本来定義は 0 というか 1900 というか。DecodeYear() も参照。 + const ConfigItem& item = gConfig->Find("rtc-epoch-year"); + int year_epoch_val; + if (item.TryInt(&year_epoch_val) == false) { + item.Err(); + return false; + } + // 雑にチェック + if (year_epoch_val < 1900) { + item.Err("Must be >= 1900"); + return false; + } + year_epoch = (uint)year_epoch_val; - fd = open(filename.c_str(), O_RDWR); - if (fd == -1) { - if (errno != ENOENT) { - warn("NVRAM \"%s\" open failed", filename.c_str()); - return false; + // MK48T02 のデータシートには年には西暦年の下位2桁を設定するように + // 指示されており、4.4BSD (luna68k) はこの通りになっている。 + // しかし CMU Mach luna88k, NetBSD/luna68k, OpenBSD/luna88k はいずれも + // 西暦年から 1970 引いた値を設定するようになっている (2021/07 現在)。 + // このため、うるう年が2年ずれた状態になる。 + // + // NetBSD で MK48T02 を採用している luna68k 以外の port は + // ソースコード上の変数 year0 もしくは YEAR0 に + // newsmips, news68k: 1900 (データシート規定通り) + // sun3, sparc, mvme68k: 1968 (1968 はうるう年なので問題ない) + // を採用しており、バグっていない。 + // この year0 は UNIX EPOCH とは直接関係がない RTC ハードウェアに + // 依存する値なのだが、LUNA では混同したのだろうか? + // + // たぶんこのバグはもう修正されることはないだろうから、こちらで補正する + // スイッチを用意した。 + if (gMainApp.Has(VMCap::LUNA)) { + if (gConfig->Find("luna-adjust-misused-epoch").AsInt()) { + // エポック年のうるう年カウンタ相当を補正項として持つ + adjust_leap = year_epoch % 4; } else { - // オープンできない理由が ENOENT ならファイルを作ってみる - fd = open(filename.c_str(), O_RDWR | O_CREAT, 0644); - if (fd < 0) { - warn("NVRAM \"%s\" create failed", filename.c_str()); - return false; - } - - // ゼロで埋める (可変長配列は一様初期化できない(環境がある)ようだ) - char zerobuf[memlen]; - memset(zerobuf, 0, memlen); - if (write(fd, zerobuf, sizeof(zerobuf)) < 0) { - warn("NVRAM \"%s\" write failed", filename.c_str()); - close(fd); - return false; - } - warnx("NVRAM \"%s\" created", filename.c_str()); - // FALLTHROUGH + adjust_leap = 0; } } - // ファイルサイズをチェック。 - // ファイルが短くても mmap はしれっと成功してしまうようだ。 - if (fstat(fd, &st) == -1) { - warn("NVRAM \"%s\" fstat failed", filename.c_str()); - close(fd); - return false; - } - if (st.st_size < memlen) { - warnx("NVRAM \"%s\" is too short (filesize is expected %d but %d)", - filename.c_str(), memlen, (int)st.st_size); - close(fd); - return false; + // MSX-DOS モードなら NVRAM 不要なのでここでダミーのメモリだけ用意。 + if (gMainApp.msxdos_mode) { + // XXX アプリケーション寿命まで解放してないけど、まあいいか + mem = (uint8 *)malloc(16 * 1024); + return true; } - if (st.st_size > memlen) { - // 大きすぎるほうはワーニングだけでいいか - warnx("NVRAM \"%s\" is too large (ignore %d bytes)", - filename.c_str(), (int)st.st_size - memlen); + + // ファイルサイズを更新。 + // ver 0.2.3 までの NVRAM.DAT は 2040 バイトだったが、ver 0.2.4 以降は + // 2048 バイトになるため、ここでファイルがあって 2040 バイトなら 2048 + // バイトに拡張する。失敗したらここでは何もしない。 + filename = gMainApp.GetVMDir() + "NVRAM.DAT"; + std::string dispname = string_format("NVRAM \"%s\"", filename.c_str()); + if (stat(filename.c_str(), &st) == 0 && st.st_size == 2040) { + int fd; + fd = open(filename.c_str(), O_WRONLY | O_APPEND); + if (fd >= 0) { + std::array zerobuf {}; + write(fd, &zerobuf[0], zerobuf.size()); + close(fd); + stat(filename.c_str(), &st); + warnx("%s extended to %jd bytes", + dispname.c_str(), (intmax_t)st.st_size); + } } - mem = (uint8 *)mmap(NULL, memlen, PROT_READ | PROT_WRITE, - MAP_SHARED, fd, 0); - if (mem == MAP_FAILED) { - warn("NVRAM \"%s\" mmap failed", filename.c_str()); - close(fd); + // ファイルのロード + file.SetFilename(filename); + file.SetDispname(dispname); + mem = file.OpenCreate(2048); + if (mem == NULL) { return false; } - // レジスタ値固定オプション (パフォーマンス測定用) - force_fixed = gConfig->Get(".rtc-force-fixed").AsInt(); - if (force_fixed) { - putmsg("force_fixed"); - } return true; } -// RTC を初期化 -bool -MK48T02Device::InitRTC() +void +MK48T02Device::StartTime() { - // ホストの時刻を UTC で取得 - LoadHostRTC(false); + inherited::StartTime(); - return true; + // 内部カレンダをメモリに反映 + Store(reg.in); } // バイナリ表現を BCD 表現に変換する @@ -135,225 +160,336 @@ MK48T02Device::BCD2num(uint8 bcd) return (bcd >> 4) * 10 + (bcd & 0x0f); } -uint64 -MK48T02Device::Read(uint32 addr) +busdata +MK48T02Device::ReadPort(uint32 offset) { - uint32 an = addr; - uint32 data; + busdata data; - if (an < 2040) { - data = mem[an]; - putlog(3, "$%08x -> $%02x", addr, data); - } else { - // XXX ReadBit が立っていない時は何が読めるのか - switch (an - 2040) { - case 0: // コントロールレジスタ - data = reg.ctrl; - break; - case 1: // 秒 - data = reg.sec; - if (reg.stop) { - data |= MK48T02Clock::STOP; - } - break; - case 2: // 分 - data = reg.min; - break; - case 3: // 時 - data = reg.hour; - if (reg.kickstart) { - data |= MK48T02Clock::KICKSTART; - } - break; - case 4: // 曜日 - data = reg.wday; - break; - case 5: // 日 - data = reg.mday; - break; - case 6: // 月 - data = reg.mon; - break; - case 7: // 年 - data = reg.year; - break; - default: - __unreachable(); - } + data = mem[offset]; + putlog(3, "$%08x -> $%02x", mpu->GetPaddr(), data.Data()); - if (force_fixed) { - // パフォーマンス測定をする際に、RTC が実時間を返すと - // ゲストの状態が毎回異なることになるため、 - // いつ何時も同じ値を返すことで、世界から隔絶された - // VM を作り、パフォーマンス測定を安定させる。 - data = 3; - } - } + // XXX wait? + data |= BusData::Size1; return data; } -uint64 -MK48T02Device::Write(uint32 addr, uint32 data) +busdata +MK48T02Device::WritePort(uint32 offset, uint32 data) { - uint32 an = addr; + putlog(3, "$%08x <- $%02x", mpu->GetPaddr(), data); + + switch (offset - 2040) { + case 0: // コントロール + WriteCtrl(data); + break; + case 1: // 秒 (b7 は STOP BIT) + reg.stop = data & MK48T02Clock::STOP; + break; + case 2: // 分 + break; + case 3: // 時 (b7 は KICK START) + reg.kickstart = data & MK48T02Clock::KICKSTART; + break; + case 4: // 曜日 (b6 は Frequency Test (未実装)) + // データシートでは b7, b5, b4 は '0' とあるが実際には '1' が + // 書き込める。書き込んだ '1' が内部クロックの更新でどうなるか + // 未確認のため、とりあえず放置。 + reg.freqtest = data & MK48T02Clock::FREQTEST; + break; + case 5: // 日 + // データシートでは b7, b6 は '0' とあるが実際には b7 には '1' が + // 書き込める (b6 には書き込めない)。b7 に書き込んだ '1' が内部 + // クロックの更新でどうなるか未確認のため、とりあえずは放置。 + break; + case 6: // 月 + break; + case 7: // 年 + break; + default: + break; + } + + mem[offset] = data; + + // XXX wait? + busdata r = BusData::Size1; + return r; +} - if (an < 2040) { - putlog(3, "$%08x <- $%02x", addr, data); - mem[an] = data; +busdata +MK48T02Device::PeekPort(uint32 offset) +{ + return mem[offset]; +} + +bool +MK48T02Device::PokePort(uint32 offset, uint32 data) +{ + // 時計部分にはコントロールレジスタもあるので書き込み不可。 + if (offset >= 2040) { + return false; } else { - switch (an - 2040) { - case 0: // コントロール - WriteCtrl(data); - break; - case 1: // 秒 (b7 は STOP BIT) - reg.stop = (data & MK48T02Clock::STOP); - if ((reg.ctrl & MK48T02Clock::WRITE)) { - rtc.sec = BCD2num(data & 0x7f); - } - break; - case 2: // 分 - if ((reg.ctrl & MK48T02Clock::WRITE)) { - rtc.min = BCD2num(data & 0x7f); - } - break; - case 3: // 時 (b7 は KICK START) - reg.kickstart = (data & MK48T02Clock::KICKSTART); - if ((reg.ctrl & MK48T02Clock::WRITE)) { - rtc.hour = BCD2num(data & 0x3f); - } - break; - case 4: // 曜日 (b6 は Frequency Test (未実装)) - if ((reg.ctrl & MK48T02Clock::WRITE)) { - rtc.wday = data & 0x07; - } - break; - case 5: // 日 - if ((reg.ctrl & MK48T02Clock::WRITE)) { - rtc.mday = BCD2num(data & 0x3f); - } - break; - case 6: // 月 - if ((reg.ctrl & MK48T02Clock::WRITE)) { - rtc.mon = BCD2num(data & 0x1f); - } - break; - case 7: // 年 - if ((reg.ctrl & MK48T02Clock::WRITE)) { - rtc.year = BCD2num(data) + MK48T02Clock::YEAR_EPOCH; - } - break; - default: - __unreachable(); + if ((int32)data >= 0) { + mem[offset] = data; } + return true; } - return 0; } -uint64 -MK48T02Device::Peek(uint32 addr) +void +MK48T02Device::MonitorUpdate(Monitor *, TextScreen& screen) { - uint32 an = addr; - uint8 data; + uint32 addr; + uint32 v; + int y = 0; + + screen.Clear(); + + // BCD なので %x で表示する + screen.Print(0, y++, + "Internal: %04u[%02x]/%02x/%02x(%s) %02x:%02x:%02x.%1x", + DecodeYear(reg.in.year), reg.in.year, + reg.in.mon, reg.in.mday, wdays[reg.in.wday], + reg.in.hour, reg.in.min, reg.in.sec, (uint32)((cnt >> 1) & 0xf)); + + auto ex = Load(); + screen.Print(0, y++, + "Register: %04u[%02x]/%02x/%02x(%s) %02x:%02x:%02x", + DecodeYear(ex.year), ex.year, + ex.mon, ex.mday, wdays[ex.wday], + ex.hour, ex.min, ex.sec); + + screen.Print(0, y++, "TimeZone: %s", + use_localtime ? "Local" : "UTC"); + screen.Print(0, y++, "Epoch : %4u (Adjust Leap: %u)", + year_epoch, adjust_leap); + y++; + + addr = 0x800; + v = PeekPort(--addr); + screen.Print(0, y++, "$%3x: $%02x (year=%02x)", addr, v, v); + v = PeekPort(--addr); + screen.Print(0, y++, "$%3x: $%02x (mon =%02x)", addr, v, v); + v = PeekPort(--addr); + screen.Print(0, y++, "$%3x: $%02x (date=%02x)", addr, v, v); + v = PeekPort(--addr); + screen.Print(0, y++, "$%3x: $%02x (wday=%02x FT=%u)", + addr, v, (v & 0x07), (v & MK48T02Clock::FREQTEST) ? 1 : 0); + v = PeekPort(--addr); + screen.Print(0, y++, "$%3x: $%02x (hour=%02x KS=%u)", + addr, v, (v & 0x3f), (v & MK48T02Clock::KICKSTART) ? 1 : 0); + v = PeekPort(--addr); + screen.Print(0, y++, "$%3x: $%02x (min =%02x)", addr, v, (v & 0x7f)); + v = PeekPort(--addr); + screen.Print(0, y++, "$%3x: $%02x (sec =%02x ST=%u)", + addr, v, (v & 0x7f), (v & MK48T02Clock::STOP) ? 1 : 0); + v = PeekPort(--addr); + screen.Print(0, y++, "$%3x: $%02x (W=%u R=%u)", + addr, v, + (v & MK48T02Clock::WRITE) ? 1 : 0, + (v & MK48T02Clock::READ) ? 1 : 0); +} - if (an < 2040) { - return mem[an]; - } else { - switch (an - 2040) { - case 0: // コントロールレジスタ - return reg.ctrl; - case 1: // 秒 | STOP - data = reg.sec; - if (reg.stop) { - data |= MK48T02Clock::STOP; - } - return data; - case 2: // 分 - return reg.min; - case 3: // 時 - data = reg.hour; - if (reg.kickstart) { - data |= MK48T02Clock::KICKSTART; - } - return data; - case 4: // 曜日 - return reg.wday; - case 5: // 日 - return reg.mday; - case 6: // 月 - return reg.mon; - case 7: // 年 - return reg.year; - default: - __unreachable(); - } +// コントロールレジスタへの書き込み +void +MK48T02Device::WriteCtrl(uint32 data) +{ + uint8 old = mem[0x7f8]; + + bool write_fall = bit_falling(old, data, MK48T02Clock::WRITE); + + // ホールドしているかどうかは状態なのでエッジではない + hold = (data & (MK48T02Clock::READ | MK48T02Clock::WRITE)); + + if (write_fall) { + // WRITE ビットの立ち下がりエッジでメモリから内部カウンタへロード + reg.in = Load(); + + // 分周段は規定されていないが意味的にクリアするほうが良いだろう + cnt = 0; } } -bool -MK48T02Device::MonitorUpdate() +// NVRAM からカレンダをロード +MK48T02Clock::calendar +MK48T02Device::Load() const { - monitor.Clear(); + MK48T02Clock::calendar cal; - monitor.Print(0, 0, "WR:%d RD:%d", - (reg.ctrl & MK48T02Clock::WRITE) ? 1 : 0, - (reg.ctrl & MK48T02Clock::READ) ? 1 : 0); - monitor.Print(0, 1, "ST:%d KS:%d", - reg.stop, - reg.kickstart); - // XXX レジスタの値はラッチされるまで意味がないので - // どうしたものか。 - monitor.Print(10, 0, "%4d/%02d/%02d(%s)", - rtc.year, rtc.mon, rtc.mday, wdays[rtc.wday]); - monitor.Print(10, 1, "%02d:%02d:%02d", - rtc.hour, rtc.min, rtc.sec); + cal.sec = mem[0x7f9] & 0x7f; + cal.min = mem[0x7fa] & 0x7f; + cal.hour = mem[0x7fb] & 0x3f; + cal.wday = mem[0x7fc] & 0x07; + cal.mday = mem[0x7fd] & 0x3f; + cal.mon = mem[0x7fe] & 0x1f; + cal.year = mem[0x7ff]; - return true; + return cal; } -// コントロールレジスタへの書き込み +// カレンダを NVRAM にストア void -MK48T02Device::WriteCtrl(uint32 data) +MK48T02Device::Store(const MK48T02Clock::calendar& cal) { - reg.ctrl = data; + mem[0x7f9] = (mem[0x7f9] & ~0x7f) | cal.sec; + mem[0x7fa] = (mem[0x7fa] & ~0x7f) | cal.min; + mem[0x7fb] = (mem[0x7fb] & ~0x3f) | cal.hour; + mem[0x7fc] = (mem[0x7fc] & ~0x07) | cal.wday; + mem[0x7fd] = (mem[0x7fd] & ~0x3f) | cal.mday; + mem[0x7fe] = (mem[0x7fe] & ~0x1f) | cal.mon; + mem[0x7ff] = cal.year; +} - if ((reg.ctrl & MK48T02Clock::WRITE)) { - // WRITE ビットを立てると計時を止める - rtc.enable_sec = false; - } else { - // WRITE ビットを下ろすと計時を再開して - rtc.enable_sec = true; - // KickStart, FreqTest をゼロにする - reg.kickstart = false; - } - - if ((reg.ctrl & MK48T02Clock::READ)) { - // READ ビットを立てたところで計時をとめて(?) - // 値をレジスタにラッチ(?) - rtc.enable_sec = false; - reg.sec = num2BCD(rtc.sec); - reg.min = num2BCD(rtc.min); - reg.hour = num2BCD(rtc.hour); - reg.wday = rtc.wday; - reg.mday = num2BCD(rtc.mday); - reg.mon = num2BCD(rtc.mon); - reg.year = num2BCD(rtc.year - MK48T02Clock::YEAR_EPOCH); - } else { - // READ ビットを下ろすと計時を再開 - rtc.enable_sec = true; +uint +MK48T02Device::GetSec() const +{ + return BCD2num(reg.in.sec); +} + +void +MK48T02Device::SetSec(uint v) +{ + reg.in.sec = num2BCD(v); +} + +uint +MK48T02Device::GetMin() const +{ + return BCD2num(reg.in.min); +} + +void +MK48T02Device::SetMin(uint v) +{ + reg.in.min = num2BCD(v); +} + +uint +MK48T02Device::GetHour() const +{ + return BCD2num(reg.in.hour); +} + +void +MK48T02Device::SetHour(uint v) +{ + reg.in.hour = num2BCD(v); +} + +uint +MK48T02Device::GetWday() const +{ + // データシートの正規の値範囲は 1..7 だが 0 も問題なく保持できて、 + // NetBSD/luna68k, OpenBSD/luna88k は日付を日曜に変更する際には 0 を + // レジスタに書き込んでいる。そのためレジスタ値は 0 と 7 を日曜、 + // 1を月曜…という運用ということにする。 + // 戻り値は 0..6 (0が日曜)。 + + uint v = reg.in.wday; + if (v == 7) { + v = 0; } + return v; } -// 32Hz クロック入力 -// -// 32Hz ごとに呼ばれて、これが RTC の時計を進める処理をするところ。 -// RTC を実時間に同期して進めるモードなら実時間を計るスケジューラから、 -// RTC を仮想時間に同期して進めるモードなら VM スケジューラから呼ばれる。 -void -MK48T02Device::ClockIn() -{ - // 32Hz カウンタを進める。 - // 内部表現とデバイスから読み出せる値が同じなので、更新処理は不要。 - CountUp(); +void +MK48T02Device::SetWday(uint v) +{ + // 引数は 0..6 (0が日曜)、MK48T02 の正規の範囲は 1..7 で曜日との対応は + // 運用依存のようだ。NetBSD/luna68k, OpenBSD/luna88k は 0 を日曜、1 を + // 月曜…と扱っているが、MK48T02 の曜日レジスタは 7 の翌日 1 に繰り上がる + // 動作なので、ここでもレジスタには 1..7 (1が月曜、7が日曜) をセットする。 + if (v == 0) + v = 7; + reg.in.wday = v; +} + +uint +MK48T02Device::GetMday() const +{ + return BCD2num(reg.in.mday); +} + +void +MK48T02Device::SetMday(uint v) +{ + reg.in.mday = num2BCD(v); +} + +uint +MK48T02Device::GetMon() const +{ + return BCD2num(reg.in.mon); +} + +void +MK48T02Device::SetMon(uint v) +{ + reg.in.mon = num2BCD(v); +} + +uint +MK48T02Device::DecodeYear(uint bcd_year) const +{ + uint y = BCD2num(bcd_year); + y += year_epoch; + + // 1970年より前を 2000 年代とみなすのはシステム依存だが + // UNIX 系はみんなこう(?) + // XXX ここの 1970 と RTC エポックを一致させれば端折れるじゃん!と + // 思いついたのかしら? + if (y < 1970) { + y += 100; + } + return y; +} + +uint +MK48T02Device::GetYear() const +{ + // see DecodeYear() + return DecodeYear(reg.in.year); +} + +void +MK48T02Device::SetYear(uint v) +{ + // see DecodeYear() + v -= year_epoch; + reg.in.year = num2BCD(v % 100); +} + +uint +MK48T02Device::GetLeap() const +{ + // 補正項込みで計算 + return (BCD2num(reg.in.year) + adjust_leap) & 3; +} + +void +MK48T02Device::SetLeap(uint v) +{ + // nop + // MK48T02 ではうるう年カウンタはなく、西暦年でうるう年を判断している。 +} + +// 1Hz 仮想 RTC からのコール +// (分周段 0/32 秒でコールされる) +void +MK48T02Device::Tick1Hz() +{ + if (reg.stop) { + // 実機は電力消費を抑えるため発振器ごと止めるが、 + // 分周段が規定されていないので秒カウントを止めれば十分なはず + //nop + } else { + CountUpSec(); + } + + if (hold == false) { + // 秒の更新でメモリにコピーする (Datasheet p2) + Store(reg.in); + } } // 指定したアドレスからのゼロ終端文字列を返す @@ -363,9 +499,9 @@ MK48T02Device::PeekString(uint32 addr) c { std::string rv = ""; - uint32 an = addr & 0x7ff; - for (; mem[an]; an++) { - rv += mem[an]; + addr &= 0x7ff; + for (; mem[addr]; addr++) { + rv += mem[addr]; } return rv; } @@ -379,17 +515,31 @@ MK48T02Device::WriteString(uint32 addr, { int i = 0; - // XXX ここの文字列はゼロ終端なのかどうか - if (data.length() == 0 || data.length() > 15) { + // ここの文字列は16バイト未満なら一見ゼロ終端のようだが、 + // 16バイトならゼロ終端せず16文字とするフォーマット。 + + if (data.length() > 16) { return false; } addr &= 0x7ff; for (; i < data.length(); i++) { - Write(addr + i, data[i]); + mem[addr + i] = data[i]; } for (; i < 16; i++) { - Write(addr + i, 0); + mem[addr + i] = 0; } return true; } + +// 全域をゼロクリアする。 +// クリアするだけなので、その後何か書いたりチェックサムを計算したりする +// 必要はある。 +// (内蔵 ROM からの書き込み用) +void +MK48T02Device::ClearAll() +{ + for (int i = 0, sz = file.GetMemSize(); i < sz; i++) { + mem[i] = 0; + } +}