|
|
nono 1.7.0
//
// nono
// Copyright (C) 2024 nono project
// Licensed under nono-license.txt
//
#include "mpu680x0.h"
#include "mainbus.h"
#include "m68040mmu.h"
#include "m88200.h"
// 読み込みのアドレス変換を行う。
busaddr
MPU68040Device::TranslateRead(busaddr laddr)
{
const m68040TT *tt;
m68040ATC *atc;
putlog(3, "TranslateRead %c.$%08x",
(laddr.IsSuper() ? 'S' : 'U'), laddr.Addr());
// まず TT をチェック。
if (laddr.IsData()) {
atc = atc_data.get();
tt = TTMatch(mmu_dtt, laddr);
} else {
atc = atc_inst.get();
tt = TTMatch(mmu_itt, laddr);
}
if (tt != NULL) {
putlog(3, "TranslateRead %s matched", tt->GetName());
return laddr;
}
// TC がオフならここで終わり。
if (__predict_false(mmu_tc->e == false)) {
putlog(3, "TranslateRead TC:E OFF");
return laddr;
}
return LookupATC(*atc, laddr);
}
// 書き込みのアドレス変換を行う。
busaddr
MPU68040Device::TranslateWrite(busaddr laddr)
{
putlog(3, "TranslateWrite %c.$%08x",
(laddr.IsSuper() ? 'S' : 'U'), laddr.Addr());
// まず TT をチェック。
const m68040TT *tt = TTMatch(mmu_dtt, laddr);
if (tt) {
if (tt->w) {
putlog(3, "TranslateWrite %s matched (WriteProtected)",
tt->GetName());
return BusAddr::BusErr;
} else {
putlog(3, "TranslateWrite %s matched", tt->GetName());
return laddr;
}
}
// TC がオフならここで終わり。
if (__predict_false(mmu_tc->e == false)) {
putlog(3, "TranslateWrite TC:E OFF");
return laddr;
}
m68040ATC *atc = atc_data.get();
return LookupATC(*atc, laddr);
}
// *TTn にマッチすれば、マッチした方のポインタを返す。
// マッチしなければ NULL を返す。
m68040TT *
MPU68040Device::TTMatch(std::array<std::unique_ptr<m68040TT>, 2>& ttbase,
const busaddr laddr) const
{
for (int n = 0; n < 2; n++) {
m68040TT *tt = ttbase[n].get();
if (tt->Match(laddr)) {
return tt;
}
}
return NULL;
}
// ATC を検索する。
busaddr
MPU68040Device::LookupATC(m68040ATC& atc, const busaddr laddr)
{
m68040ATCEntry *entry;
uint32 tag;
uint32 idx;
int n;
// ATC のタグに使う論理アドレスはページサイズによって区切る位置が変わる。
// インデックスは本来タグの比較には使わないが、
// 表示の際にはインデックス部分がないとだいぶ意味が分からないのと、
// 4KB/page なら 16進数で下から 4桁目をインデックスで補完出来るからいいが
// 8KB/page の時にはつらいので、最初からタグに含めておく。
//
// 3 2 1 0
// 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
// +-+-------------------------------+-------+-----------------------+
// |S| Logical Address (16bit) | Index | Page Offset (4KB/page)|
// +-+-------------------------------+-------+-----------------------+
// :<-- FC2 + LAddress (+ Index) = 21bit --->:
//
// +-+-----------------------------+-------+-------------------------+
// |S| Logical Address (15bit) | Index | Page Offset (8KB/page)|
// +-+-----------------------------+-------+-------------------------+
// :<-- FC2 + LAddress (+ Index) = 20bit ->:
//
// タグはこの位置までシフト(とマスク)したもの。
// 3 2 1 0
// 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
// +-----+-+---------------------------------------+-------------+--+
// | 0 |S| Logical Address + Index (19/20bit) : | 0 |~V|
// +-----+-+---------------------------------------+-------------+--+
idx = (laddr.Addr() >> mmu_tc->tic_shift) & 0xf;
tag = (laddr.Get() >> 4) & atc.tag_mask; // FC2+Addr(+Index)
m68040ATCSet& set = atc.sets[idx];
// このセットから探す。
for (n = 0; n < set.entry.size(); n++) {
entry = &set.entry[n];
if (entry->tag == tag) {
if (entry->MatchStatus(laddr)) {
// 見付かった。
goto found;
} else {
break;
}
}
}
// 空きラインか、空いてなければ生贄を差し出す。
n = set.GetEntry();
entry = &set.entry[n];
entry->tag = tag;
// テーブルサーチを行い、エントリを更新する。
Search(entry, laddr);
// このエントリで再度ステータスが一致するか。(タグは一致している)
if (entry->MatchStatus(laddr) == false) {
putlog(3, " LookupATC entry not matched");
return BusAddr::BusErr;
}
found:
// 見付かったのでこれを返す。
set.LRU = m88200CacheSet::TryUseLine(set.LRU, n);
uint32 offset = laddr.Addr() & ~mmu_tc->page_mask;
busaddr paddr = laddr;
paddr.ChangeAddr(entry->paddr | offset);
putlog(3, " result $%08x", paddr.Addr());
return paddr;
}
// laddr がこのエントリの各種ステータスにマッチするか調べる。
// タグが一致している状態で呼ぶこと。
bool
m68040ATCEntry::MatchStatus(busaddr laddr) const
{
if (IsResident() == false) {
return false;
}
if (laddr.IsSuper() == false && IsSuperProtected()) {
return false;
}
if (laddr.IsWrite() && IsWriteProtected()) {
return false;
}
return true;
}
// 更新用に空きエントリか、空きがなければどれかを差し出す。
int
m68040ATCSet::GetEntry() const
{
for (int i = 0; i < 4; i++) {
if (entry[i].IsValid() == false) {
return i;
}
}
int oldest = m88200CacheSet::TryGetOldestLine(LRU);
return oldest;
}
// テーブルサーチを行い、entry を埋める。
void
MPU68040Device::Search(m68040ATCEntry *entry, const busaddr laddr)
{
uint32 base;
uint32 idx;
uint32 mask;
uint32 addr;
uint32 desc;
uint32 pdt;
busdata bd;
bool acc_wp = false;
bool is_write = laddr.IsWrite();
putlog(3, " Search %c.$%08x", (laddr.IsSuper() ? 'S' : 'U'), laddr.Addr());
// FC2 からルートポインタを決定。
// XXX FC2 なのかこれからアクセスする空間の FC なのか。
base = laddr.IsSuper() ? reg.srp40 : reg.urp40;
// 第1、第2 レベルのディスクリプタを処理。
idx = laddr.Addr() >> 25;
mask = 0xfffffe00;
for (int lv = 0; lv < 2; lv++) {
addr = base + idx * 4;
putlog(4, " base=$%08x [$%02x] descaddr=$%08x", base, idx, addr);
bd = read_phys_4(addr);
if (__predict_false(bd.IsBusErr())) {
putlog(4, " BusErr (on read desc)");
// エラーなので R を落としたエントリを作成。
desc = 0;
goto done;
}
desc = bd.Data();
if (__predict_false(loglevel >= 4)) {
static const char *udtname[] = {
"Invalid", "Invalid", "Resident", "Resident",
};
uint32 dt = desc & m68040MMU::DESC_DT;
putlogn(" desc=$%08x %c%c UDT%u(%s)", desc,
((desc & m68040MMU::DESC_U) ? 'U' : '-'),
((desc & m68040MMU::DESC_W) ? 'W' : '-'),
dt, udtname[dt]);
}
// ここから Fig. 3-10 左下あたり。
// UDT はビット1 が %0 なら無効。
if ((desc & 2) == 0) {
putlog(4, " invalid");
// R を落としたエントリを作成。(Fig. 3-9)
// UDT の Resident はビット1だが、desc の Resident はビット0。
desc &= ~m68040MMU::DESC_R;
goto done;
}
// WP を積算。
if ((desc & m68040MMU::DESC_W) != 0) {
acc_wp = true;
}
// U が立ってなければ U を立てて更新。
// 実際には次のタイミングに遅延するらしいが。
if ((desc & m68040MMU::DESC_U) == 0) {
desc |= m68040MMU::DESC_U;
bd = write_phys_4(addr, desc);
if (__predict_false(bd.IsBusErr())) {
putlog(4, " BusErr (on writeback U)");
// エラーなので R を落としたエントリを作成。
desc &= ~m68040MMU::DESC_R;
goto done;
}
}
// 次の段へ。
base = desc & mask;
idx = (laddr.Addr() >> 18) & 0x7f;
mask = mmu_tc->table_mask;
}
// ページディスクリプタを処理。
idx = (laddr.Addr() >> mmu_tc->tic_shift) & mmu_tc->tic_mask;
addr = base + idx * 4;
putlog(4, " base=$%08x [$%02x] descaddr=$%08x", base, idx, addr);
bd = read_phys_4(addr);
if (__predict_false(bd.IsBusErr())) {
putlog(4, " BusErr (on read desc)");
// エラーなので R を落としたエントリを作成。
desc &= ~m68040MMU::DESC_R;
goto done;
}
desc = bd.Data();
pdt = desc & m68040MMU::DESC_DT;
if (__predict_false(loglevel >= 4)) {
static const char *pdtname[] = {
"Invalid", "Resident", "INDIRECT", "Resident",
};
putlogn(" desc=$%08x %c%c%c%c%c CM%u PDT%u(%s)", desc,
((desc & m68040MMU::DESC_G) ? 'G' : '-'),
((desc & m68040MMU::DESC_S) ? 'S' : '-'),
((desc & m68040MMU::DESC_M) ? 'M' : '-'),
((desc & m68040MMU::DESC_U) ? 'U' : '-'),
((desc & m68040MMU::DESC_W) ? 'W' : '-'),
((desc & m68040MMU::DESC_CM) >> 5),
pdt, pdtname[pdt]);
}
// ここから Fig. 3-10 右下あたり。
// PDT は %00 なら無効。この場合エントリは作成しない。
if (__predict_false(pdt == m68040MMU::PDT_INVALID)) {
putlog(4, " invalid");
// R を落としたエントリを作成。(Fig. 3-9)
desc &= ~m68040MMU::DESC_R;
goto done;
} else if (__predict_false(pdt == m68040MMU::PDT_INDIRECT)) {
PANIC("indirect descriptor");
}
// WP を積算。
if ((desc & m68040MMU::DESC_W) != 0) {
acc_wp = true;
}
if (__predict_true(is_write == false)) {
// READ ACCESS
if ((desc & m68040MMU::DESC_U) == 0) {
desc |= m68040MMU::DESC_U;
// 本当は Read-Modify-Write。
bd = write_phys_4(addr, desc);
if (__predict_false(bd.IsBusErr())) {
putlog(4, " BusErr (on writeback U)");
// エラーなので R を落としたエントリを作成。
desc &= ~m68040MMU::DESC_R;
goto done;
}
}
} else {
// WRITE ACCESS
uint32 olddesc = desc;
if (acc_wp == false && (desc & m68040MMU::DESC_M) == 0) {
desc |= m68040MMU::DESC_U | m68040MMU::DESC_M;
} else if ((desc & m68040MMU::DESC_U) == 0) {
desc |= m68040MMU::DESC_U;
// こっちの場合は本当は Read-Modify-Write。
}
if (__predict_false(olddesc != desc)) {
bd = write_phys_4(addr, desc);
if (__predict_false(bd.IsBusErr())) {
putlog(4, " BusErr (on writeback U)");
// エラーなので R を落としたエントリを作成。
desc &= ~m68040MMU::DESC_R;
goto done;
}
}
}
done:
entry->paddr = desc & mmu_tc->page_mask;
entry->desc = desc;
if (__predict_false(loglevel >= 3)) {
if ((entry->desc & m68040MMU::DESC_R)) {
putlogn(" frame addr $%08x", entry->paddr);
} else {
putlogn(" Not Resident");
}
}
}
// ピーク用のアドレス変換を行う。
// 変換できれば対応する物理アドレスを、バスエラーなら BusErr を返す。
// テーブルサーチを行ったら TableSearched ビットを立てる。
busaddr
MPU68040Device::TranslatePeek(busaddr laddr)
{
const m68040TT *tt;
// TT を自前でサーチ。
if (laddr.IsData()) {
tt = TTMatch(mmu_dtt, laddr);
} else {
tt = TTMatch(mmu_itt, laddr);
}
if (__predict_false(tt != NULL)) {
if (tt->Match(laddr)) {
return laddr;
}
}
if (__predict_false(mmu_tc->e == false)) {
return laddr;
}
// ATC を自前でサーチ。
const m68040ATC *atc;
if (laddr.IsData()) {
atc = atc_data.get();
} else {
atc = atc_inst.get();
}
uint32 idx = (laddr.Addr() >> mmu_tc->tic_shift) & 0xf;
uint32 tag = (laddr.Get() >> 4) & atc->tag_mask; // FC2+Addr(+Index)
const m68040ATCSet& set = atc->sets[idx];
for (const auto& entry : set.entry) {
if (entry.tag == tag) {
if (entry.MatchStatus(laddr)) {
// 見付かった。
uint32 offset = laddr.Addr() & ~mmu_tc->page_mask;
laddr.ChangeAddr(entry.paddr | offset);
return laddr;
} else {
break;
}
}
}
// ATC になければ、テーブルサーチ相当を自前で行う。
laddr |= BusAddr::TableSearched;
bool is_write = laddr.IsWrite();
uint32 desc;
// FC2 からルートポインタを決定。
uint32 base = laddr.IsSuper() ? reg.srp40 : reg.urp40;
// 第1レベル。
idx = laddr.Addr() >> 25;
desc = PeekDesc(base, idx);
// UDT はビット1 が %0 なら無効。
if ((desc & 2) == 0) {
return BusAddr::BusErr;
}
if (__predict_false(is_write) && (desc & 4)) {
return BusAddr::BusErr;
}
base = desc & 0xfffffe00;
// 第2レベル。
idx = (laddr.Addr() >> 18) & 0x7f;
desc = PeekDesc(base, idx);
// UDT はビット1 が %0 なら無効。
if ((desc & 2) == 0) {
return BusAddr::BusErr;
}
if (__predict_false(is_write) && (desc & 4)) {
return BusAddr::BusErr;
}
base = desc & mmu_tc->table_mask;
// ページディスクリプタ。
idx = (laddr.Addr() >> mmu_tc->tic_shift) & mmu_tc->tic_mask;
desc = PeekDesc(base, idx);
// PDT は %00 なら無効。
uint32 pdt = desc & m68040MMU::DESC_DT;
if (pdt == m68040MMU::PDT_INVALID) {
return BusAddr::BusErr;
} else if (__predict_false(pdt == m68040MMU::PDT_INDIRECT)) {
PANIC("indirect descriptor");
}
if (__predict_false(is_write) && (desc & 4)) {
return BusAddr::BusErr;
}
uint32 paddr = (desc & mmu_tc->page_mask)
| (laddr.Addr() & ~mmu_tc->page_mask);
laddr.ChangeAddr(paddr);
return laddr;
}
// base[idx] からディスクリプタを副作用なく読み出す。(デバッガ用)
// U フラグは更新しない。
// バスエラーが起きれば 0 を返す (ディスクリプタの下位2ビットが %00 なら
// 無効を示すので)。
uint32
MPU68040Device::PeekDesc(uint32 base, uint32 idx)
{
uint32 addr = base + idx * 4;
uint64 data = mainbus->Peek4(addr);
if ((int64)data < 0) {
return 0;
}
return data;
}
// 現在の TC:E、TT:E の状態に応じてアドレス変換の有無を切り替える。
// mmu_enable ならアドレス変換ありのバスアクセスを使う。see m68030bus.cpp
void
MPU68040Device::mmu_enable_changed()
{
bool enable;
// TC、ITT*、DTT* のいずれかが有効ならアドレス変換あり。
enable = (reg.tc40 & m68040TC::E);
enable |= mmu_itt[0]->e || mmu_itt[1]->e;
enable |= mmu_dtt[0]->e || mmu_dtt[1]->e;
// 変化した時だけ
if (mmu_enable && !enable) {
// 無効にする
mmu_enable = false;
putlog(1, "MMU Translation Disabled");
} else if (!mmu_enable && enable) {
// 有効にする
mmu_enable = true;
putlog(1, "MMU Translation Enabled");
}
}
// *TTn レジスタへの書き込み。
void
MPU68040Device::SetTT(m68040TT *tt, uint32 data)
{
*tt->reg = (data & m68040TT::MASK);
putlog(1, "%s <- $%08x", tt->GetName(), *tt->reg);
tt->base = (data & m68040TT::LBASE_MASK);
tt->mask = (~data & m68040TT::LMASK_MASK) << 8;
tt->e = (data & m68040TT::E);
tt->s_ign = (data & m68040TT::S_IGN);
tt->s_fc2 = (data & m68040TT::S_FC2);
tt->u = (data & m68040TT::U_MASK);
tt->cm = (data & m68040TT::CM) >> 5;
tt->w = (data & m68040TT::W);
mmu_enable_changed();
}
void
MPU68040Device::SetSRP(uint32 data)
{
// とりあえず
reg.srp40 = data & m68040RP::ADDR_MASK;
putlog(1, "SRP <- $%08x", reg.srp40);
}
void
MPU68040Device::SetURP(uint32 data)
{
// とりあえず
reg.urp40 = data & m68040RP::ADDR_MASK;
putlog(2, "URP <- $%08x", reg.urp40);
}
void
MPU68040Device::SetTC(uint32 data)
{
// とりあえず
reg.tc40 = data & m68040TC::MASK;
putlog(1, "TC <- $%04x", reg.tc40);
mmu_tc->e = reg.tc40 & m68040TC::E;
if ((reg.tc40 & m68040TC::P) == 0) {
// 4KB/page
mmu_tc->ps4k = true;
mmu_tc->table_mask = 0xffffff00;
mmu_tc->tic_shift = 12;
mmu_tc->tic_mask = 0x0000003f;
mmu_tc->page_mask = 0xfffff000;
} else {
// 8KB/page
mmu_tc->ps4k = false;
mmu_tc->table_mask = 0xffffff80;
mmu_tc->tic_shift = 13;
mmu_tc->tic_mask = 0x0000001f;
mmu_tc->page_mask = 0xffffe000;
}
// ページサイズと連動している ATC の変数もセット。
uint32 tag_mask = (mmu_tc->page_mask >> 4) | 0x1000'0000;
atc_inst->tag_mask = tag_mask;
atc_data->tag_mask = tag_mask;
mmu_enable_changed();
}
// リセット例外の CPU 固有部分。
void
MPU68040Device::ResetMMU()
{
mmu_tc->e = false;
mmu_itt[0]->e = false;
mmu_itt[1]->e = false;
mmu_dtt[0]->e = false;
mmu_dtt[1]->e = false;
// ATC は影響を受けない。
}
// *TT
// コンストラクタ
m68040TT::m68040TT(const char *name_, uint32 *reg_)
{
name = name_;
reg = reg_;
}
// デストラクタ
m68040TT::~m68040TT()
{
}
// addr がマッチすれば true を返す。
bool
m68040TT::Match(busaddr addr) const
{
if (__predict_true(e == false)) {
return false;
}
if (s_ign == false) {
if (addr.IsSuper() != s_fc2) {
return false;
}
}
if ((addr.Addr() & mask) != base) {
return false;
}
// 一致した。
return true;
}
// ATC
// ATC の指定の権限空間をすべて無効にする。
void
m68040ATC::Flush(bool s, bool only_non_global)
{
uint32 target = s ? 0x1000'0000 : 0;
// G only_non_global
// --- ---
// 0 0 invalidate
// 0 1 invalidate
// 1 0 invalidate
// 1 1 nop
for (auto& set : sets) {
for (auto& entry : set.entry) {
// S/U と Valid ビットのみ比較。
if ((entry.tag & 0x1000'0001) == target) {
if (only_non_global == false || entry.IsGlobal() == false) {
entry.Invalidate();
}
}
}
}
}
// ATC の指定の空間+アドレスを無効にする。
void
m68040ATC::Flush(busaddr addr, bool only_non_global)
{
uint32 tag = (addr.Get() >> 4) & tag_mask;
for (auto& set : sets) {
for (auto& entry : set.entry) {
// S/U、アドレス、Valid ビットすべてを比較。
if (entry.tag == tag) {
if (only_non_global == false || entry.IsGlobal() == false) {
entry.Invalidate();
}
}
}
}
}
// モニター更新 (68040 ATC)
void
MPU68040Device::MonitorScreenATC(Monitor *, TextScreen& screen)
{
int x = 64;
// 0 1 2 3 4 5
// 0123456789012345678901234567890123456789012345678901234567890
// IDX TagAddr PAddr Stat IDX TagAddr PAddr Stat
// [0] S.12345'000 12345'000 ---- [1] S.12345'000 12345'000 ----
bool enable = mmu_tc->e;
TA attr_e = enable ? TA::Normal : TA::Disable;
screen.Clear();
screen.Puts(0, 0, attr_e, "<Data ATC>");
screen.Puts(x, 0, attr_e, "<Instruction ATC>");
for (int i = 0; i < 4; i++) {
screen.Puts(i * 31 + (i & 2), 1, "IDX TagAddr PAddr Stat");
}
MonitorScreenATC1(screen, 0, enable, atc_data.get());
MonitorScreenATC1(screen, x, enable, atc_inst.get());
}
void
MPU68040Device::MonitorScreenATC1(TextScreen& screen, int x, bool enable,
const m68040ATC *atc)
{
int y = 2;
TA attr_e = enable ? TA::Normal : TA::Disable;
for (uint i = 0; i < atc->sets.size(); i++) {
const m68040ATCSet& set = atc->sets[i];
screen.Print(x, y, attr_e, "[%x]", i);
// 古い順に取り出して 3-0 の順序をつける。(新しい順に表示するため)
int order[4];
uint tmpL = set.LRU;
for (int j = 3; j >= 0; j--) {
int line = m88200CacheSet::TryGetOldestLine(tmpL);
order[j] = line;
tmpL = m88200CacheSet::TryUseLine(tmpL, line);
}
for (int j = 0; j < set.entry.size(); j++) {
int line = order[j];
const m68040ATCEntry& entry = set.entry[line];
TA attr = entry.IsValid() ? TA::Normal : TA::Disable;
screen.Print(x + 4, y, attr,
"%c.%05x'000 %05x'000 %c%c%c%c",
(entry.IsSuper() ? 'S' : 'U'),
((entry.tag >> 8) & 0xfffff),
entry.paddr >> 12,
((entry.desc & m68040MMU::DESC_G) ? 'G' : '-'),
((entry.desc & m68040MMU::DESC_S) ? 'S' : '-'),
((entry.desc & m68040MMU::DESC_W) ? 'W' : '-'),
((entry.desc & m68040MMU::DESC_R) ? 'R' : '-'));
y++;
}
if (i == 7) {
x += 31;
y = 2;
}
}
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.