|
|
1.1 root 1: //
2: // nono
1.1.1.3 root 3: // Copyright (C) 2020 nono project
4: // Licensed under nono-license.txt
1.1 root 5: //
6:
1.1.1.8 root 7: #include "mpu680x0.h"
1.1 root 8: #include "m68030mmu.h"
1.1.1.8 root 9: #include "mainbus.h"
1.1 root 10:
11: #define TIA (0)
12: #define TIB (1)
13: #define TIC (2)
14: #define TID (3)
15: #define FCL (4) // 便宜上ね
16: #define MAX_LV (5)
17:
18: // ディスクリプタタイプ
19: #define GET_DT(x) (x & 0x03)
20: #define DT_INVALID (0)
21: #define DT_PAGE (1)
22: #define DT_VALID4 (2)
23: #define DT_VALID8 (3)
24:
25: // ディスクリプタ
26: #define MMU_DESC_S (0x00000100)
27: #define MMU_DESC_CI (0x00000040)
1.1.1.4 root 28: #define MMU_DESC_M (0x00000010)
29: #define MMU_DESC_U (0x00000008)
1.1 root 30: #define MMU_DESC_WP (0x00000004)
31: #define MMU_DESC_DT (0x00000003)
32:
33: // エントリの状態
34: enum ATCtype {
35: INVALID,
36: NORMAL,
37: EARLY,
38: INDIRECT,
39: };
40:
41: //
42: // デバッグ表示
43: //
44:
45: // デバッグ表示のため値の表示に必要な桁数を返す
46: #define NIBBLE(x) ( (x) == 0 ? 1 : ((x) + 3) / 4 )
47:
1.1.1.6 root 48: // インデント付き string_format
49: static std::string indent_format(int indent, const char *fmt, ...)
50: __printflike(2, 3);
51: static std::string
52: indent_format(int indent, const char *fmt, ...)
1.1 root 53: {
54: va_list ap;
1.1.1.6 root 55: char *buf;
1.1 root 56:
57: va_start(ap, fmt);
1.1.1.6 root 58: vasprintf(&buf, fmt, ap);
1.1 root 59: va_end(ap);
60:
1.1.1.6 root 61: std::string rv(indent, ' ');
62: rv += buf;
63: free(buf);
1.1 root 64:
1.1.1.6 root 65: return rv;
66: }
1.1 root 67:
1.1.1.9 root 68: #define MMULOG(lv, indent, arg...) do { \
69: if (__predict_false(loglevel >= (lv))) { \
70: cpu->putlogf(lv, [&, lam_func = __func__] { \
71: (void)lam_func; \
72: return indent_format(indent, arg); \
73: }); \
74: } \
75: } while (0)
1.1 root 76:
77: // テーブルサーチ操作クラス
1.1.1.3 root 78: class m68030MMU final
1.1 root 79: {
80: public:
1.1.1.11 root 81: m68030MMU(MPU68030Device *, int loglevel_);
1.1.1.8 root 82: ~m68030MMU();
1.1 root 83:
84: // サーチを実行
1.1.1.10 root 85: void search(int level, busaddr laddr);
1.1 root 86:
87: // ATC にエントリを作成
88: void make_atc(m68030ATCLine *);
89:
90: // ATC にエントリを作成 (デバッガアクセス用)
1.1.1.9 root 91: busaddr make_atc_debugger() const;
1.1 root 92:
93: private:
94: // リミットチェック
95: bool limitcheck() const;
96:
97: // ディスクリプタを取得
98: bool fetch_desc(uint32, int);
99:
100: // ディスクリプタを表示用に (デバッグ用)
101: std::string sprintf_desc();
102:
103: // ディスクリプタタイプ文字列 (デバッグ用)
104: const char *dtname(uint32 dt);
105:
106: public:
1.1.1.9 root 107: ATCtype m_type {}; // 状態
108: bool m_s {}; // S ビット
109: bool m_wp {}; // WP ビット
110: bool m_ci {}; // CI ビット
111: bool m_m {}; // ページディスクリプタの M が立っているか
112: bool m_lim {}; // サーチ中にリミットを越えた
113: bool m_berr {}; // テーブルサーチ中にバスエラー
114: bool m_ptest {}; // PTEST[RW] 命令中なら真
115: bool m_debugger {}; // デバッガからのアクセスなら true にする
116: int x {}; // 段数 TIA〜TID, FCL
117: uint32 m_lastaddr {}; // 最後にアクセスしたディスクリプタアドレス(PTEST)
1.1.1.8 root 118: private:
1.1.1.9 root 119: int m_lv[MAX_LV] {};
120: uint32 m_desc1 {}; // ディスクリプタ
121: uint32 m_desc2 {}; // ロングディスクリプタの後半部
122: int m_descsize {}; // ディスクリプタサイズ (4 or 8)
123: int m_lastsize {}; // 前段のディスクリプタサイズ
1.1 root 124:
125: // デバッグ用文字列
126: static const char * const m_lvname[MAX_LV];
127:
1.1.1.9 root 128: int loglevel {};
129:
1.1.1.11 root 130: MPU68030Device *cpu;
1.1 root 131: };
132:
133: // 現在の TC:E、TT:E の状態に応じてアドレス変換の有無を切り替える。
134: // mmu_enable ならアドレス変換ありのバスアクセスを使う。see m68030bus.cpp
135: void
1.1.1.11 root 136: MPU68030Device::mmu_enable_changed()
1.1 root 137: {
138: bool enable;
139:
140: // TC、TT0、TT1 のいずれかが有効ならアドレス変換あり
141: enable = (mmu_tc.e || mmu_tt[0].e || mmu_tt[1].e);
142:
143: // 変化した時だけ
144: if (mmu_enable && !enable) {
145: // 無効にする
146: mmu_enable = false;
1.1.1.8 root 147: putlog(1, "MMU Translation Disabled");
1.1 root 148: } else if (!mmu_enable && enable) {
149: // 有効にする
150: mmu_enable = true;
1.1.1.8 root 151: putlog(1, "MMU Translation Enabled");
1.1 root 152: }
153: }
154:
155:
156: //
157: // レジスタクラス
158: //
159:
1.1.1.8 root 160: bool
1.1.1.11 root 161: MPU68030Device::SetSRP(uint32 h, uint32 l)
1.1 root 162: {
1.1.1.8 root 163: if ((h & m68030RP::DT_MASK) == 0) {
164: return false;
165: }
1.1 root 166: reg.srp.h = h & m68030RP::H_MASK;
167: reg.srp.l = l & m68030RP::L_MASK;
1.1.1.8 root 168: return true;
1.1 root 169: }
170:
1.1.1.8 root 171: bool
1.1.1.11 root 172: MPU68030Device::SetCRP(uint32 h, uint32 l)
1.1 root 173: {
1.1.1.8 root 174: if ((h & m68030RP::DT_MASK) == 0) {
175: return false;
176: }
1.1 root 177: reg.crp.h = h & m68030RP::H_MASK;
178: reg.crp.l = l & m68030RP::L_MASK;
1.1.1.8 root 179: return true;
1.1 root 180: }
181:
182: void
1.1.1.11 root 183: MPU68030Device::SetTT(int n, uint32 data)
1.1 root 184: {
185: uint32 lbase;
186: uint32 lmask;
187: uint32 sbase;
188: uint32 smask;
189:
1.1.1.10 root 190: // ハッシュから TT#n の成分を一旦クリアする。
191: if (mmu_tt[n].e) {
192: // 変更前の TT#n にマッチして、現在の TT#(1-n) にマッチしない
193: // アドレスブロックに対応する 4096 エントリをクリア。
1.1.1.12 root 194: for (int fc = 1; fc <= 6; fc++) {
1.1.1.10 root 195: auto *table = atc.fctables[fc];
196: if (table) {
197: busaddr laddr = busaddr(0) | busaddr::FC(fc);
198: for (int i = 0; i < 256; i++) {
199: // XXX R/W は?
200: if (mmu_tt[n].Match(laddr) &&
201: mmu_tt[1 - n].Match(laddr) == false)
202: {
203: uint32 baseidx = i << (24 - 12);
204: memset(&table->hash[baseidx],
205: m68030ATCTable::HASH_NONE, 4096);
206: }
207: laddr += 0x0100'0000;
208: }
209: }
210: }
211: }
212:
1.1 root 213: // マスクしてレジスタイメージにセット
214: reg.tt[n] = data & m68030TT::MASK;
215:
216: // data を分解してメンバにセット。
217: //
218: // 3 2 1 0
219: // 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
220: // +---------------+---------------+-+-+-+-+-+-+-+-+-+-----+-+-----+
221: // |LogicalAddrBase|LogicalAddrMask|E| |C|R|M| | FCB | | FCM |
222: // +---------------+---------------+-+-+-+-+-+-+-+-+-+-----+-+-----+
223:
224: mmu_tt[n].e = (data & m68030TT::E);
225: mmu_tt[n].ci = (data & m68030TT::CI);
226:
227: // 下位ロングワード
228: // ・lbase は比較先アドレスで、ff000000の位置。
229: // ・lmask はアドレスマスク。%1 が無視を表しているので NOT しておく。
230: //
231: // target AAAAAAAA xxxxxxxx xxxxxxxx xxxxxxxx : Aは論理アドレス
232: // ^ : xは不問
233: // lbase BBBBBBBB 00000000 00000000 00000000 : Bは論理アドレスベース
234: // &
235: // lmask MMMMMMMM 00000000 00000000 00000000 : Mは論理アドレスマスク
236: //
237: lbase = (data & m68030TT::LBASE_MASK);
238: lmask = (~data & m68030TT::LMASK_MASK) << 8;
239:
240: // 上位ロングワード
241: // ・E ビットは位置はどこでもいいので TT:E ビットの
242: // 位置をそのまま使用する。target 側のこの位置は必ずビットが落ちている
243: // ため base 側にビットを立てておくと必ず不一致になることを利用して、
244: // E ビットの評価も一度に行う。
245: // ・RW ビットは SSW の RW と比較するので、SSW_RW のビット位置を使う
1.1.1.8 root 246: // (0x00000040 の位置)。TT:RW も SSW_RW も Read が %1。
1.1 root 247: // ・RWM が %1 なら R/W は無視、RWM が %0 なら R/W の一致も検査するので
248: // RWM が %0 の時は RW のマスクビットを立てておく。
1.1.1.9 root 249: // ・FC は busaddr の配置に合わせて1ビット左シフトしたところを使う。
1.1 root 250: // ・FCMask は %1 が無視を表しているので NOT しておく。
251: //
1.1.1.9 root 252: // target 00000000 00000000 00000000 0R00FFFS : R はアクセス R/~W
1.1 root 253: // ^ : F はアクセス FC
1.1.1.9 root 254: // : S は FC2 のコピー
255: // base 00000000 00000000 E0000000 0R00FFF0 : R は期待する R/~W
1.1 root 256: // & : F は期待する FC
257: // : E は ~TT:E
1.1.1.9 root 258: // mask 00000000 00000000 10000000 0M00CCC0 : M は R/~W マスク
1.1 root 259: // : C は FC マスク
1.1.1.8 root 260: sbase = ~data & m68030TT::E;
261: sbase |= (data & m68030TT::RW) >> 3;
1.1.1.9 root 262: sbase |= (data & m68030TT::FCBASE_MASK) >> 3;
1.1 root 263: smask = m68030TT::E;
264: if ((data & m68030TT::RWM) == 0) {
1.1.1.11 root 265: smask |= M68030::SSW_RW;
1.1 root 266: }
1.1.1.9 root 267: smask |= (~data & m68030TT::FCMASK_MASK) << 1;
1.1 root 268:
269: mmu_tt[n].base = lbase | (((uint64)sbase) << 32);
270: mmu_tt[n].mask = lmask | (((uint64)smask) << 32);
271:
1.1.1.10 root 272: // 更新後にこの TT が有効ならハッシュも更新。
273: if (mmu_tt[n].e) {
274: // この TT#n にマッチするアドレスブロックに対応する 4096 エントリを
275: // TT 印にする。
1.1.1.12 root 276: for (uint fc = 1; fc <= 6; fc++) {
1.1.1.10 root 277: auto *table = atc.fctables[fc];
278: if (table) {
279: busaddr laddr = busaddr(0) | busaddr::FC(fc);
280: for (uint i = 0; i < 256; i++) {
281: // XXX R/W は?
282: if (mmu_tt[n].Match(laddr)) {
283: uint32 baseidx = i << (24 - 12);
284: memset(&table->hash[baseidx],
285: m68030ATCTable::HASH_TT, 4096);
286: }
287: laddr += 0x0100'0000;
288: }
289: }
1.1 root 290: }
291: }
292:
293: mmu_enable_changed();
294: }
295:
1.1.1.5 root 296: // アクセスがこの TT とマッチするか調べる。
1.1.1.9 root 297: // laddr の上位ワードはそのまま使える。
298: // 下位32ビットが論理アドレス (下24ビットは呼び出し側でマスクしなくてよい)。
1.1.1.5 root 299: // この TT が有効でアドレスがマッチすれば true を返す。
300: // (TT:E は演算に織り込んである、このすぐ上の SetTT() のコメント参照)
1.1 root 301: bool
1.1.1.9 root 302: m68030TT::Match(busaddr laddr) const
1.1 root 303: {
1.1.1.9 root 304: bool rv = (((laddr.Get() ^ base) & mask) == 0);
1.1 root 305: return rv;
306: }
307:
308: // 成功すれば true、MMU 構成例外が起きる場合は false を返す。
309: // 呼び出し側で例外を起こすこと。
310: bool
1.1.1.11 root 311: MPU68030Device::SetTC(uint32 data)
1.1 root 312: {
313: // マスクしてレジスタイメージにセット
314: reg.tc = data & m68030TC::MASK;
315:
316: // data を分解してメンバにセット
317: mmu_tc.Set(data);
318:
319: // 有効にする場合は一貫性を確認
320: if (mmu_tc.e) {
321: if (mmu_tc.Check() == false) {
322: // エラーなら E をクリアして MMU 構成例外。
323: // (例外処理自体は呼び出し側で行う)
1.1.1.9 root 324: mmu_tc.e = false;
1.1 root 325: mmu_enable_changed();
326: return false;
327: }
328: }
329:
330: // マスクをあらかじめ用意しておく
331: mmu_tc.MakeMask();
332:
1.1.1.10 root 333: // ATC でも PS を覚えておく必要がある。
334: for (int i = 0; i < countof(atc.tables); i++) {
335: auto *table = atc.tables[i].get();
336: table->ps4k = mmu_tc.ps - 12;
337: }
338:
1.1 root 339: // TC:E が変更されていればスイッチ更新
340: mmu_enable_changed();
341: return true;
342: }
343:
344: // TC レジスタへの書き込み値を m68030TC クラスにセットする。
345: void
346: m68030TC::Set(uint32 data)
347: {
348: e = data & TC_E;
349: sre = data & TC_SRE;
350: fcl = data & TC_FCL;
351: tid = (data) & 0x0f;
352: tic = (data >>= 4) & 0x0f;
353: tib = (data >>= 4) & 0x0f;
354: tia = (data >>= 4) & 0x0f;
355: is = (data >>= 4) & 0x0f;
356: ps = (data >>= 4) & 0x0f;
357: }
358:
359: // 現在の値が MMU 構成例外になるなら false を返す。
360: bool
361: m68030TC::Check() const
362: {
363: if (ps < 8)
364: return false;
365: if (tia == 0)
366: return false;
367: if (tib == 0 && (tic > 0 || tid > 0))
368: return false;
369: if (tic == 0 && tid > 0)
370: return false;
371: if (ps + is + tia + tib + tic + tid != 32)
372: return false;
373:
374: return true;
375: }
376:
377: // 論理マスク、ページマスクを作成。(テーブルサーチで参照される)
378: void
379: m68030TC::MakeMask()
380: {
381: // lmask (論理マスク) は TIA〜TID 部分のマスク。
382: // pgmask (ページマスク) は PS 部分のマスク。
383: //
384: // 例えば IS=$8, TIA=$3, TIB=$4, TIC=$4, TID=$0, PS=$d
385: // (X68030 IPL 内の MMU 判定のケース) なら
386: //
387: // 3 2 1 0
388: // 10987654 32109876 54321098 76543210
389: // +--------+--------+--------+--------+
390: // |IIIIIIII AAABBBBC CCCPPPPP PPPPPPPP|
391: // +--------+--------+--------+--------+
392: // lmask = 00000000 11111111 11100000 00000000
393: // pgmask = 00000000 00000000 00011111 11111111
394: lmask = (1 << (tia + tib + tic + tid)) - 1;
395: lmask <<= ps;
396: pgmask = (1 << ps) - 1;
397:
398: // アドレスを (IS,) TIA 〜 TID に分解。
399: //
400: // TIA=$C, TIB=$A, (TIC = TID = 0), PS=$A の場合以下のようにする。
401: // 使ってない TIC, TID については tix[TIC] == 0 で判断する。
402: //
403: // A B PS
404: // +----------------+--------------+--------------+
405: // $00A01A00 | 0000 0000 1010 | 0000 0001 10 | xx xxxx xxxx |
406: // +----------------+--------------+--------------+
407: // shift[TIA]=20 shift[TIB]=10
408: // mask[TIA]=0xfff mask[TIB]=0x3ff
1.1.1.10 root 409: uint sh = ps;
1.1 root 410: for (int i = TID; i >= TIA; i--) {
411: if (tix[i] > 0) {
412: shift[i] = sh;
413: mask[i] = (1 << tix[i]) - 1;
414: sh += tix[i];
415: }
416: }
417: }
418:
419: void
1.1.1.11 root 420: MPU68030Device::SetMMUSR(uint16 data)
1.1 root 421: {
422: reg.mmusr = data & m68030MMUSR::MASK;
423: }
424:
1.1.1.11 root 425: // リセット例外の CPU 固有部分。
426: void
427: MPU68030Device::ResetMMU()
428: {
429: SetTC(GetTC() & ~m68030TC::TC_E);
430: SetTT(0, GetTT(0) & ~m68030TT::E);
431: SetTT(1, GetTT(1) & ~m68030TT::E);
432: }
433:
1.1 root 434:
435: //
436: // ATC
437: //
1.1.1.10 root 438: // 今となっては独立したクラスである必要はなくなっているが、これを
1.1.1.11 root 439: // MPU68030Device にマージするとパフォーマンスが下がるので維持してある。
1.1.1.10 root 440: //
1.1 root 441:
442: // コンストラクタ
1.1.1.11 root 443: m68030ATC::m68030ATC(MPU68030Device *cpu_)
1.1.1.8 root 444: {
445: cpu = cpu_;
1.1.1.9 root 446:
1.1.1.10 root 447: tables[0].reset(new m68030ATCTable(cpu, 1));
448: tables[1].reset(new m68030ATCTable(cpu, 2));
449: tables[2].reset(new m68030ATCTable(cpu, 5));
450: tables[3].reset(new m68030ATCTable(cpu, 6));
451:
452: fctables[1] = tables[0].get();
453: fctables[2] = tables[1].get();
454: fctables[5] = tables[2].get();
455: fctables[6] = tables[3].get();
1.1.1.8 root 456: }
457:
458: // デストラクタ
459: m68030ATC::~m68030ATC()
1.1 root 460: {
461: }
462:
1.1.1.9 root 463: // ログレベルの設定。
464: // これは Object のオーバーライドではないが、同じ使い方をする。
465: void
466: m68030ATC::SetLogLevel(int loglevel_)
467: {
468: loglevel = loglevel_;
1.1.1.10 root 469: for (int i = 0; i < countof(tables); i++) {
470: auto *table = tables[i].get();
471: table->loglevel = loglevel_;
1.1.1.9 root 472: }
473: }
474:
1.1.1.10 root 475: // ATC をすべてフラッシュする。命令からコールされる
1.1 root 476: void
1.1.1.10 root 477: m68030ATC::flush()
1.1 root 478: {
1.1.1.10 root 479: for (int i = 0; i < countof(tables); i++) {
480: auto *table = tables[i].get();
481: table->Flush();
1.1 root 482: }
483: }
484:
1.1.1.10 root 485: // ATC の fc, mask が一致するエントリをフラッシュする。
486: // 命令からコールされる。
1.1 root 487: void
1.1.1.10 root 488: m68030ATC::flush(uint32 fc, uint32 mask)
1.1 root 489: {
1.1.1.12 root 490: for (uint i = 1; i <= 6; i++) {
1.1.1.10 root 491: if (((i ^ fc) & mask) == 0) {
492: // FC が一致したテーブルを全フラッシュ
493: auto *table = fctables[i];
494: if (table) {
495: table->Flush();
496: }
1.1 root 497: }
498: }
499: }
500:
1.1.1.10 root 501: // ATC の fc, mask, addr が一致するエントリをフラッシュする。
502: // 命令からコールされる。
1.1 root 503: void
1.1.1.10 root 504: m68030ATC::flush(uint32 fc, uint32 mask, uint32 addr)
1.1 root 505: {
1.1.1.12 root 506: for (uint i = 1; i <= 6; i++) {
1.1.1.10 root 507: if (((i ^ fc) & mask) == 0) {
508: // FC が一致したテーブルから addr が一致したエントリをフラッシュ
509: auto *table = fctables[i];
510: if (table) {
511: table->Flush(addr);
512: }
513: }
1.1 root 514: }
515: }
516:
517:
1.1.1.10 root 518: //
519: // ATC テーブル
520: //
1.1 root 521:
1.1.1.10 root 522: // アドレス変換の本来のフローはこんな感じだが、
523: //
524: // if (TT*.E || TC.E) {
525: // if (TT*.E が有効) {
526: // laddr,RW が TT[01] とマッチしたら、paddr = laddr,CI
527: // }
528: // if (TC.E が有効) {
529: // ATC とマッチしたら、paddr = atc[n].paddr,CI
530: // そうでなければ、 paddr = テーブルサーチ
531: // }
532: // } else {
533: // paddr = laddr (アドレス変換オフ)
534: // }
535: //
536: // ハッシュで一度に引く。
537: // switch (hash[laddr>>12]) {
538: // case HASH_TT:
539: // laddr,RW が TT[01] とマッチしたら、paddr = laddr,CI
540: // case HASH_ATC:
541: // paddr = atc[n].paddr,CI
542: // case HASH_NONE:
543: // TC.E なら paddr = テーブルサーチ
544: // そうでなければ、paddr = laddr (アドレス変換オフ)
545: // case HASH_SUB:
546: // PS が 4KB 未満だとハッシュの1エントリに2つ以上が混在するので、
547: // この場合は諦めて ATC を線形探索。通常起きないので性能無視。
548: // }
549:
550: // ATC テーブルは本来は 1つ(FC混合) x 22 ラインだが、
551: // 高速化のため FC別(4テーブル) x 22 ラインのモードを用意する。
552: // FC 混合版ではテーブルは tables[0] のみで、FC(1,0,2) は busaddr の
553: // 34-32 ビット部分として laddr と一緒に検索キーにする。
554: // FC 分離版ではテーブルは fctables[fc] で検索キーは laddr(下位32ビット部分)。
555: //
556: // ハッシュなのでライン間の順序関係はないが、LRU で古いのを捨てる時や
557: // PS<4K の時に前から探索する時のために、age を使った順序管理をする。
558: // ラインを使用するたびに age に ++latest_age を代入しておくことで、
559: // 順序が必要な時には age の大きい順にすれば最近使った順になる。
560: // 32 ビット符号なし整数が折り返すと事故るがたぶんそれより高い頻度で
561: // フラッシュが起きるんじゃないかな…
1.1 root 562:
1.1.1.10 root 563: // コンストラクタ
1.1.1.11 root 564: m68030ATCTable::m68030ATCTable(MPU68030Device *cpu_, uint fc_)
1.1 root 565: {
1.1.1.10 root 566: cpu = cpu_;
567: fc = fc_;
1.1 root 568:
1.1.1.10 root 569: std::fill(hash.begin(), hash.end(), HASH_NONE);
1.1 root 570:
1.1.1.10 root 571: latest_age = 0;
572: Flush();
1.1 root 573: }
574:
1.1.1.10 root 575: // デストラクタ
576: m68030ATCTable::~m68030ATCTable()
1.1 root 577: {
578: }
579:
1.1.1.10 root 580: // この ATC テーブルのエントリをすべてフラッシュする。
581: void
582: m68030ATCTable::Flush()
1.1 root 583: {
1.1.1.10 root 584: for (int i = 0; i < LINES; i++) {
585: Invalidate(&line[i]);
1.1.1.12 root 586: line[i].age = 0;
1.1.1.10 root 587: }
1.1.1.12 root 588: latest_age = 0;
1.1 root 589: }
590:
1.1.1.10 root 591: // この ATC テーブルから addr が一致するエントリをすべてフラッシュする。
592: void
593: m68030ATCTable::Flush(uint32 addr)
1.1 root 594: {
1.1.1.10 root 595: for (int i = 0; i < LINES; i++) {
596: m68030ATCLine *a = &line[i];
597: if (a->GetTag() == addr) {
598: Invalidate(a);
1.1 root 599: }
600: }
601: }
602:
1.1.1.10 root 603: // この要素を先頭に移動。
604: inline void
605: m68030ATCTable::MoveHead(m68030ATCLine *a)
1.1 root 606: {
1.1.1.10 root 607: a->age = ++latest_age;
1.1.1.12 root 608:
609: if (__predict_false(a->age == 0)) {
610: // latest_age が1周したので、一旦全部リセット。
611: for (int i = 0; i < LINES; i++) {
612: line[i].age = 0;
613: }
614: a->age = ++latest_age;
615: }
1.1 root 616: }
617:
1.1.1.10 root 618: // このテーブルのうち最も古い要素を差し出す。
1.1 root 619: m68030ATCLine *
1.1.1.10 root 620: m68030ATCTable::RemoveTail()
1.1 root 621: {
1.1.1.10 root 622: m68030ATCLine *a = NULL;
623: uint32 min = (uint32)~0;
624: uint32 minidx = 0;
1.1 root 625:
1.1.1.10 root 626: // 最後に使われたの (あるいは空き) を探す。
627: for (int i = 0; i < LINES; i++) {
628: if (line[i].IsInvalid()) {
1.1.1.12 root 629: return &line[i];
1.1 root 630: }
1.1.1.10 root 631: if (line[i].age < min) {
632: min = line[i].age;
1.1 root 633: minidx = i;
634: }
635: }
1.1.1.10 root 636: if (a == NULL) {
637: a = &line[minidx];
1.1 root 638: }
639:
1.1.1.10 root 640: // ハッシュから削除。
641: Invalidate(a);
1.1 root 642:
643: return a;
644: }
645:
1.1.1.10 root 646: // a からハッシュを作成する。
1.1 root 647: void
1.1.1.12 root 648: m68030ATCTable::MakeHash(const m68030ATCLine *a)
1.1 root 649: {
1.1.1.10 root 650: uint32 addr4k = a->GetLAddr() >> 12;
651: int idx = a - &line[0];
652:
653: if (__predict_true(ps4k == 0)) {
654: hash[addr4k] = idx;
655: } else if (ps4k > 0) {
656: for (int i = 0; i < (1 << ps4k); i++) {
657: hash[addr4k + i] = idx;
658: }
659: } else {
660: hash[addr4k] = HASH_SUB;
1.1 root 661: }
662: }
663:
1.1.1.10 root 664: // ATC a を無効にする。すでに無効なら何もしない。
1.1 root 665: void
1.1.1.10 root 666: m68030ATCTable::Invalidate(m68030ATCLine *a)
1.1 root 667: {
1.1.1.10 root 668: if (a->IsValid()) {
669: // 有効ならハッシュから削除。
670: if (__predict_true(ps4k == 0)) {
671: uint32 addr4k = a->GetLAddr() >> 12;
672: hash[addr4k] = HASH_NONE;
673: } else if (ps4k > 0) {
674: // PS>4KB
675: uint32 addr4k = a->GetLAddr() >> 12;
676: for (int i = 0; i < (1U << ps4k); i++) {
677: hash[addr4k + i] = HASH_NONE;
678: }
679: } else {
680: // PS<4KB、自分が抜けることでこの枠が空くかどうか
681: InvalidateSub(a);
1.1 root 682: }
1.1.1.10 root 683:
684: a->Invalidate();
1.1 root 685: }
686: }
687:
1.1.1.10 root 688: // PS<4KB の場合に aa が抜けることでこの addr4k の枠が空けばハッシュから削除。
1.1 root 689: void
1.1.1.10 root 690: m68030ATCTable::InvalidateSub(const m68030ATCLine *aa)
1.1 root 691: {
1.1.1.10 root 692: const uint32 MASK_4K = 0xfffff000 | m68030ATCLine::INVALID;
693: uint32 target_laddr = aa->GetTag() & MASK_4K;
694: bool in_use = false;
695:
696: for (int i = 0; i < LINES; i++) {
697: auto *a = &line[i];
698: // 自分自身は今から削除予定なので除く。
699: if (a == aa) {
700: continue;
701: }
702: // それ以外で有効なものがあるか。
703: if ((a->GetTag() & MASK_4K) == target_laddr) {
704: in_use = true;
705: break;
1.1 root 706: }
707: }
708:
1.1.1.10 root 709: // 誰もいなくなれば削除。
710: if (in_use == false) {
711: hash[target_laddr >> 12] = HASH_NONE;
1.1 root 712: }
713: }
714:
1.1.1.10 root 715: // laddr を ATC から探す。見つからなければ NULL を返す。
716: m68030ATCLine *
717: m68030ATCTable::FindSub(const busaddr laddr)
1.1 root 718: {
1.1.1.10 root 719: uint32 addr = laddr.Addr() & cpu->mmu_tc.lmask;
720:
721: // 同じエントリはないはずなので、順序通りに探さなくてもいいはず。
722: for (int i = 0; i < LINES; i++) {
723: auto *a = &line[i];
724: if (a->GetTag() == addr) {
725: return a;
1.1 root 726: }
727: }
1.1.1.10 root 728: return NULL;
1.1 root 729: }
1.1.1.10 root 730:
731: // テーブルサーチを行う。
732: // テーブルという用語がぶつかっているので MMU Search にしてある。
733: m68030ATCLine *
734: m68030ATCTable::MMUSearch(busaddr laddr)
1.1 root 735: {
1.1.1.10 root 736: m68030ATCLine *a = RemoveTail();
737: a->tag = laddr.Addr() & cpu->mmu_tc.lmask;
738:
739: m68030MMU mmu(cpu, loglevel);
740: mmu.search(7, laddr);
741: mmu.make_atc(a);
742:
743: MakeHash(a);
744: return a;
1.1 root 745: }
746:
1.1.1.10 root 747: // デバッグ表示。
748: std::string
749: m68030ATCTable::LineToStr(const m68030ATCLine *a) const
1.1 root 750: {
1.1.1.10 root 751: uint32 laddr = a->GetLAddr();
752:
753: if (!a->IsBusError()) {
754: uint32 paddr = a->GetPAddr();
755: uint32 ci = a->IsCInhibit();
756: uint32 wp = a->IsWProtect();
757: uint32 mod = a->IsModified();
758: return indent_format(1,
759: "ATC: match %u:$%08x -> $%08x CI=%u WP=%u M=%u",
760: fc, laddr, paddr, ci, wp, mod);
761: } else {
762: return indent_format(1,
763: "ATC: match %u:$%08x -> BusErr",
764: fc, laddr);
1.1 root 765: }
766: }
767:
768:
769: //
770: // テーブルサーチ
771: //
772:
773: // コンストラクタ
1.1.1.11 root 774: m68030MMU::m68030MMU(MPU68030Device *cpu_, int loglevel_)
1.1 root 775: {
1.1.1.8 root 776: cpu = cpu_;
1.1.1.9 root 777: loglevel = loglevel_;
1.1 root 778:
779: m_type = INVALID;
780: x = -1;
781: }
782:
1.1.1.8 root 783: // デストラクタ
784: m68030MMU::~m68030MMU()
785: {
786: }
787:
1.1 root 788: // テーブルサーチのメイン部分。
789: // m_type および ATC 作成に必要な情報を埋めてから帰る。
790: // ATC は帰った先で作成する。
791: void
1.1.1.10 root 792: m68030MMU::search(int level, busaddr laddr)
1.1 root 793: {
794: const char *rpname;
1.1.1.10 root 795: uint32 addr;
796: uint32 fc;
1.1 root 797: int dt;
798: bool is_super;
799:
1.1.1.10 root 800: addr = laddr.Addr();
801: fc = laddr.GetFC();
1.1 root 802:
1.1.1.10 root 803: is_super = (fc & 0x04);
1.1 root 804:
805: // アドレスを (IS,) TIA .. TID に分解。
806: //
807: // TIA=$C, TIB=$A, (TIC = TID = 0), PS=$A の場合以下のようにする。
808: // 使ってない TIC, TID については cpu->mmu_tc->{tic,tid} == 0 で判断する。
809: //
810: // A B PS
811: // +----------------+--------------+--------------+
812: // $00A01A00 | 0000 0000 1010 | 0000 0001 10 | xx xxxx xxxx |
813: // +----------------+--------------+--------------+
814: // shift[TIA] =20 shift[TIB] =10
815: // lv[TIA].idx=0xA lv[TIB].idx=0x6
816: //
817: for (int i = TID; i >= TIA; i--) {
818: if (cpu->mmu_tc.tix[i] > 0) {
819: uint32 mask = cpu->mmu_tc.mask[i];
1.1.1.10 root 820: m_lv[i] = (addr >> cpu->mmu_tc.shift[i]) & mask;
1.1 root 821: }
822: }
1.1.1.10 root 823: m_lv[FCL] = fc;
1.1 root 824:
1.1.1.8 root 825: cpu->putlogf(3, [&] {
1.1 root 826: std::string buf;
827:
1.1.1.10 root 828: buf = indent_format(2, "search: laddr=$%08x ->", addr);
1.1 root 829: if (cpu->mmu_tc.is > 0)
1.1.1.10 root 830: buf += string_format(" IS=%u", cpu->mmu_tc.is);
1.1 root 831: if (cpu->mmu_tc.fcl)
1.1.1.10 root 832: buf += string_format(" FC=%u", m_lv[FCL]);
1.1 root 833: for (int i = TIA; i <= TID; i++) {
834: if (cpu->mmu_tc.tix[i] > 0) {
1.1.1.10 root 835: buf += string_format(" %s=%u($%0*x)",
1.1 root 836: m_lvname[i], cpu->mmu_tc.tix[i],
837: NIBBLE(cpu->mmu_tc.tix[i]), m_lv[i]);
838: }
839: }
1.1.1.6 root 840: return buf;
841: });
1.1 root 842:
1.1.1.9 root 843: // この辺に図9-26 の初期化だがコンストラクタで初期化済み。
1.1 root 844:
845: // ここから図9-25。
846:
847: // 使用するルートポインタを決定
1.1.1.12 root 848: if (__predict_true(cpu->mmu_tc.sre) && is_super) {
1.1 root 849: rpname = "SRP";
850: m_desc1 = cpu->GetSRPh();
851: m_desc2 = cpu->GetSRPl();
852: } else {
853: rpname = "CRP";
854: m_desc1 = cpu->GetCRPh();
855: m_desc2 = cpu->GetCRPl();
856: }
857:
858: // ルートポインタの DT をチェック
859: dt = GET_DT(m_desc1);
1.1.1.9 root 860: MMULOG(3, 2, "%s: %s dt=%s", lam_func, rpname, dtname(dt));
1.1.1.12 root 861: if (__predict_true(dt == DT_VALID4)) {
862: // 有効 4 バイト。基本的にはこれしか使われないので優先。
863: m_descsize = 4;
864: m_lastsize = 8;
865: } else if (dt == DT_VALID8) { // 有効 (8バイト)
866: m_descsize = 8;
867: m_lastsize = 8;
868: } else if (dt == DT_PAGE) { // ページディスクリプタ
1.1 root 869: m_type = EARLY;
870: return;
1.1.1.12 root 871: } else {
872: // 無効は設定できないはずなので、ここには来ないはず。
873: PANIC("%s.DT==Invalid", rpname);
1.1 root 874: }
875:
876: // 必要ならファンクションコード・ルックアップ
1.1.1.12 root 877: if (__predict_false(cpu->mmu_tc.fcl)) {
1.1 root 878: x = FCL;
1.1.1.8 root 879: // たぶん検索レベルを1つ消費するはず。未確認。
880: level--;
881:
1.1.1.9 root 882: MMULOG(3, 2, "%s: %s tableaddr=$%08x idx=$%x", lam_func,
1.1 root 883: m_lvname[x], m_desc2, m_lv[x]);
884:
885: // ディスクリプタをフェッチ
886: if (fetch_desc(m_desc2, m_lv[x]) == false) {
887: return;
888: }
889:
890: // ディスクリプタタイプをチェック
891: dt = GET_DT(m_desc1);
892: switch (dt) {
893: case DT_INVALID: // 無効
894: m_type = INVALID;
895: return;
896:
897: case DT_PAGE: // ページディスクリプタ
898: m_type = EARLY;
899: return;
900:
901: case DT_VALID4: // 有効4バイト
902: case DT_VALID8: // 有効8バイト
903: m_lastsize = m_descsize;
904: m_descsize = 1 << dt;
905: break;
906: }
907: }
908:
909: // TIx のテーブルサーチに入る。図9-25 の下半分。
910:
1.1.1.8 root 911: for (x = TIA; x < level; x++) {
1.1 root 912: // リミットチェック
1.1.1.12 root 913: if (__predict_false(m_lastsize == 8) && limitcheck() == false) {
1.1 root 914: m_type = INVALID;
1.1.1.9 root 915: m_lim = true;
1.1 root 916: return;
917: }
918:
919: // テーブルアドレス
920: uint32 tableaddr;
921: tableaddr = (m_lastsize == 4) ? m_desc1 : m_desc2;
922: tableaddr &= 0xfffffff0;
1.1.1.9 root 923: MMULOG(3, 2, "%s: %s tableaddr=$%08x idx=$%x", lam_func,
1.1 root 924: m_lvname[x], tableaddr, m_lv[x]);
925:
926: // ディスクリプタをフェッチ
927: if (fetch_desc(tableaddr, m_lv[x]) == false) {
928: return;
929: }
930:
931: // ディスクリプタタイプをチェック
932: dt = GET_DT(m_desc1);
933: switch (dt) {
934: case DT_INVALID: // 無効
935: m_type = INVALID;
936: return;
937:
938: case DT_PAGE: // ページディスクリプタ
939: if (x == TID) {
940: m_type = NORMAL;
941: } else {
942: if (cpu->mmu_tc.tix[x + 1] == 0) {
943: m_type = NORMAL;
944: } else {
945: m_type = EARLY;
946: }
947: }
948: return;
949:
950: case DT_VALID4: // 有効4バイト
951: case DT_VALID8: // 有効8バイト
952: m_lastsize = m_descsize;
953: m_descsize = 1 << dt;
954:
1.1.1.12 root 955: if (__predict_false(x == TID || cpu->mmu_tc.tix[x + 1] == 0)) {
1.1 root 956: // 間接
957: m_type = INDIRECT;
958:
959: // ディスクリプタをフェッチ
960: tableaddr = (m_lastsize == 4) ? m_desc1 : m_desc2;
961: tableaddr &= 0xfffffff0;
962: if (fetch_desc(tableaddr, 0) == false) {
963: return;
964: }
965: if (GET_DT(m_desc1) != DT_PAGE) {
966: m_type = INVALID;
967: }
968: return;
969: }
970: // 次段のサーチを繰り返す
1.1.1.8 root 971: break;
1.1 root 972: }
973: }
1.1.1.8 root 974:
975: // レベル上限にひっかかった場合、x は過ぎているので一つ戻しておく。
976: x--;
1.1 root 977: }
978:
979: // リミットチェックを実行。図9-28。
980: // 無効のケースに落ちたら false を返す。
981: bool
982: m68030MMU::limitcheck() const
983: {
1.1.1.8 root 984: uint16 limit = (m_desc1 >> 16) & 0x7fff;
1.1 root 985:
1.1.1.8 root 986: // 最上位ビットが L/U
987: if ((int32)m_desc1 < 0) {
988: if (m_lv[x] < limit) {
1.1.1.9 root 989: MMULOG(3, 2, "%s: $%04x < $%04x", lam_func, m_lv[x], limit);
1.1.1.8 root 990: return false;
991: }
992: } else {
993: if (m_lv[x] > limit) {
1.1.1.9 root 994: MMULOG(3, 2, "%s: $%04x > $%04x", lam_func, m_lv[x], limit);
1.1.1.8 root 995: return false;
996: }
1.1 root 997: }
998: return true;
999: }
1000:
1001: // ディスクリプタ中のフラグの更新 (を予約する)
1002: #define UPDATE_DESC(flag) do { \
1003: if ((m_desc1 & (flag)) == 0) { \
1004: m_desc1 |= (flag); \
1005: do_write = true; \
1006: } \
1007: } while (0)
1008:
1009: // ディスクリプタのフェッチと、ヒストリおよびステータスの更新。
1010: // 図9-29。
1011: // ディスクリプタを取得できれば true、
1012: // ATC エントリの作成に向かう場合は false。
1013: bool
1014: m68030MMU::fetch_desc(uint32 tableaddr, int idx)
1015: {
1016: uint32 descaddr;
1017: bool do_write;
1018: uint dt;
1019:
1020: // ディスクリプタのアドレスを計算。ここだけマニュアルの説明が雑すぎ。
1021: // インダイレクトなら idx = 0 でコールしてあるので、
1022: // tableaddr がそのまま PA になるという寸法。
1023: descaddr = tableaddr + idx * m_descsize;
1.1.1.10 root 1024: MMULOG(3, 2, "%s: descsize=%u descaddr=$%08x", lam_func,
1.1 root 1025: m_descsize, descaddr);
1026:
1027: // バスエラー発生フラグをクリアしておく
1028: m_berr = false;
1029:
1030: // ディスクリプタを取得
1.1.1.12 root 1031: if (__predict_false(m_debugger)) {
1.1 root 1032: // デバッガアクセス
1033: uint64 data;
1.1.1.10 root 1034: data = cpu->mainbus->Peek4(descaddr);
1.1 root 1035: if ((int64)data < 0) {
1036: goto buserr;
1037: }
1038: m_desc1 = data;
1039:
1040: if (m_descsize == 8) {
1.1.1.10 root 1041: data = cpu->mainbus->Peek4(descaddr + 4);
1.1 root 1042: if ((int64)data < 0) {
1043: goto buserr;
1044: }
1045: m_desc2 = data;
1046: }
1047: } else {
1.1.1.9 root 1048: busdata bd;
1.1 root 1049:
1.1.1.11 root 1050: bd = cpu->read_phys_4(descaddr);
1.1.1.12 root 1051: if (__predict_false(bd.IsBusErr())) {
1.1 root 1052: goto buserr;
1053: }
1.1.1.9 root 1054: m_desc1 = bd.Data();
1.1 root 1055:
1.1.1.12 root 1056: if (__predict_false(m_descsize == 8)) {
1.1.1.11 root 1057: bd = cpu->read_phys_4(descaddr + 4);
1.1.1.9 root 1058: if (bd.IsBusErr()) {
1.1 root 1059: goto buserr;
1060: }
1.1.1.9 root 1061: m_desc2 = bd.Data();
1.1 root 1062: }
1.1.1.8 root 1063:
1064: m_lastaddr = descaddr;
1.1 root 1065: }
1066:
1067: // ディスクリプタタイプをチェック
1068: // DT は必ず最初のロングワードの下位2ビット。
1069: do_write = false;
1070: dt = GET_DT(m_desc1);
1.1.1.9 root 1071: MMULOG(3, 2, "%s: desc=%s dt=%s", lam_func,
1.1 root 1072: sprintf_desc().c_str(), dtname(dt));
1073: switch (dt) {
1074: case DT_INVALID: // 無効
1075: return true;
1076:
1077: case DT_PAGE: // ページディスクリプタ
1078: // U ビットを更新
1079: UPDATE_DESC(MMU_DESC_U);
1080:
1081: // 書き込み操作なら M を更新
1082: if (cpu->bus.IsWrite() && !m_ptest) {
1083: UPDATE_DESC(MMU_DESC_M);
1084: }
1085: // 図にはないけど、ATC 更新のためと PTEST のために
1086: // ページディスクリプタの M ビットを記録。
1087: if ((m_desc1 & MMU_DESC_M))
1.1.1.9 root 1088: m_m = true;
1.1 root 1089: break;
1090:
1091: case DT_VALID4: // 有効4バイト
1092: case DT_VALID8: // 有効8バイト
1093: // U ビットを更新
1094: UPDATE_DESC(MMU_DESC_U);
1095: break;
1.1.1.12 root 1096:
1097: default:
1098: __unreachable();
1.1 root 1099: }
1100:
1101: // デバッガ自身がアドレス変換を実施している時は書き込みは行わない
1.1.1.12 root 1102: if (__predict_false(m_debugger)) {
1.1 root 1103: do_write = false;
1104: }
1105:
1106: // ライトライクルの実行
1107: if (do_write) {
1.1.1.11 root 1108: busdata bd = cpu->write_phys_4(descaddr, m_desc1);
1.1.1.12 root 1109: if (__predict_false(bd.IsBusErr())) {
1.1 root 1110: goto buserr;
1111: }
1112: // バス動作正常終了
1.1.1.9 root 1113: MMULOG(3, 2, "%s: desc=%s updated", lam_func, sprintf_desc().c_str());
1.1 root 1114: }
1115:
1.1.1.9 root 1116: // ステータス更新 (OR していく)
1.1 root 1117: // XXX 実際には CI はページディスクリプタにしかないけど
1.1.1.9 root 1118: m_wp |= (m_desc1 & MMU_DESC_WP);
1119: m_ci |= (m_desc1 & MMU_DESC_CI);
1120: if (__predict_false(m_descsize == 8)) {
1121: m_s |= (m_desc1 & MMU_DESC_S);
1.1 root 1122: }
1123:
1124: return true;
1125:
1126: // バスエラー
1127: buserr:
1128: m_type = INVALID;
1129: m_berr = true;
1130: return false;
1131: }
1132:
1133: // ATC エントリを作成。実行系用。
1134: // 図9-27 だが雑すぎ。
1135: void
1136: m68030MMU::make_atc(m68030ATCLine *a)
1137: {
1138: std::string buf;
1139: uint32 paddr;
1140:
1141: a->data = 0;
1142: switch (m_type) {
1143: case INVALID: // 無効
1144: a->data |= m68030ATCLine::BUSERROR;
1.1.1.9 root 1145: MMULOG(3, 2, "%s: type=INVALID -> BusErr", lam_func);
1.1 root 1146: return;
1147:
1148: case NORMAL: // ノーマル
1149: case INDIRECT: // 間接
1150: paddr = (m_descsize == 4) ? m_desc1 : m_desc2;
1.1.1.9 root 1151: MMULOG(3, 2, "%s: type=NORMAL paddr=$%08x", lam_func,
1152: paddr & 0xffffff00);
1.1 root 1153: break;
1154:
1155: case EARLY: // アーリーターミネーション
1156: paddr = (m_descsize == 4) ? m_desc1 : m_desc2;
1.1.1.9 root 1157: buf = string_format("type=EARLY paddr=$%08x", paddr & 0xffffff00);
1.1 root 1158:
1159: // 残ってるビットも足して返す
1160: for (int i = x + 1; i <= TID; i++) {
1161: if (cpu->mmu_tc.tix[i] > 0) {
1162: paddr |= m_lv[i] << cpu->mmu_tc.shift[i];
1163: buf += string_format(" +%s=%08x", m_lvname[i], paddr);
1164: }
1165: }
1.1.1.9 root 1166: MMULOG(3, 2, "%s: %s", lam_func, buf.c_str());
1.1 root 1167: break;
1168: default:
1.1.1.10 root 1169: PANIC("corrupted m_type=%u", (uint)m_type);
1.1 root 1170: }
1171:
1172: // PS の部分は反映しない
1173: a->paddr = paddr & cpu->mmu_tc.lmask;
1174:
1.1.1.9 root 1175: if (m_ci)
1176: a->data |= m68030ATCLine::CINHIBIT;
1.1 root 1177: if (m_wp)
1178: a->data |= m68030ATCLine::WPROTECT;
1179: if (m_m)
1180: a->data |= m68030ATCLine::MODIFIED;
1181: }
1182:
1183: // make_atc() と同様にテーブルサーチの結果から物理ページアドレスを返す。
1.1.1.9 root 1184: // バスエラーになるケースなら BusErr を返す。
1.1 root 1185: // デバッガアクセスなので、VM に一切干渉しない。
1.1.1.9 root 1186: busaddr
1187: m68030MMU::make_atc_debugger() const
1.1 root 1188: {
1.1.1.10 root 1189: busaddr paddr;
1.1 root 1190:
1191: switch (m_type) {
1192: case INVALID: // 無効
1.1.1.10 root 1193: paddr = BusAddr::BusErr;
1.1 root 1194: break;
1195:
1196: case NORMAL: // ノーマル
1197: case INDIRECT: // 間接
1.1.1.10 root 1198: {
1199: uint32 descaddr;
1200: descaddr = (m_descsize == 4) ? m_desc1 : m_desc2;
1201: descaddr &= 0xffffff00;
1202: paddr = busaddr(descaddr);
1.1 root 1203: break;
1.1.1.10 root 1204: }
1.1 root 1205:
1206: case EARLY: // アーリーターミネーション
1.1.1.10 root 1207: {
1208: uint32 descaddr;
1209: descaddr = (m_descsize == 4) ? m_desc1 : m_desc2;
1210: descaddr &= 0xffffff00;
1.1 root 1211: // 残ってるビットも足して返す
1212: for (int i = x + 1; i <= TID; i++) {
1213: if (cpu->mmu_tc.tix[i] > 0) {
1.1.1.10 root 1214: descaddr |= m_lv[i] << cpu->mmu_tc.shift[i];
1.1 root 1215: }
1216: }
1.1.1.10 root 1217: paddr = busaddr(descaddr);
1.1 root 1218: break;
1.1.1.10 root 1219: }
1.1.1.9 root 1220:
1.1 root 1221: default:
1.1.1.10 root 1222: PANIC("corrupted m_type=%u", (uint)m_type);
1.1 root 1223: }
1.1.1.9 root 1224:
1.1.1.10 root 1225: paddr |= BusAddr::TableSearched;
1.1 root 1226: return paddr;
1227: }
1228:
1229: // ディスクリプタ表示用の文字列を返す。
1230: // m_descsize によって m_desc1, m_desc2 を適切に文字列にする。
1231: std::string
1232: m68030MMU::sprintf_desc()
1233: {
1234: std::string buf;
1235:
1236: if (m_descsize == 4) {
1237: buf = string_format("%08x", m_desc1);
1238: } else {
1239: buf = string_format("%08x_%08x", m_desc1, m_desc2);
1240: }
1241:
1242: // フラグの出現条件がややこしいので注意
1243: int dt = GET_DT(m_desc1);
1244: buf += string_format(" (%s%s%s%c%c)",
1245: ((m_descsize == 4) ? "x" : ((m_desc1 & MMU_DESC_S) ? "S" : "-")),
1246: ((dt != DT_PAGE) ? "x" : ((m_desc1 & MMU_DESC_CI) ? "C" : "-")),
1247: ((dt != DT_PAGE) ? "x" : ((m_desc1 & MMU_DESC_M) ? "M" : "-")),
1248: (m_desc1 & MMU_DESC_U) ? 'U' : '-',
1249: (m_desc1 & MMU_DESC_WP) ? 'W' : '-'
1250: );
1251:
1252: return buf;
1253: }
1254:
1255: // ディスクリプタタイプ文字列 (デバッグ用)
1256: const char *
1257: m68030MMU::dtname(uint32 dt)
1258: {
1259: static char buf[32];
1260: switch (dt) {
1261: case DT_INVALID: return "invalid";
1.1.1.4 root 1262: case DT_PAGE: return "page";
1.1 root 1263: case DT_VALID4: return "valid4";
1264: case DT_VALID8: return "valid8";
1265: }
1.1.1.10 root 1266: snprintf(buf, sizeof(buf), "(invalid DT %u)", dt);
1.1 root 1267: return buf;
1268: }
1269:
1270: // デバッグ用文字列
1271: const char * const m68030MMU::m_lvname[MAX_LV] = {
1272: "TIA",
1273: "TIB",
1274: "TIC",
1275: "TID",
1276: "FCL", // この順ではないが、便宜上ここにおいてある
1277: };
1278:
1279: //
1280: // アドレス変換
1281: //
1282:
1.1.1.9 root 1283: // 論理アドレスが TT0/TT1 どちらかと一致すれば (bus.ci を更新して) true を返す。
1.1 root 1284: // 一致しなければ false を返す。
1.1.1.8 root 1285: bool
1.1.1.11 root 1286: MPU68030Device::CheckTT()
1.1 root 1287: {
1288: bool ci;
1289: int match;
1290:
1291: match = 0;
1292: ci = false;
1293: for (int i = 0; i < 2; i++) {
1.1.1.8 root 1294: const m68030TT& tt = mmu_tt[i];
1.1.1.9 root 1295: if (tt.Match(bus.laddr)) {
1.1 root 1296: // 一致した
1297: match += i + 1;
1298: ci |= tt.ci != 0;
1.1.1.10 root 1299: putlog(3, " %s: TT%u match", __func__, i);
1.1 root 1300: }
1301: }
1302:
1303: // 一致したら PA <- LA
1304: if (match != 0) {
1.1.1.9 root 1305: putlog(3, "%s: result %08x (match TT%s)", __func__,
1.1.1.10 root 1306: bus.laddr.Addr(),
1.1 root 1307: (match == 3) ? "0 and TT1" : ((match == 1) ? "0" : "1"));
1.1.1.9 root 1308: bus.ci = ci;
1.1 root 1309: return true;
1310: }
1311: return false;
1312: }
1313:
1.1.1.10 root 1314: // リード/フェッチ用のアドレス変換を行う。
1.1 root 1315: // 変換できれば true、バスエラーなら false を返す。
1316: bool
1.1.1.11 root 1317: MPU68030Device::TranslateRead()
1.1 root 1318: {
1.1.1.10 root 1319: m68030ATCLine *a;
1320: busaddr& laddr = bus.laddr;
1321: uint32 addr = laddr.Addr();
1322: uint32 fc = laddr.GetFC();
1.1.1.9 root 1323:
1.1 root 1324: // 状態表示
1.1.1.9 root 1325: putlog(3, "%s: enter %c:$%08x pc=%c:$%08x", __func__,
1.1.1.10 root 1326: (laddr.IsSuper() ? 'S' : 'U'),
1327: addr,
1.1.1.8 root 1328: IsSuper() ? 'S' : 'U',
1329: ppc);
1.1 root 1330:
1.1.1.10 root 1331: m68030ATCTable *table = atc.fctables[fc];
1332: uint8 n = table->hash[addr >> 12];
1333: if (__predict_true((int8)n >= 0)) {
1334: // ATC #(n)
1335: table->atcstat.total++;
1336: a = &table->line[n];
1.1.1.12 root 1337: } else if (n == m68030ATCTable::HASH_NONE) {
1338: if (__predict_true(mmu_tc.e)) {
1339: // TC.E 有効で対応なしなら、テーブルサーチ。
1.1.1.10 root 1340: table->atcstat.total++;
1.1.1.12 root 1341: table->atcstat.miss++;
1342: a = table->MMUSearch(laddr);
1.1.1.10 root 1343: } else {
1.1.1.12 root 1344: // TC.E 無効。(total もカウントしない)
1345: return true;
1346: }
1347: } else if (n == m68030ATCTable::HASH_TT) {
1348: // TT。CheckTT で外れたかどうかはカウントしない。ほぼ 0 なので。
1349: table->atcstat.total++;
1350: table->atcstat.tthit++;
1351: return CheckTT();
1352: } else {
1353: // PS<4KB でマッチしたので再検査。
1354: table->atcstat.total++;
1355: a = table->FindSub(laddr);
1356: if (a == NULL) {
1357: table->atcstat.miss++;
1358: a = table->MMUSearch(laddr);
1.1 root 1359: }
1360: }
1361:
1.1.1.10 root 1362: // ATC a でマッチした
1363: table->MoveHead(a);
1364: putlog(3, "%s", table->LineToStr(a).c_str());
1365: if (__predict_false(a->IsBusError())) {
1366: return false;
1367: }
1368: bus.paddr.ChangeAddr(a->GetPAddr() | (addr & mmu_tc.pgmask));
1369: bus.ci = a->IsCInhibit();
1.1 root 1370: return true;
1371: }
1372:
1373: // ライト用のアドレス変換を行う。
1374: // 変換できれば true、バスエラーなら false を返す。
1375: bool
1.1.1.11 root 1376: MPU68030Device::TranslateWrite()
1.1 root 1377: {
1.1.1.10 root 1378: m68030ATCLine *a;
1379: busaddr& laddr = bus.laddr;
1380: uint32 addr = laddr.Addr();
1381: uint32 fc = laddr.GetFC();
1.1.1.9 root 1382:
1.1 root 1383: // 状態表示
1.1.1.9 root 1384: putlog(3, "%s: enter %c:$%08x pc=%c:$%08x", __func__,
1.1.1.10 root 1385: (laddr.IsSuper() ? 'S' : 'U'),
1386: addr,
1.1.1.8 root 1387: IsSuper() ? 'S' : 'U',
1388: ppc);
1.1 root 1389:
1.1.1.10 root 1390: m68030ATCTable *table = atc.fctables[fc];
1391: uint8 n = table->hash[addr >> 12];
1392: if (__predict_true((int8)n >= 0)) {
1393: // ATC #(n)
1394: table->atcstat.total++;
1395: a = &table->line[n];
1.1.1.12 root 1396: } else if (n == m68030ATCTable::HASH_NONE) {
1397: if (__predict_true(mmu_tc.e)) {
1398: // TC.E 有効で対応なしなら、テーブルサーチ。
1.1.1.10 root 1399: table->atcstat.total++;
1.1.1.12 root 1400: goto tablesearch;
1.1.1.10 root 1401: } else {
1.1.1.12 root 1402: // TC.E 無効。(total もカウントしない)
1403: return true;
1.1 root 1404: }
1.1.1.12 root 1405: } else if (n == m68030ATCTable::HASH_TT) {
1406: // TT。CheckTT で外れたかどうかはカウントしない。ほぼ 0 なので。
1407: table->atcstat.total++;
1408: table->atcstat.tthit++;
1409: return CheckTT();
1410: } else {
1411: // PS<4KB でマッチしたので再検査。
1412: table->atcstat.total++;
1413: a = table->FindSub(laddr);
1414: if (a == NULL) {
1415: goto tablesearch;
1416: }
1417: }
1418:
1419: // ライトアクセスなので、WP でなく Modified == 0 なら
1420: // Modified を立てるため、このエントリを破棄して改めて
1421: // テーブルサーチを実行する。
1422: if (a->IsWProtect() == false && a->IsModified() == false) {
1423: table->Invalidate(a);
1424: tablesearch:
1425: table->atcstat.miss++;
1426: a = table->MMUSearch(laddr);
1.1 root 1427: }
1428:
1.1.1.10 root 1429: // ATC a でマッチした
1430: table->MoveHead(a);
1.1 root 1431:
1.1.1.10 root 1432: if (__predict_false(a->IsBusError())) {
1433: return false;
1434: }
1435: if (__predict_false(a->IsWProtect())) {
1436: // Write アクセスで WP が立ってたらバスエラー
1437: putlog(3, "%s: result Buserr (ATC:WP)", __func__);
1438: return false;
1.1 root 1439: }
1440:
1.1.1.10 root 1441: putlog(3, "%s", table->LineToStr(a).c_str());
1442: bus.paddr.ChangeAddr(a->GetPAddr() | (addr & mmu_tc.pgmask));
1443: bus.ci = a->IsCInhibit();
1.1 root 1444: return true;
1445: }
1446:
1447: // ピーク用のアドレス変換を行う。
1.1.1.9 root 1448: // 変換できれば対応する物理アドレスを、バスエラーなら BusErr を返す。
1449: // テーブルサーチを行ったら TableSearched ビットを立てる。
1450: busaddr
1.1.1.11 root 1451: MPU68030Device::TranslatePeek(busaddr laddr)
1.1 root 1452: {
1.1.1.10 root 1453: const m68030ATCLine *a;
1.1.1.9 root 1454: busaddr paddr;
1.1.1.10 root 1455: uint32 addr = laddr.Addr();
1456: uint32 fc = laddr.GetFC();
1.1 root 1457:
1458: // 状態表示
1.1.1.12 root 1459: putlog(5, "%s: enter %c:$%08x", __func__,
1.1.1.10 root 1460: (laddr.IsSuper() ? 'S' : 'U'),
1461: addr);
1.1 root 1462:
1.1.1.10 root 1463: m68030ATCTable *table = atc.fctables[fc];
1464: uint8 n = table->hash[addr >> 12];
1.1.1.12 root 1465: // PS<4KB のケースを先に解決する。
1466: if (__predict_false(n == m68030ATCTable::HASH_SUB)) {
1467: a = table->FindSub(laddr);
1468: if (a) {
1469: n = a - &table->line[0];
1470: } else {
1471: n = m68030ATCTable::HASH_NONE;
1472: }
1473: }
1.1.1.10 root 1474: if (__predict_true((int8)n >= 0)) {
1475: // ATC #(n)
1476: a = &table->line[n];
1477: paddr = busaddr(a->GetPAddr());
1478: paddr |= addr & mmu_tc.pgmask;
1.1.1.12 root 1479: putlog(5, "%s: result $%08x (match atc)", __func__, paddr.Addr());
1.1.1.10 root 1480: return paddr;
1.1.1.12 root 1481: } else if (n == m68030ATCTable::HASH_NONE) {
1482: if (__predict_true(mmu_tc.e)) {
1483: // TC.E 有効で対応なしなら、こっそりテーブルサーチ。
1484:
1485: // mmu.search() はさすがに bus を使うので事前に退避。
1486: m68kbus backup = bus;
1487:
1488: m68030MMU mmu(this, loglevel);
1489: mmu.m_debugger = true;
1490: mmu.search(7, laddr);
1491: paddr = mmu.make_atc_debugger();
1492: // リストア
1493: bus = backup;
1494:
1495: if (paddr.IsBusErr()) {
1496: // TC が有効だがヒットしなかったらバスエラー
1497: putlog(5, "%s: result BusErr", __func__);
1.1.1.10 root 1498: } else {
1.1.1.12 root 1499: paddr |= addr & mmu_tc.pgmask;
1500: putlog(5, "%s: result $%08x (table search)",
1.1.1.10 root 1501: __func__, paddr.Addr());
1502: }
1.1.1.12 root 1503: return paddr;
1.1.1.9 root 1504: } else {
1.1.1.12 root 1505: // TC.E 無効
1506: paddr = laddr;
1507: putlog(5, "%s: result $%08x (no translation)",
1508: __func__, paddr.Addr());
1509: return paddr;
1510: }
1511: } else {
1512: // TT をこっそりチェック。
1513: for (int i = 0; i < 2; i++) {
1514: m68030TT& tt = mmu_tt[i];
1515: if (tt.Match(laddr)) {
1516: // 一致した。
1517: // CI はここでは使わないため、一致したことだけわかればよい
1518: putlog(5, "%s: result $%08x (match tt)",
1519: __func__, paddr.Addr());
1520: return paddr;
1.1.1.10 root 1521: }
1.1.1.9 root 1522: }
1.1.1.12 root 1523: return BusAddr::BusErr;
1.1 root 1524: }
1525: }
1526:
1527: //
1528: // 命令
1529: //
1530:
1.1.1.8 root 1531: // ir2 の下位5ビットから FC を返す。
1.1 root 1532: // 不当パターンなら C++ の例外をスローする。
1.1.1.8 root 1533: uint32
1.1.1.11 root 1534: MPU68030Device::get_fc()
1.1 root 1535: {
1.1.1.8 root 1536: uint fcfield = ir2 & 0x1f;
1.1 root 1537:
1538: if (fcfield == 0) {
1.1.1.9 root 1539: return reg.GetSFC();
1.1 root 1540: } else if (fcfield == 1) {
1.1.1.9 root 1541: return reg.GetDFC();
1.1 root 1542: } else if (fcfield < 0x08) {
1543: // break;
1544: } else if (fcfield < 0x10) {
1.1.1.8 root 1545: return reg.D[fcfield & 7] & 7;
1.1 root 1546: } else if (fcfield < 0x18) {
1547: return fcfield & 7;
1548: }
1549: // 不当命令
1.1.1.8 root 1550: throw M68K::EXCEP_ILLEGAL;
1.1 root 1551: }
1552:
1553: // MMU 不当命令 (2ワード目で確定)
1.1.1.8 root 1554: void
1.1.1.11 root 1555: MPU68030Device::mmu_op_illg2()
1.1 root 1556: {
1.1.1.8 root 1557: putlog(1, "MMU illegal instruction %04x %04x", ir, ir2);
1558: Exception(M68K::EXCEP_FLINE);
1.1 root 1559: }
1560:
1561: // PFLUSH fc,#mask 命令 (EA を持たない方)
1562: void
1.1.1.11 root 1563: MPU68030Device::mmu_op_pflush()
1.1 root 1564: {
1565: uint32 mask;
1566: uint32 fc;
1567:
1.1.1.8 root 1568: mask = (ir2 >> 5) & 7;
1569: fc = get_fc();
1570: atc.flush(fc, mask);
1.1 root 1571: }
1572:
1573: // PFLUSH fc,#mask,ea 命令
1574: void
1.1.1.11 root 1575: MPU68030Device::mmu_op_pflush_ea()
1.1 root 1576: {
1577: uint32 ea;
1578: uint32 mask;
1579: uint32 fc;
1580:
1.1.1.8 root 1581: ea = cea_copro();
1.1.1.12 root 1582: ea &= mmu_tc.lmask;
1.1.1.8 root 1583: mask = (ir2 >> 5) & 7;
1584: fc = get_fc();
1585: atc.flush(fc, mask, ea);
1.1 root 1586: }
1587:
1588: // PLOADR/PLOADW 命令
1589: void
1.1.1.11 root 1590: MPU68030Device::mmu_op_pload()
1.1 root 1591: {
1592: uint32 ea;
1593: uint32 fc;
1.1.1.10 root 1594: busaddr laddr;
1.1 root 1595:
1.1.1.8 root 1596: if ((ir2 & 0x01e0) != 0) {
1597: mmu_op_illg2();
1.1 root 1598: return;
1599: }
1.1.1.8 root 1600: ea = cea_copro();
1601: fc = get_fc();
1.1 root 1602:
1603: // 実効アドレスを論理アドレスとする ATC を作成。
1604: // ってこれでいいのか?
1.1.1.8 root 1605: if ((ir2 & 0x0200)) {
1.1.1.10 root 1606: laddr = busaddr(ea) | busaddr::FC(fc) | BusAddr::R;
1.1 root 1607: } else {
1.1.1.10 root 1608: laddr = busaddr(ea) | busaddr::FC(fc) | BusAddr::W;
1609: }
1610:
1611: m68030ATCTable *table = atc.fctables[fc];
1.1.1.12 root 1612: m68030ATCLine *a;
1.1.1.10 root 1613: uint8 n = table->hash[laddr.Addr() >> 12];
1.1.1.12 root 1614: // PS<4KB のケースを先に解決する。
1615: if (__predict_false(n == m68030ATCTable::HASH_SUB)) {
1616: a = table->FindSub(laddr);
1617: if (a) {
1618: n = a - &table->line[0];
1.1.1.10 root 1619: } else {
1.1.1.12 root 1620: n = m68030ATCTable::HASH_NONE;
1.1.1.10 root 1621: }
1622: }
1.1.1.12 root 1623: if ((int8)n >= 0) {
1624: // すでにエントリがある場合
1625: if (laddr.IsWrite()) {
1626: // 命令が ploadw でエントリが WProtect==0 && Modified==0 なら、
1627: // 一回破棄して書き込み用に作り直す。
1628: a = &table->line[n];
1629: if (a->IsWProtect() == false && a->IsModified() == false) {
1630: table->Invalidate(a);
1631: table->MMUSearch(laddr);
1632: }
1633: }
1634: } else if (n == m68030ATCTable::HASH_NONE) {
1.1.1.10 root 1635: if (__predict_true(mmu_tc.e)) {
1636: // TC.E 有効で対応なしなら、テーブルサーチを行って
1637: // ATC を作成。
1.1.1.12 root 1638: table->MMUSearch(laddr);
1.1.1.10 root 1639: } else {
1640: // TC.E 無効なら何もしない?
1641: }
1642: } else if (n == m68030ATCTable::HASH_TT) {
1643: // TT の領域なら何もしてはいけない。
1.1 root 1644: }
1645: }
1646:
1647: // PTESTR/PTESTW 命令
1648: void
1.1.1.11 root 1649: MPU68030Device::mmu_op_ptest()
1.1 root 1650: {
1.1.1.8 root 1651: uint level = (ir2 >> 10) & 7;
1.1.1.10 root 1652: busaddr rw = (ir2 & 0x0200) ? BusAddr::R : BusAddr::W;
1.1.1.8 root 1653: uint rn = (ir2 >> 5) & 0xf;
1.1 root 1654: uint32 ea;
1655: uint32 fc;
1656: uint16 mmusr;
1657:
1.1.1.8 root 1658: ea = cea_copro();
1659: fc = get_fc();
1660: mmusr = 0;
1.1 root 1661:
1662: if (level == 0) {
1.1.1.8 root 1663: if (rn != 0) {
1664: // レベル0で An 指定なら Fライン例外
1665: mmu_op_illg2();
1666: return;
1667: }
1.1.1.13! root 1668: CYCLE(22);
1.1 root 1669:
1.1.1.8 root 1670: // laddr は IS と PS のところを落としたもの。
1671: // ssw_laddr のアドレスは IS を落とさないもの (PSは不問)。
1.1.1.10 root 1672: busaddr laddr = busaddr(ea) | busaddr::FC(fc) | rw;
1673: uint32 addr = ea & mmu_tc.lmask;
1674:
1675: m68030ATCTable *table = atc.fctables[fc];
1676: m68030ATCLine *a = NULL;
1677: uint8 n = table->hash[addr >> 12];
1.1.1.12 root 1678: // PS<4KB のケースを先に解決する。
1679: if (__predict_false(n == m68030ATCTable::HASH_SUB)) {
1680: a = table->FindSub(laddr);
1681: if (a) {
1682: n = a - &table->line[0];
1683: } else {
1684: n = m68030ATCTable::HASH_NONE;
1685: }
1686: }
1.1.1.10 root 1687: if (__predict_true((int8)n >= 0)) {
1688: // ATC #(n) で見付かった。
1689: a = &table->line[n];
1.1.1.12 root 1690: } else if (n == m68030ATCTable::HASH_NONE) {
1691: // ATC に見付からなかった。Invalid を立てる。
1692: mmusr = m68030MMUSR::I;
1693: } else if (n == m68030ATCTable::HASH_TT) {
1694: for (int i = 0; i < 2; i++) {
1695: m68030TT& tt = mmu_tt[i];
1696: if (tt.Match(laddr)) {
1697: mmusr |= m68030MMUSR::T;
1.1.1.10 root 1698: }
1699: }
1700: }
1.1.1.8 root 1701:
1702: if (a) {
1.1.1.10 root 1703: // 見付かれば Modified 等の状態を返す。
1.1.1.8 root 1704: if (a->IsBusError()) {
1705: mmusr |= m68030MMUSR::B;
1706: } else if (a->IsWProtect()) {
1707: mmusr |= m68030MMUSR::W;
1708: } else if (a->IsModified()) {
1709: mmusr |= m68030MMUSR::M;
1710: }
1.1 root 1711: }
1712: } else {
1.1.1.8 root 1713: // レベル 1-7 ならテーブルサーチを実行。
1.1.1.13! root 1714: CYCLE(88);
1.1.1.8 root 1715:
1.1.1.10 root 1716: m68030MMU mmu(this, loglevel);
1717: mmu.m_ptest = true;
1718: mmu.search(level, busaddr(ea) | busaddr::FC(fc) | rw);
1.1.1.8 root 1719:
1.1.1.10 root 1720: if (mmu.m_berr) {
1.1 root 1721: mmusr |= m68030MMUSR::B;
1722: }
1.1.1.10 root 1723: if (mmu.m_lim) {
1.1.1.8 root 1724: // XXX not tested
1725: mmusr |= m68030MMUSR::L;
1726: }
1.1.1.10 root 1727: if (mmu.m_s && (fc & 4)) {
1.1.1.8 root 1728: // XXX not tested
1729: mmusr |= m68030MMUSR::S;
1730: }
1.1.1.10 root 1731: if (mmu.m_wp) {
1.1.1.8 root 1732: mmusr |= m68030MMUSR::W;
1733: }
1.1.1.10 root 1734: if (mmu.m_type == INVALID) {
1.1.1.8 root 1735: mmusr |= m68030MMUSR::I;
1736: }
1.1.1.10 root 1737: if (mmu.m_m) {
1.1.1.8 root 1738: mmusr |= m68030MMUSR::M;
1739: }
1740: // N を求める。
1741: // もともとそういう風には出来ていないので逆算している。
1742: int n;
1743: if (mmu_tc.fcl) {
1744: // たぶんファンクションテーブルで1段消費する? (未確認)
1.1.1.10 root 1745: if (mmu.x == FCL) {
1.1.1.8 root 1746: n = 1;
1747: } else {
1.1.1.10 root 1748: n = mmu.x + 2;
1.1.1.8 root 1749: }
1750: } else {
1.1.1.10 root 1751: n = mmu.x + 1;
1.1.1.8 root 1752: }
1753: mmusr |= n;
1754:
1755: // アドレスレジスタ指定があれば、最後のディスクリプタアドレスを返す。
1756: // rn は有効なら 8-15。
1757: if (rn != 0) {
1758: // XXX 途中でバスエラーが起きるケースは未実装
1.1.1.10 root 1759: if (mmu.m_berr == false) {
1760: reg.R[rn] = mmu.m_lastaddr;
1.1.1.8 root 1761: }
1762: }
1.1 root 1763: }
1764:
1.1.1.8 root 1765: SetMMUSR(mmusr);
1.1 root 1766: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.