|
|
nono 1.7.0
//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//
//
// PIO/PPI (i8255)
//
// IODevice
// |
// | +-------------+
// +--| i8255Device | (8255 としての共通部分)
// +-------------+
// |
// | +-----------+
// +--| PPIDevice | (X68030)
// | +-----------+
// |
// | +------------+
// +--| PIO0Device | (LUNA)
// | +------------+
// |
// | +------------+
// +--| PIO1Device | (LUNA)
// +------------+
#include "pio.h"
#include "adpcm.h"
#include "bitops.h"
#include "dipsw.h"
#include "event.h"
#include "interrupt.h"
#include "lcd.h"
#include "mainapp.h"
#include "monitor.h"
#include "mpu64180.h"
#include "power.h"
#include "scheduler.h"
//
// i8255
//
// コンストラクタ
i8255Device::i8255Device(uint objid_)
: inherited(objid_)
{
}
// デストラクタ
i8255Device::~i8255Device()
{
}
// リセット
void
i8255Device::ResetHard(bool poweron)
{
// clears the control register
// and place ports A, B, and C in input mode.
// The input latches in ports A, B, and C are not cleared.
ctrl = 0b10011011;
}
// コントロールポートへの書き込み。
void
i8255Device::WriteCtrl(uint32 data)
{
if ((data & 0x80)) {
// 最上位が立っていればモード設定
ctrl = data;
if (__predict_false(loglevel >= 1)) {
if (loglevel >= 2) {
putlogn("Ctrl <- $%02x: GrA/Bmode=%u/%u PA=%s PB=%s PC=%s:%s",
data,
GetGroupAMode(), GetGroupBMode(),
PortADir() == DIR_IN ? "IN" : "OUT",
PortBDir() == DIR_IN ? "IN" : "OUT",
PortCHDir() == DIR_IN ? "IN" : "OUT",
PortCLDir() == DIR_IN ? "IN" : "OUT");
} else {
putlogn("Ctrl <- $%02x: SetMode", data);
}
}
// モード設定すると PC はゼロクリアされる
for (int pc = 0; pc < 8; pc++) {
SetPC(pc, 0);
}
} else {
// portC 出力
//
// 0 x x x P P P V
// | | | +-- 書き込むビットデータ
// +--+--+----- 書き込むビット位置
uint pc = (data >> 1) & 7;
uint val = data & 1;
putlog(2, "Ctrl <- $%02x: SetPC%u=%u", data, pc, val);
SetPC(pc, val);
}
}
bool
i8255Device::PokePort(uint32 offset, uint32 data)
{
return false;
}
// 共通部分を表示。
// 戻り値は表示を終えた次の行。
int
i8255Device::MonitorScreenCommon(TextScreen& screen, int y)
{
// Control = $xx
// GroupA (PortA,PortCH) Mode: 0(Basic I/O)
// GroupB (PortB,PortCL) Mode: 1(Strobed I/O)
// Port Direction: PA=OUT PB=IN PC(H)=OUT PC(L)=OUT
screen.Print(1, y, "Control = $%02x:", ctrl);
static const char * const modestr[] = {
"Basic I/O",
"Strobed I/O",
"BiDirection",
"BiDirection",
};
const int x = 16;
screen.Print(x, y++, "GroupA (PortA,PortCH) Mode: %u(%s)",
GetGroupAMode(),
modestr[GetGroupAMode()]);
screen.Print(x, y++, "GroupB (PortB,PortCL) Mode: %u(%s)",
GetGroupBMode(),
modestr[GetGroupBMode()]);
static const char *inoutstr[] = {
"OUT",
"IN ",
};
screen.Print(x, y++, "Port Direction: PA=%s PB=%s PC(H)=%s PC(L)=%s",
inoutstr[PortADir()],
inoutstr[PortBDir()],
inoutstr[PortCHDir()],
inoutstr[PortCLDir()]);
return y;
}
//
// X68k PPI
//
// InsideOut p.135
static const busdata ppi_wait = busdata::Wait(19 * 40_nsec);
// コンストラクタ
PPIDevice::PPIDevice()
: inherited(OBJ_PPI)
{
}
// デストラクタ
PPIDevice::~PPIDevice()
{
}
// 初期化
bool
PPIDevice::Init()
{
adpcm = GetADPCMDevice();
return true;
}
busdata
PPIDevice::ReadPort(uint32 offset)
{
busdata data;
switch (offset) {
case 0:
case 1:
data = 0xff;
break;
case 2:
data = GetPC();
putlog(1, "PortC -> $%02x", data.Data());
break;
case 3:
putlog(0, "Read $e9a007 Ctrl (NOT IMPLEMENTED)");
data = 0xff;
break;
default:
VMPANIC("corrupted offset=%u", offset);
}
data |= ppi_wait;
data |= BusData::Size1;
return data;
}
busdata
PPIDevice::WritePort(uint32 offset, uint32 data)
{
switch (offset) {
case 0:
case 1:
break;
case 2: // PPI PortC
for (int i = 0; i < 8; i++) {
SetPC(i, data & 1);
data >>= 1;
}
break;
case 3:
WriteCtrl(data);
break;
default:
VMPANIC("corrupted offset=%u", offset);
}
busdata r = ppi_wait;
r |= BusData::Size1;
return r;
}
busdata
PPIDevice::PeekPort(uint32 offset)
{
// XXX
return 0xff;
}
// PortC 読み込み
uint32
PPIDevice::GetPC() const
{
uint32 data = 0;
// パンの状態はこちらで持っていない。
if (adpcm->GetPanOutR() == false) {
data |= 0x01;
}
if (adpcm->GetPanOutL() == false) {
data |= 0x02;
}
// サンプリングレートは書き込み値をこちらでも持っている(ADPCMにもある)。
data |= adpcm_rate << 2;
// bit4: PC4 (通常 0)
// bit5: PC5 (通常 0)
// bit6: IOA5 (1=ジョイスティック#1のAが押された状態にする, 0=通常)
// bit6: IOA6 (1=ジョイスティック#1のBが押された状態にする, 0=通常)
return data;
}
// PortC 書き込み
void
PPIDevice::SetPC(uint pc, uint val)
{
switch (pc) {
case 0: // ADPCM PAN Left Off
adpcm->SetPanOutL(!val);
return;
case 1: // ADPCM PAN Right Off
adpcm->SetPanOutR(!val);
return;
case 2: // ADPCM サンプリングレート
adpcm_rate &= ~1U;
adpcm_rate |= val ? 1U : 0;
adpcm->SetRate(adpcm_rate);
return;
case 3: // ADPCM サンプリングレート
adpcm_rate &= ~2U;
adpcm_rate |= val ? 2U : 0;
adpcm->SetRate(adpcm_rate);
return;
case 4: // ジョイスティック #1 コントロール
case 5: // ジョイスティック #2 コントロール
case 6: // ジョイスティック #1 ボタン A
case 7: // ジョイスティック #1 ボタン B
putlog(1, "SetPC(%u) <- %u (NOT IMPLEMENTED)", pc, val);
return;
default:
VMPANIC("corrupted pc=%u", pc);
}
}
//
// LUNA PIO0
//
// 仕様は 0x49000000 から 4バイトだが、
// LUNA-I では 0x48000000 から 0x4bffffff まで 4バイト単位のミラーが見える。
// LUNA-88K では 0x48000000 から 0x49ffffff までが
// (こちらはロングワード配置なので) 16バイト単位のミラーに見える。
//
// PIO から読み出せる値のビットの並び順が機種によって異なる。
// LUNA-I では DIPSW 1番が LSB、8番が MSB の並び順、
// LUNA-88K では DIPSW 1番が MSB、8番が LSB の並び順。
// 値は、並び順に関わらずどちらも
// %0 が false = down = "0"、%1 が true = up = "1" である。
//
// #1 #2 #3 #4 #5 #6 #7 #8
// +-------------------------+
// | [] [] [] [] [] [] [] | 本体外観
// | [] |
// +-------------------------+
//
// luna-dipsw1 = "01111111" ← 設定ファイル
// |||||||| ↓ このクラスのメンバ変数
// |||||||+- dipsw1[7] = true;
// ||||||+-- dipsw1[6] = true;
// |||||+--- dipsw1[5] = true;
// ||||+---- dipsw1[4] = true;
// |||+----- dipsw1[3] = true;
// ||+------ dipsw1[2] = true;
// |+------- dipsw1[1] = true;
// +-------- dipsw1[0] = false;
//
// b7 b6 b5 b4 b3 b2 b1 b0
// +----+----+----+----+----+----+----+----+
// LUNA-I | #8 | #7 | #6 | #5 | #4 | #3 | #2 | #1 |
// PortA | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | %1=UP, %0=DOWN
// +----+----+----+----+----+----+----+----+
//
// b7 b6 b5 b4 b3 b2 b1 b0
// +----+----+----+----+----+----+----+----+
// LUNA-88K | #1 | #2 | #3 | #4 | #5 | #6 | #7 | #8 |
// PortA | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | %1=UP, %0=DOWN
// +----+----+----+----+----+----+----+----+
//
// LUNA-I:
// #1-1 up なら UNIX をロードして移行。down なら ROM モニタで起動。
// #1-2 up ならディスプレイコンソール。down ならシリアルコンソール。
// #1-3 up - color display, down - force to have monochrome display
// #1-4 up - no write verification,
// down - verification on every harddisk write operation
// #1-5 up - OS is UniOS-U (SystemV/COFF),
// down - OS is UniOS (4.3BSD/a.out OMAGIC)
// #1-6 down - force monochrome display?
// #1-7 up - boot from local device, down - boot from network
// #1-8 up - normal boot, down - start diagnostics と書いてあるが
// down にすると画面真っ暗で詳細不明。
//
// #2-x すべて up にすると /boot が自動的にカーネルを起動。一つでも down が
// あれば /boot がプロンプトで停止。これは ROM ではなく NetBSD のブート
// ローダの話。
// どこか由来のソースコードらしいがたぶんビット演算を間違えている。
//
// LUNA-88K:
// #1-1 up なら ROM モニタで停止、down ならマルチユーザブート。
// OpenBSD カーネルも up なら UserKernelConfig で止まる模様?。
// #1-2 up なら外付けシリアルコンソール、down なら内蔵ビットマップ。
// #1-3..#1-7 は不明?
// #1-8 down なら自己診断後電源オフするようだ。
// #2-x はユーザ(?)
// コンストラクタ
PIO0Device::PIO0Device()
: inherited(OBJ_PIO0)
{
// LUNA-I と LUNA-88K とでポートのビット並びが逆。どうして…
if (gMainApp.IsLUNA88K()) {
msb_first = true;
} else {
msb_first = false;
}
// PIO0/PIO1
monitor = gMonitorManager->Regist(ID_MONITOR_PIO, this);
monitor->SetCallback(&PIO0Device::MonitorScreen);
monitor->SetSize(76, 15);
}
// デストラクタ
PIO0Device::~PIO0Device()
{
}
// 初期化
bool
PIO0Device::Init()
{
interrupt = GetInterruptDevice();
pio1 = GetPIO1Device();
return true;
}
// リセット
void
PIO0Device::ResetHard(bool poweron)
{
auto dipswdev = GetDipswDevice();
// XXX 本当は XP から割り込みが上がったタイミングとからしいけど、
// XP が出来るまでは当面リセットでラッチにしておく。
for (int i = 0; i < 8; i++) {
dipsw1[i] = dipswdev->Get(i);
dipsw2[i] = dipswdev->Get(i + 8);
}
// XXX どうなる?
for (int i = 0; i < intreq.size(); i++) {
intreq[i] = false;
inten[i] = false;
}
}
busdata
PIO0Device::ReadPort(uint32 offset)
{
busdata data;
switch (offset) {
case 0:
// PortA
data = GetPA();
// 割り込み要求(High)をクリア
intreq[INT_H] = false;
ChangeInterrupt();
putlog(1, "DIP-SW1 -> $%02x", data.Data());
break;
case 1:
// PortB
data = GetPB();
// 割り込み要求(Low)をクリア
intreq[INT_L] = false;
ChangeInterrupt();
putlog(1, "DIP-SW2 -> $%02x", data.Data());
break;
case 2:
// PortC
data = GetPC();
putlog(1, "PortC -> $%02x", data.Data());
break;
case 3:
data = ReadCtrl();
putlog(1, "Ctrl -> $%02x", data.Data());
break;
default:
VMPANIC("corrupted offset=%u", offset);
}
// XXX wait?
data |= BusData::Size1;
return data;
}
busdata
PIO0Device::WritePort(uint32 offset, uint32 data)
{
switch (offset) {
case 0: // PIO0 PortA: DIPSW1 (入力のみ)
case 1: // PIO0 PortB: DIPSW2 (入力のみ)
break;
case 2: // PIO0 PortC: ホスト割り込み制御
for (int i = 0; i < 8; i++) {
SetPC(i, data & 1);
data >>= 1;
}
break;
case 3: // コントロール
WriteCtrl(data);
break;
default:
VMPANIC("corrupted offset=%u", offset);
}
// XXX wait?
busdata r = BusData::Size1;
return r;
}
busdata
PIO0Device::PeekPort(uint32 offset)
{
switch (offset) {
case 0:
return GetPA();
case 1:
return GetPB();
case 2:
return GetPC();
case 3:
return ReadCtrl();
default:
return 0xff;
}
}
// PIO0 が PIO0/PIO1 のモニタ両方を担当する。
void
PIO0Device::MonitorScreen(Monitor *, TextScreen& screen)
{
int y;
screen.Clear();
// PIO0
screen.Puts(0, 0, "PIO0 (BaseAddr: $49000000)");
y = MonitorScreenCommon(screen, 1);
// PIO0 固有部
uint32 pa = GetPA();
uint32 pb = GetPB();
screen.Print(1, y, "PortA(DIP-SW1)=$%02x:", pa);
screen.Print(1, y + 1, "PortB(DIP-SW2)=$%02x:", pb);
for (int i = 0; i < 8; i++) {
// この図は bit7..0 をこの順で表示するので bitpos の向きは固定。
int bitpos = 7 - i;
// DIPSW の物理位置 #1 をバイトの下にするか上にするかが機種によって
// 違う。どうして…。
int label = (msb_first ? i : bitpos) + 1;
bool d1 = (pa & (1U << bitpos));
bool d2 = (pb & (1U << bitpos));
screen.Print(21 + i * 7, y, TA::OnOff(d1), "DIP1-%u", label);
screen.Print(21 + i * 7, y + 1, TA::OnOff(d2), "DIP2-%u", label);
}
y += 2;
uint32 pc = GetPC();
screen.Print(1, y, "PortC(XP etc.)=$%02x:", pc);
static const char * const pcstr[] = {
"XP RST", "PARITY", "------", "INT5EN",
"INT5RQ", "INT1EN", "------", "INT1RQ",
};
for (int i = 0; i < 8; i++) {
screen.Puts(21 + i * 7, y, TA::OnOff(pc & (1U << (7 - i))), pcstr[i]);
}
y++;
// PIO1 移譲
y++;
pio1->MonitorScreenPIO1(screen, y);
}
// PortA (DIP-SW 1) 読み込み
uint32
PIO0Device::GetPA() const
{
uint32 data = 0;
for (int i = 0; i < 8; i++) {
data |= ((int)dipsw1[i]) << i;
}
if (msb_first) {
data = bitrev8(data);
}
return data & 0xff;
}
// PortB (DIP-SW 2) 読み込み
uint32
PIO0Device::GetPB() const
{
uint32 data = 0;
for (int i = 0; i < 8; i++) {
data |= ((int)dipsw2[i]) << i;
}
if (msb_first) {
data = bitrev8(data);
}
return data & 0xff;
}
// PortC 読み込み
uint32
PIO0Device::GetPC() const
{
uint32 data;
data = 0x00 // XXX 空きビットは何が読めるか?
| (intreq[INT_L] ? 0x01 : 0)
| (inten[INT_L] ? 0x04 : 0)
| (intreq[INT_H] ? 0x08 : 0)
| (inten[INT_H] ? 0x10 : 0)
| (parity ? 0x40 : 0)
| (xp_reset ? 0x80 : 0);
return data;
}
// PortC 書き込み
void
PIO0Device::SetPC(uint pc, uint val)
{
switch (pc) {
case 0:
// INT1 割り込み要求 (Read Only)
return;
case 1:
// Not Connected.
return;
case 2:
inten[INT_L] = val;
ChangeInterrupt();
return;
case 3:
// INT5 割り込み要求 (Read Only)
return;
case 4:
inten[INT_H] = val;
ChangeInterrupt();
return;
case 5:
// Not Connected.
return;
case 6:
// パリティ有効/無効
// 使わないので無視するが読み返しのために覚えておく
parity = val;
return;
case 7:
// XP リセット
if ((val ^ xp_reset)) {
xp_reset = val;
putlog(1, "XP Reset %u", xp_reset);
auto mpu64180 = GetMPU64180Device();
mpu64180->ChangeRESET(xp_reset);
}
return;
default:
VMPANIC("corrupted pc=%u", pc);
}
}
// とりあえず、XP からここまではエッジトリガーで、
// ここから割り込みコントローラへはレベルトリガーとしておく。
// XP からの割り込み (High)
void
PIO0Device::InterruptXPHigh()
{
intreq[INT_H] = true;
ChangeInterrupt();
}
// XP からの割り込み (Low)
void
PIO0Device::InterruptXPLow()
{
intreq[INT_L] = true;
ChangeInterrupt();
}
// 割り込み状態を変更。
// intreq[], inten[] の変更で呼ぶこと。
void
PIO0Device::ChangeInterrupt()
{
bool val;
val = (inten[INT_H] && intreq[INT_H]);
interrupt->ChangeINT(DEVICE_XPINT_HIGH, val);
val = (inten[INT_L] && intreq[INT_L]);
interrupt->ChangeINT(DEVICE_XPINT_LOW, val);
}
//
// LUNA PIO1
//
// 仕様は 0x4d000000 から 4バイトだが、
// LUNA-I では 0x4c000000 から 0x4fffffff まで 4バイト単位でミラーが見える。
// LUNA-88K では 0x4c000000 から 0x4dffffff までが
// (こちらはロングワード配置なので) 16バイト単位のミラーに見える。
// コンストラクタ
PIO1Device::PIO1Device()
: inherited(OBJ_PIO1)
{
}
// デストラクタ
PIO1Device::~PIO1Device()
{
}
// 初期化
bool
PIO1Device::Init()
{
lcd = GetLCDDevice();
mpu64180 = GetMPU64180Device();
powerdev = GetPowerDevice();
return true;
}
// リセット
void
PIO1Device::ResetHard(bool poweron)
{
inherited::ResetHard(poweron);
// 電源ユニットは POWF* 信号が 'L' になると 1msec 後に電源オフになる。
// 正確には「1msec 以内に 'H' にすれば回避できる」なので、実際に電源が
// オフになるのはそれよりもっと十分長いとは思われる。
// 8255 リセット時、ポート C の各ピンは入力モードになっているので、
// ハイインピーダンス。たぶん電源的にも 'H' になってると想像する。
// なので電源オンで POWF* がハイインピーダンスなだけでは電源オフ
// タイマーは動作しないんじゃないかな。
power = true;
powerdev->SetSystemPowerOn(power);
}
busdata
PIO1Device::ReadPort(uint32 offset)
{
busdata data;
switch (offset) {
case 0:
// PIO1 PortA: LCD データ
data = lcd->Read();
break;
case 1:
// PIO1 PortB: 無効
data = 0xff;
break;
case 2:
// PortC
data = lcd->Get();
data |= xp_intreq ? 0 : 0x02;
// 残りのビットは %1 だろうか?
data |= 0x0d;
putlog(1, "PortC -> $%02x", data.Data());
break;
case 3:
data = ReadCtrl();
putlog(1, "Ctrl -> $%02x", data.Data());
break;
default:
VMPANIC("corrupted offset=%u", offset);
}
// XXX wait?
data |= BusData::Size1;
return data;
}
busdata
PIO1Device::WritePort(uint32 offset, uint32 data)
{
switch (offset) {
case 0: // PIO1 PortA: LCD データ
lcd->Write(data);
break;
case 1: // PIO1 PortB: XP の INT0 をアサート
// データ不問でアサートする。
putlog(1, "Assert XP INT0");
mpu64180->AssertINT0();
break;
case 2: // PIO1 PortC: LCD/パワーオフ制御
for (int i = 0; i < 8; i++) {
SetPC(i, data & 1);
data >>= 1;
}
break;
case 3: // コントロール
WriteCtrl(data);
break;
default:
VMPANIC("corrupted offset=%u", offset);
}
// XXX wait?
busdata r = BusData::Size1;
return r;
}
busdata
PIO1Device::PeekPort(uint32 offset)
{
switch (offset) {
case 0: // PIO1 PortA LCD データ
return lcd->Peek();
case 1: // PIO1 PortB 無効
return 0xff;
case 2: // PIO1 PortC
return GetPC();
case 3:
return ReadCtrl();
default:
return 0xff;
}
}
// PIO0 の下請け。
void
PIO1Device::MonitorScreenPIO1(TextScreen& screen, int y)
{
screen.Puts(0, y++, "PIO1 (BaseAddr: $4d000000)");
// 共通部分
y = MonitorScreenCommon(screen, y++);
// PIO1 固有部
screen.Print(1, y++, "PortA(LCDDATA)=$%02x", lcd->Peek());
screen.Print(1, y++, "PortB(NotConn)=$%02x", 0xff);
uint32 pc = GetPC();
screen.Print(1, y, "PortC(LCD etc)=$%02x:", pc);
static const char * const pcstr[] = {
"LCD E", "LCD RS", "LCD RD", "!PWOFF",
"------", "------", "!XPINT", "------",
};
for (int i = 0; i < 8; i++) {
screen.Puts(21 + i * 7, y, TA::OnOff(pc & (1U << (7 - i))), pcstr[i]);
}
}
uint32
PIO1Device::GetPC() const
{
uint32 data;
data = lcd->Get();
data |= xp_intreq ? 0 : 0x02;
data |= power ? 0x10 : 0;
// 残りのビットは %1 だろうか?
data |= 0x0d;
return data;
}
void
PIO1Device::SetPC(uint pc, uint val)
{
switch (pc) {
case 0:
case 1:
case 2:
case 3:
// PortC の下半分は入力ポートとして使われることを想定して
// いるので、ここへの書き込みは起きないだろうし、無視する。
return;
case 4:
// 8255 はモード変更 (WriteCtrl) をするとポート C を 'L' にするが、
// PC4 (POWF*) が 'L' だと電源オフ指示なので、1msec 以内に
// ソフトウェアでこれをキャンセルしないといけない。
// どうしてこうなった…。
power = val;
if (power) {
// 電源オフ取り消し
putlog(1, "Power-off canceled");
powerdev->SetSystemPowerOn(true);
} else {
// 1msec 後に電源オフ
putlog(1, "Power-off scheduled");
powerdev->SetSystemPowerOn(false);
}
return;
case 5:
lcd->SetRW(val);
return;
case 6:
lcd->SetRS(val);
return;
case 7:
lcd->SetE(val);
return;
default:
VMPANIC("corrupted pc=%u", pc);
}
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.