Annotation of nono/vm/mk48t02.cpp, revision 1.1.1.12

1.1       root        1: //
                      2: // nono
1.1.1.4   root        3: // Copyright (C) 2020 nono project
                      4: // Licensed under nono-license.txt
1.1       root        5: //
                      6: 
                      7: //
1.1.1.12! root        8: // NVRAM/RTC (MK48T02)
1.1       root        9: //
1.1.1.12! root       10: 
1.1       root       11: // SRAM 部分はビッグエンディアンのままメモリに置かれるので、
                     12: // 必要ならアクセス時に都度変換すること。
                     13: //
                     14: // 仕様は 0x45000000 から 2KB だが、
                     15: // 0x44000000 から 0x48000000 の手前まで 2KB 単位でミラーが見える。
                     16: 
1.1.1.12! root       17: #include "mk48t02.h"
        !            18: #include "bitops.h"
        !            19: #include "config.h"
        !            20: #include "mainapp.h"
        !            21: #include "mpu.h"
        !            22: #include <fcntl.h>
        !            23: #include <unistd.h>
        !            24: #include <sys/stat.h>
        !            25: #include <array>
        !            26: 
        !            27: // コンストラクタ
1.1       root       28: MK48T02Device::MK48T02Device()
1.1.1.9   root       29:        : inherited("MK48T02")
1.1       root       30: {
1.1.1.9   root       31:        AddAlias("NVRAM");
                     32:        AddAlias("RTC");
                     33: 
1.1       root       34:        devaddr = 0x45000000;
1.1.1.9   root       35: 
1.1.1.12! root       36:        // 今の所 UTC として使うケースだけのようだ。
1.1.1.10  root       37:        use_localtime = false;
                     38: 
1.1.1.12! root       39:        monitor.func = ToMonitorCallback(&MK48T02Device::MonitorUpdate);
1.1.1.10  root       40:        monitor.SetSize(40, 13);
1.1.1.9   root       41:        monitor.Regist(ID_MONITOR_MK48T02);
1.1       root       42: }
                     43: 
1.1.1.12! root       44: // デストラクタ
1.1       root       45: MK48T02Device::~MK48T02Device()
                     46: {
                     47: }
                     48: 
                     49: bool
                     50: MK48T02Device::Init()
                     51: {
1.1.1.12! root       52:        struct stat st;
        !            53: 
        !            54:        // NetBSD/luna68k, OpenBSD/luna88k ともに epoch を 1970年としている
        !            55:        // (2021/07 現在) 一方、4.4BSD (luna68k 版?) は 1900年としている。
        !            56:        // MK48T02 本来定義は 0 というか 1900 というか。DecodeYear() も参照。
        !            57:        const ConfigItem& item = gConfig->Find("rtc-epoch-year");
        !            58:        int year_epoch_val;
        !            59:        if (item.TryInt(&year_epoch_val) == false) {
        !            60:                item.Err();
1.1.1.10  root       61:                return false;
                     62:        }
1.1.1.12! root       63:        // 雑にチェック
        !            64:        if (year_epoch_val < 1900) {
        !            65:                item.Err("must be >= 1900");
        !            66:                return false;
        !            67:        }
        !            68:        year_epoch = (uint)year_epoch_val;
1.1.1.10  root       69: 
                     70:        // MK48T02 のデータシートには年には西暦年の下位2桁を設定するように
1.1.1.12! root       71:        // 指示されており、4.4BSD (luna68k) はこの通りになっている。
1.1.1.10  root       72:        // しかし CMU Mach luna88k, NetBSD/luna68k, OpenBSD/luna88k はいずれも
                     73:        // 西暦年から 1970 引いた値を設定するようになっている (2021/07 現在)。
                     74:        // このため、うるう年が2年ずれた状態になる。
                     75:        //
                     76:        // NetBSD で MK48T02 を採用している luna68k 以外の port は
                     77:        // ソースコード上の変数 year0 もしくは YEAR0 に
                     78:        //  newsmips, news68k: 1900 (データシート規定通り)
                     79:        //  sun3, sparc, mvme68k: 1968 (1968 はうるう年なので問題ない)
                     80:        // を採用しており、バグっていない。
                     81:        // この year0 は UNIX EPOCH とは直接関係がない RTC ハードウェアに
                     82:        // 依存する値なのだが、LUNA では混同したのだろうか?
                     83:        //
                     84:        // たぶんこのバグはもう修正されることはないだろうから、こちらで補正する
                     85:        // スイッチを用意した。
                     86: 
                     87:        int adjust_misused_epoch;
                     88:        adjust_misused_epoch = gConfig->Find("luna-adjust-misused-epoch").AsInt();
                     89:        if (adjust_misused_epoch) {
                     90:                // エポック年のうるう年カウンタ相当を補正項として持つ
                     91:                adjust_leap = year_epoch % 4;
                     92:        } else {
                     93:                adjust_leap = 0;
                     94:        }
                     95: 
1.1.1.12! root       96:        // ファイルサイズを更新。
        !            97:        // ver 0.2.3 までの NVRAM.DAT は 2040 バイトだったが、ver 0.2.4 以降は
        !            98:        // 2048 バイトになるため、ここでファイルがあって 2040 バイトなら 2048
        !            99:        // バイトに拡張する。失敗したらここでは何もしない。
1.1.1.3   root      100:        filename = gMainApp.GetVMDir() + "NVRAM.DAT";
1.1.1.12! root      101:        std::string dispname = string_format("NVRAM \"%s\"", filename.c_str());
        !           102:        if (stat(filename.c_str(), &st) == 0 && st.st_size == 2040) {
        !           103:                int fd;
        !           104:                fd = open(filename.c_str(), O_WRONLY | O_APPEND);
        !           105:                if (fd >= 0) {
        !           106:                        std::array<uint8, 8> zerobuf {};
        !           107:                        write(fd, &zerobuf[0], zerobuf.size());
        !           108:                        close(fd);
        !           109:                        stat(filename.c_str(), &st);
        !           110:                        warnx("%s extended to %jd bytes",
        !           111:                                dispname.c_str(), (intmax_t)st.st_size);
        !           112:                }
        !           113:        }
        !           114: 
        !           115:        // ファイルのロード
1.1.1.4   root      116:        file.SetFilename(filename);
1.1.1.12! root      117:        file.SetDispname(dispname);
        !           118:        mem = file.OpenCreate(2048);
1.1.1.4   root      119:        if (mem == NULL) {
1.1       root      120:                return false;
                    121:        }
                    122: 
1.1.1.12! root      123:        return inherited::Init();
        !           124: }
        !           125: 
        !           126: void
        !           127: MK48T02Device::StartTime()
        !           128: {
        !           129:        inherited::StartTime();
        !           130: 
        !           131:        // 内部カレンダをメモリに反映
        !           132:        Store(reg.in);
1.1       root      133: }
                    134: 
                    135: // バイナリ表現を BCD 表現に変換する
                    136: /*static*/ inline uint8
                    137: MK48T02Device::num2BCD(uint8 num)
                    138: {
                    139:        return ((num / 10) << 4) + (num % 10);
                    140: }
                    141: 
                    142: // BCD 表現をバイナリ表現に変換する
                    143: /*static*/ inline uint8
                    144: MK48T02Device::BCD2num(uint8 bcd)
                    145: {
                    146:        return (bcd >> 4) * 10 + (bcd & 0x0f);
                    147: }
                    148: 
                    149: uint64
1.1.1.7   root      150: MK48T02Device::Read(uint32 offset)
1.1       root      151: {
                    152:        uint32 data;
                    153: 
1.1.1.12! root      154:        data = mem[offset];
        !           155:        putlog(3, "$%08x -> $%02x", gMPU->GetPaddr(), data);
1.1       root      156: 
                    157:        return data;
                    158: }
                    159: 
                    160: uint64
1.1.1.7   root      161: MK48T02Device::Write(uint32 offset, uint32 data)
1.1       root      162: {
1.1.1.12! root      163:        putlog(3, "$%08x <- $%02x", gMPU->GetPaddr(), data);
1.1.1.10  root      164: 
1.1.1.12! root      165:        switch (offset - 2040) {
        !           166:         case 0:        // コントロール
        !           167:                WriteCtrl(data);
        !           168:                break;
        !           169:         case 1:        // 秒 (b7 は STOP BIT)
        !           170:                reg.stop = data & MK48T02Clock::STOP;
        !           171:                break;
        !           172:         case 2:        // 分
        !           173:                break;
        !           174:         case 3:        // 時 (b7 は KICK START)
        !           175:                reg.kickstart = data & MK48T02Clock::KICKSTART;
        !           176:                break;
        !           177:         case 4:        // 曜日 (b6 は Frequency Test (未実装))
        !           178:                // データシートでは b7, b5, b4 は '0' とあるが実際には '1' が
        !           179:                // 書き込める。書き込んだ '1' が内部クロックの更新でどうなるか
        !           180:                // 未確認のため、とりあえず放置。
        !           181:                reg.freqtest = data & MK48T02Clock::FREQTEST;
        !           182:                break;
        !           183:         case 5:        // 日
        !           184:                // データシートでは b7, b6 は '0' とあるが実際には b7 には '1' が
        !           185:                // 書き込める (b6 には書き込めない)。b7 に書き込んだ '1' が内部
        !           186:                // クロックの更新でどうなるか未確認のため、とりあえずは放置。
        !           187:                break;
        !           188:         case 6:        // 月
        !           189:                break;
        !           190:         case 7:        // 年
        !           191:                break;
        !           192:         default:
        !           193:                break;
1.1       root      194:        }
1.1.1.12! root      195: 
        !           196:        mem[offset] = data;
        !           197: 
1.1       root      198:        return 0;
                    199: }
                    200: 
                    201: uint64
1.1.1.7   root      202: MK48T02Device::Peek(uint32 offset)
1.1       root      203: {
1.1.1.12! root      204:        return mem[offset];
1.1       root      205: }
                    206: 
1.1.1.5   root      207: void
1.1.1.9   root      208: MK48T02Device::MonitorUpdate(Monitor *, TextScreen& screen)
1.1       root      209: {
1.1.1.10  root      210:        uint32 addr;
                    211:        uint32 v;
                    212:        int y = 0;
                    213: 
1.1.1.9   root      214:        screen.Clear();
1.1       root      215: 
1.1.1.10  root      216:        // BCD なので %x で表示する
                    217:        screen.Print(0, y++,
                    218:                "Internal: %04d[%02x]/%02x/%02x(%s) %02x:%02x:%02x.%1x",
                    219:                DecodeYear(reg.in.year), reg.in.year,
                    220:                reg.in.mon, reg.in.mday, wdays[reg.in.wday],
                    221:                reg.in.hour, reg.in.min, reg.in.sec, (uint32)((cnt >> 1) & 0xf));
1.1.1.12! root      222: 
        !           223:        auto ex = Load();
1.1.1.10  root      224:        screen.Print(0, y++,
                    225:                "Register: %04d[%02x]/%02x/%02x(%s) %02x:%02x:%02x",
1.1.1.12! root      226:                DecodeYear(ex.year), ex.year,
        !           227:                ex.mon, ex.mday, wdays[ex.wday],
        !           228:                ex.hour, ex.min, ex.sec);
        !           229: 
1.1.1.10  root      230:        screen.Print(0, y++, "TimeZone: %s",
                    231:                use_localtime ? "Local" : "UTC");
                    232:        screen.Print(0, y++, "Epoch   : %4d  (Adjust Leap: %d)",
                    233:                year_epoch, adjust_leap);
                    234:        y++;
                    235: 
                    236:        addr = 0x800;
                    237:        v = Peek(--addr);
                    238:        screen.Print(0, y++, "$%3x: $%02x (year=%02x)", addr, v, v);
                    239:        v = Peek(--addr);
                    240:        screen.Print(0, y++, "$%3x: $%02x (mon =%02x)", addr, v, v);
                    241:        v = Peek(--addr);
                    242:        screen.Print(0, y++, "$%3x: $%02x (date=%02x)", addr, v, v);
                    243:        v = Peek(--addr);
                    244:        screen.Print(0, y++, "$%3x: $%02x (wday=%02x  FT=%d)",
                    245:                addr, v, (v & 0x07), (v & MK48T02Clock::FREQTEST) ? 1 : 0);
                    246:        v = Peek(--addr);
                    247:        screen.Print(0, y++, "$%3x: $%02x (hour=%02x  KS=%d)",
                    248:                addr, v, (v & 0x3f), (v & MK48T02Clock::KICKSTART) ? 1 : 0);
                    249:        v = Peek(--addr);
                    250:        screen.Print(0, y++, "$%3x: $%02x (min =%02x)", addr, v, (v & 0x7f));
                    251:        v = Peek(--addr);
                    252:        screen.Print(0, y++, "$%3x: $%02x (sec =%02x  ST=%d)",
                    253:                addr, v, (v & 0x7f), (v & MK48T02Clock::STOP) ? 1 : 0);
                    254:        v = Peek(--addr);
                    255:        screen.Print(0, y++, "$%3x: $%02x (W=%d R=%d)",
                    256:                addr, v,
                    257:                (v & MK48T02Clock::WRITE) ? 1 : 0,
                    258:                (v & MK48T02Clock::READ)  ? 1 : 0);
1.1       root      259: }
                    260: 
                    261: // コントロールレジスタへの書き込み
                    262: void
                    263: MK48T02Device::WriteCtrl(uint32 data)
                    264: {
1.1.1.12! root      265:        uint8 old = mem[0x7f8];
1.1.1.10  root      266: 
1.1.1.12! root      267:        bool write_fall = bit_falling(old, data, MK48T02Clock::WRITE);
1.1       root      268: 
1.1.1.10  root      269:        // ホールドしているかどうかは状態なのでエッジではない
1.1.1.12! root      270:        hold = (data & (MK48T02Clock::READ | MK48T02Clock::WRITE));
1.1.1.10  root      271: 
                    272:        if (write_fall) {
1.1.1.12! root      273:                // WRITE ビットの立ち下がりエッジでメモリから内部カウンタへロード
        !           274:                reg.in = Load();
        !           275: 
1.1.1.10  root      276:                // 分周段は規定されていないが意味的にクリアするほうが良いだろう
                    277:                cnt = 0;
1.1       root      278:        }
                    279: }
                    280: 
1.1.1.12! root      281: // NVRAM からカレンダをロード
        !           282: MK48T02Clock::calendar
        !           283: MK48T02Device::Load() const
        !           284: {
        !           285:        MK48T02Clock::calendar cal;
        !           286: 
        !           287:        cal.sec = mem[0x7f9] & 0x7f;
        !           288:        cal.min = mem[0x7fa] & 0x7f;
        !           289:        cal.hour = mem[0x7fb] & 0x3f;
        !           290:        cal.wday = mem[0x7fc] & 0x07;
        !           291:        cal.mday = mem[0x7fd] & 0x3f;
        !           292:        cal.mon = mem[0x7fe] & 0x1f;
        !           293:        cal.year = mem[0x7ff];
        !           294: 
        !           295:        return cal;
        !           296: }
        !           297: 
        !           298: // カレンダを NVRAM にストア
        !           299: void
        !           300: MK48T02Device::Store(const MK48T02Clock::calendar& cal)
        !           301: {
        !           302:        mem[0x7f9] = (mem[0x7f9] & ~0x7f) | cal.sec;
        !           303:        mem[0x7fa] = (mem[0x7fa] & ~0x7f) | cal.min;
        !           304:        mem[0x7fb] = (mem[0x7fb] & ~0x3f) | cal.hour;
        !           305:        mem[0x7fc] = (mem[0x7fc] & ~0x07) | cal.wday;
        !           306:        mem[0x7fd] = (mem[0x7fd] & ~0x3f) | cal.mday;
        !           307:        mem[0x7fe] = (mem[0x7fe] & ~0x1f) | cal.mon;
        !           308:        mem[0x7ff] = cal.year;
        !           309: }
        !           310: 
1.1.1.10  root      311: uint
                    312: MK48T02Device::GetSec() const
                    313: {
                    314:        return BCD2num(reg.in.sec);
                    315: }
                    316: 
                    317: void
                    318: MK48T02Device::SetSec(uint v)
                    319: {
                    320:        reg.in.sec = num2BCD(v);
                    321: }
                    322: 
                    323: uint
                    324: MK48T02Device::GetMin() const
                    325: {
                    326:        return BCD2num(reg.in.min);
                    327: }
                    328: 
                    329: void
                    330: MK48T02Device::SetMin(uint v)
                    331: {
                    332:        reg.in.min = num2BCD(v);
                    333: }
                    334: 
                    335: uint
                    336: MK48T02Device::GetHour() const
                    337: {
                    338:        return BCD2num(reg.in.hour);
                    339: }
                    340: 
                    341: void
                    342: MK48T02Device::SetHour(uint v)
                    343: {
                    344:        reg.in.hour = num2BCD(v);
                    345: }
                    346: 
                    347: uint
                    348: MK48T02Device::GetWday() const
                    349: {
                    350:        // データシートの正規の値範囲は 1..7 だが 0 も問題なく保持できて、
                    351:        // NetBSD/luna68k, OpenBSD/luna88k は日付を日曜に変更する際には 0 を
                    352:        // レジスタに書き込んでいる。そのためレジスタ値は 0 と 7 を日曜、
                    353:        // 1を月曜…という運用ということにする。
                    354:        // 戻り値は 0..6 (0が日曜)。
                    355: 
                    356:        int v = reg.in.wday;
                    357:        if (v == 7) {
                    358:                v = 0;
                    359:        }
                    360:        return v;
                    361: }
                    362: 
                    363: void
                    364: MK48T02Device::SetWday(uint v)
                    365: {
                    366:        // 引数は 0..6 (0が日曜)、MK48T02 の正規の範囲は 1..7 で曜日との対応は
                    367:        // 運用依存のようだ。NetBSD/luna68k, OpenBSD/luna88k は 0 を日曜、1 を
                    368:        // 月曜…と扱っているが、MK48T02 の曜日レジスタは 7 の翌日 1 に繰り上がる
                    369:        // 動作なので、ここでもレジスタには 1..7 (1が月曜、7が日曜) をセットする。
                    370:        if (v == 0)
                    371:                v = 7;
                    372:        reg.in.wday = v;
                    373: }
                    374: 
                    375: uint
                    376: MK48T02Device::GetMday() const
                    377: {
                    378:        return BCD2num(reg.in.mday);
                    379: }
                    380: 
                    381: void
                    382: MK48T02Device::SetMday(uint v)
                    383: {
                    384:        reg.in.mday = num2BCD(v);
                    385: }
                    386: 
                    387: uint
                    388: MK48T02Device::GetMon() const
                    389: {
                    390:        return BCD2num(reg.in.mon);
                    391: }
                    392: 
                    393: void
                    394: MK48T02Device::SetMon(uint v)
                    395: {
                    396:        reg.in.mon = num2BCD(v);
                    397: }
                    398: 
                    399: uint
                    400: MK48T02Device::DecodeYear(uint bcd_year) const
                    401: {
                    402:        uint y = BCD2num(bcd_year);
                    403:        y += year_epoch;
                    404: 
                    405:        // 1970年より前を 2000 年代とみなすのはシステム依存だが
                    406:        // UNIX 系はみんなこう(?)
                    407:        // XXX ここの 1970 と RTC エポックを一致させれば端折れるじゃん!と
                    408:        //     思いついたのかしら?
                    409:        if (y < 1970) {
                    410:                y += 100;
                    411:        }
                    412:        return y;
                    413: }
                    414: 
                    415: uint
                    416: MK48T02Device::GetYear() const
                    417: {
                    418:        // see DecodeYear()
                    419:        return DecodeYear(reg.in.year);
                    420: }
                    421: 
                    422: void
                    423: MK48T02Device::SetYear(uint v)
                    424: {
                    425:        // see DecodeYear()
                    426:        v -= year_epoch;
                    427:        reg.in.year = num2BCD(v % 100);
                    428: }
                    429: 
                    430: uint
                    431: MK48T02Device::GetLeap() const
                    432: {
                    433:        // 補正項込みで計算
                    434:        return (BCD2num(reg.in.year) + adjust_leap) & 3;
                    435: }
                    436: 
                    437: void
                    438: MK48T02Device::SetLeap(uint v)
                    439: {
                    440:        // nop
                    441:        // MK48T02 ではうるう年カウンタはなく、西暦年でうるう年を判断している。
                    442: }
                    443: 
                    444: // 1Hz 仮想 RTC からのコール
1.1.1.12! root      445: // (分周段 0/32 秒でコールされる)
1.1.1.10  root      446: void
                    447: MK48T02Device::Tick1Hz()
                    448: {
                    449:        if (reg.stop) {
                    450:                // 実機は電力消費を抑えるため発振器ごと止めるが、
                    451:                // 分周段が規定されていないので秒カウントを止めれば十分なはず
                    452:                //nop
                    453:        } else {
                    454:                CountUpSec();
                    455:        }
                    456: 
                    457:        if (hold == false) {
1.1.1.12! root      458:                // 秒の更新でメモリにコピーする (Datasheet p2)
        !           459:                Store(reg.in);
1.1.1.10  root      460:        }
1.1       root      461: }
1.1.1.3   root      462: 
                    463: // 指定したアドレスからのゼロ終端文字列を返す
                    464: // (内蔵 ROM からのアクセス用)
                    465: std::string
                    466: MK48T02Device::PeekString(uint32 addr) const
                    467: {
                    468:        std::string rv = "";
                    469: 
1.1.1.7   root      470:        addr &= 0x7ff;
                    471:        for (; mem[addr]; addr++) {
                    472:                rv += mem[addr];
1.1.1.3   root      473:        }
                    474:        return rv;
                    475: }
                    476: 
                    477: // 指定したアドレスに文字列を書き込み、
                    478: // 16 バイトに満たないところはゼロでパディングする。
                    479: // 書き込めれば true を、書き込めなければ false を返す。
                    480: // (内蔵 ROM からの書き込み用)
                    481: bool
                    482: MK48T02Device::WriteString(uint32 addr, const std::string& data)
                    483: {
                    484:        int i = 0;
                    485: 
1.1.1.9   root      486:        // ここの文字列は16バイト未満なら一見ゼロ終端のようだが、
                    487:        // 16バイトならゼロ終端せず16文字とするフォーマット。
                    488: 
                    489:        if (data.length() > 16) {
1.1.1.3   root      490:                return false;
                    491:        }
                    492: 
                    493:        addr &= 0x7ff;
                    494:        for (; i < data.length(); i++) {
1.1.1.9   root      495:                mem[addr + i] = data[i];
1.1.1.3   root      496:        }
                    497:        for (; i < 16; i++) {
1.1.1.9   root      498:                mem[addr + i] = 0;
1.1.1.3   root      499:        }
                    500:        return true;
                    501: }
1.1.1.9   root      502: 
                    503: // 全域をゼロクリアする。
                    504: // クリアするだけなので、その後何か書いたりチェックサムを計算したりする
                    505: // 必要はある。
                    506: // (内蔵 ROM からの書き込み用)
                    507: void
                    508: MK48T02Device::ClearAll()
                    509: {
                    510:        for (int i = 0, sz = file.GetMemSize(); i < sz; i++) {
                    511:                mem[i] = 0;
                    512:        }
                    513: }

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.