|
|
nono 1.7.0
//
// nono
// Copyright (C) 2025 nono project
// Licensed under nono-license.txt
//
//
// HD647180 ASCI (非同期シリアル)
//
#include "hd647180asci.h"
#include "event.h"
#include "hostcom.h"
#include "monitor.h"
#include "mpu64180.h"
#include "scheduler.h"
// コンストラクタ
HD647180ASCIDevice::HD647180ASCIDevice()
: inherited(OBJ_XP_ASCI)
{
// 入力クロックφ= 6.144MHz だが、設定に関わらず必ず 160 分周は
// されるので、それをした後の値を基本周波数(周期)とする。
// 6.144MHz / 160 = 38400 [bps] = 26.04166... [usec/bit]
// 12倍すると 312.5 [usec]
xc12_ns = 312.5_usec;
for (uint i = 0; i < channels.size(); i++) {
channels[i].id = i;
}
monitor = gMonitorManager->Regist(ID_MONITOR_XPASCI, this);
monitor->SetCallback(&HD647180ASCIDevice::MonitorScreen);
monitor->SetSize(63, 12);
}
// デストラクタ
HD647180ASCIDevice::~HD647180ASCIDevice()
{
for (uint n = 0; n < channels.size(); n++) {
if ((bool)hostcom[n]) {
hostcom[n]->ResetRxCallback();
}
}
}
bool
HD647180ASCIDevice::Create()
{
static const char * const names[2] = {
"XP ASCI Ch.0",
"XP ASCI Ch.1",
};
// 対応するホストドライバを作成。
for (auto& chan : channels) {
uint n = chan.id;
try {
hostcom[n].reset(new HostCOMDevice(this, n + 1, names[n]));
} catch (...) { }
if ((bool)hostcom[n] == false) {
warnx("Failed to initialize HostCOMDevice at %s", __method__);
return false;
}
auto func = ToDeviceCallback(&HD647180ASCIDevice::HostRxCallback);
hostcom[n]->SetRxCallback(func, n);
}
return true;
}
// 初期化
bool
HD647180ASCIDevice::Init()
{
xp = GetMPU64180Device();
auto evman = GetEventManager();
for (auto& chan : channels) {
uint n = chan.id;
chan.tdr_empty = true;
chan.rdr_full = false;
scheduler->ConnectMessage(MessageID::HOSTCOM_RX(n + 1), this,
ToMessageCallback(&HD647180ASCIDevice::RxMessage));
txevent[n] = evman->Regist(this, n,
ToEventCallback(&HD647180ASCIDevice::TxCallback),
string_format("XP ASCI Ch%c TX", n + '0'));
rxevent[n] = evman->Regist(this, n,
ToEventCallback(&HD647180ASCIDevice::RxCallback),
string_format("XP ASCI Ch%c RX", n + '0'));
}
return true;
}
// リセット
void
HD647180ASCIDevice::ResetHard(bool poweron)
{
// TDR, RDR はリセットの影響を受けない。
// リセットで転送は止まる (.. are stopped during RESET) とは書いてあるが
// TSR, RSR がどうなるかは不明。さすがにリセットされるか。
nRTS0 = true;
cka1_disable = true;
for (auto& chan : channels) {
uint n = chan.id;
chan.rx_enable = false;
chan.tx_enable = false;
chan.rx_intr_enable = false;
chan.tx_intr_enable = false;
chan.data8bit = false;
chan.parity_en = false;
chan.stop2bit = false;
chan.parity_odd = false;
chan.prescale = false;
chan.divratio = false;
chan.ss = 7;
chan.nCTS = false; // ?
chan.tsr = -1;
chan.rsr = -1;
ChangeBaudRate(chan.id);
ChangeInterrupt(chan.id);
scheduler->StopEvent(txevent[n]);
scheduler->StopEvent(rxevent[n]);
}
}
uint32
HD647180ASCIDevice::ReadCNTLA(uint n)
{
uint32 data = PeekCNTLA(n);
putlog(3, "CNTLA%u -> $%02x", n, data);
return data;
}
uint32
HD647180ASCIDevice::ReadCNTLB(uint n)
{
uint32 data = PeekCNTLB(n);
putlog(3, "CNTLB%u -> $%02x", n, data);
return data;
}
uint32
HD647180ASCIDevice::ReadSTAT(uint n)
{
uint32 data = PeekSTAT(n);
putlog(3, "STAT%u -> $%02x", n, data);
return data;
}
uint32
HD647180ASCIDevice::ReadTDR(uint n)
{
// TDR は副作用なく読み出し可能。
uint32 data = PeekTDR(n);
putlog(3, "TDR%u -> $%02x", n, data);
return data;
}
uint32
HD647180ASCIDevice::ReadRDR(uint n)
{
Channel& chan = channels[n];
uint32 data = PeekRDR(n);
if (chan.rsr >= 0) {
chan.rdr = chan.rsr;
chan.rsr = -1;
} else {
chan.rdr_full = false;
}
putlog(4, "RDR%u -> $%02x", n, data);
return data;
}
uint32
HD647180ASCIDevice::WriteCNTLA(uint n, uint32 data)
{
Channel& chan = channels[n];
std::string msg;
if ((data & CNTL_MPE)) {
putlog(0, "MPE=1 (NOT IMPLEMENTED)");
}
bool newrxe = (data & CNTL_RE);
if (chan.rx_enable != newrxe) {
chan.rx_enable = newrxe;
if (__predict_false(loglevel >= 1)) {
msg += string_format(" RX=%s", (newrxe ? "Enable" : "Disable"));
}
}
bool newtxe = (data & CNTL_TE);
if (chan.tx_enable != newtxe) {
chan.tx_enable = newtxe;
if (__predict_false(loglevel >= 1)) {
msg += string_format(" TX=%s", (newtxe ? "Enable" : "Disable"));
}
}
if (n == 0) {
nRTS0 = (data & CNTL_nRTS0);
} else {
cka1_disable = (data & CNTL_CKA1D);
if (cka1_disable == false) {
putlog(0, "CKA1D=0 (NOT IMPLEMENTED)");
}
}
if ((data & CNTL_EFR)) {
// OVRN, FE, PE をクリアする。
chan.overrun = false;
ChangeInterrupt(n);
}
bool new8bit = (data & CNTL_DATA8);
bool newpen = (data & CNTL_PAREN);
bool newstop = (data & CNTL_STOP2);
if (chan.data8bit != new8bit ||
chan.parity_en != newpen ||
chan.stop2bit != newstop)
{
chan.data8bit = new8bit;
chan.parity_en = newpen;
chan.stop2bit = newstop;
ChangeBaudRate(n);
if (__predict_false(loglevel >= 1)) {
msg += string_format(" data=%u,stop=%u,parity=%s",
(chan.data8bit ? 8 : 7),
(chan.stop2bit ? 2 : 1),
(chan.parity_en ? "on" : "off"));
}
}
if (__predict_false(loglevel >= 1)) {
// ログレベル1なら状態が変わった時だけ、2ならすべて表示。
int req;
if (msg.empty()) {
// 状態が変わっていない。
req = 2;
} else {
req = 1;
msg[0] = '(';
msg += ')';
}
putlog(req, "CNTLA%u <- $%02x %s", n, data, msg.c_str());
}
return 0;
}
uint32
HD647180ASCIDevice::WriteCNTLB(uint n, uint32 data)
{
Channel& chan = channels[n];
std::string msg;
if ((data & (CNTL_MPBT | CNTL_MP))) {
putlog(0, "MPBT|MP=1 (NOT IMPLEMENTED)");
}
bool newpo = (data & CNTL_PEO);
if (chan.parity_odd != newpo) {
chan.parity_odd = newpo;
if (__predict_false(loglevel >= 1)) {
msg += string_format(" Parity=%s", (newpo ? "Odd" : "Even"));
}
}
bool newps = (data & CNTL_PS);
bool newdr = (data & CNTL_DR);
uint32 newss = (data & CNTL_SS);
if (chan.prescale != newps ||
chan.divratio != newdr ||
chan.ss != newss)
{
chan.prescale = newps;
chan.divratio = newdr;
chan.ss = newss;
ChangeBaudRate(n);
if (__predict_false(loglevel >= 1)) {
msg += string_format(" PS=%u DR=%u SS=%u",
(uint)newps, (uint)newdr, newss);
if (chan.baudrate > 0) {
msg += string_format(", %u bps", chan.baudrate);
}
}
}
if (__predict_false(loglevel >= 1)) {
// ログレベル1なら状態が変わった時だけ、2ならすべて表示。
int req;
if (msg.empty()) {
// 状態が変わっていない。
req = 2;
} else {
req = 1;
msg[0] = '(';
msg += ')';
}
putlog(req, "CNTLB%u <- $%02x %s", n, data, msg.c_str());
}
return 0;
}
uint32
HD647180ASCIDevice::WriteSTAT(uint n, uint32 data)
{
Channel& chan = channels[n];
std::string msg;
if (n == 1) {
cts1_enable = (data & STAT_CTS1E);
if (cts1_enable) {
putlog(0, "CTS1E=1 (NOT IMPLEMENTED)");
}
}
bool newrie = (data & STAT_RIE);
if (chan.rx_intr_enable != newrie) {
chan.rx_intr_enable = newrie;
ChangeInterrupt(n);
msg += string_format(" RIE=%s", newrie ? "Enable" : "Disable");
}
bool newtie = (data & STAT_TIE);
if (chan.tx_intr_enable != newtie) {
chan.tx_intr_enable = newtie;
ChangeInterrupt(n);
msg += string_format(" TIE=%s", newrie ? "Enable" : "Disable");
}
if (__predict_false(loglevel >= 1)) {
// ログレベル1なら状態が変わった時だけ、2ならすべて表示。
int req;
if (msg.empty()) {
// 状態が変わっていない。
req = 2;
} else {
req = 1;
msg[0] = '(';
msg += ')';
}
putlog(req, "STAT%u <- $%02x %s", n, data, msg.c_str());
}
return 0;
}
uint32
HD647180ASCIDevice::WriteTDR(uint n, uint32 data)
{
Channel& chan = channels[n];
// どちらにしても一旦 TDR には置く。
chan.tdr = data;
putlog(4, "TDR%u <- $%02x", n, data);
// TDR TSR TDR TSR
// --- --- --- ---
// なし なし -> なし data イベント始動
// なし 有り -> data 有り (イベントは動いてるはず)
// 有り なし (すぐ1行上に遷移するのでこの状態にはならない)
// 有り 有り -> data 有り
if (chan.tsr < 0) {
// TSR が空なら即コピーして、送信イベント開始。
chan.tsr = chan.tdr;
chan.tdr_empty = true;
if (txevent[n]->IsRunning() == false) {
scheduler->StartEvent(txevent[n]);
}
} else {
// TSR が埋まっていれば何もしない。(イベントは動いてるはず)
chan.tdr_empty = false;
}
ChangeInterrupt(n);
return 0;
}
uint32
HD647180ASCIDevice::WriteRDR(uint n, uint32 data)
{
Channel& chan = channels[n];
// RDR が空の時に限り、書き込み出来る。(p.69)
if (chan.rdr_full) {
putlog(1, "RDR%u <- $%02x (Write Ignored)", n, data);
} else {
chan.rdr = data;
putlog(4, "RDR%u <- $%02x", n, data);
}
return 0;
}
uint32
HD647180ASCIDevice::PeekCNTLA(uint n) const
{
const Channel& chan = channels[n];
uint32 data = 0;
// MPE Not implemented
if (chan.rx_enable) {
data |= CNTL_RE;
}
if (chan.tx_enable) {
data |= CNTL_TE;
}
if (n == 0) {
if (nRTS0) {
data |= CNTL_nRTS0;
}
} else {
if (cka1_disable) {
data |= CNTL_CKA1D;
}
}
if (chan.data8bit) {
data |= CNTL_DATA8;
}
if (chan.parity_en) {
data |= CNTL_PAREN;
}
if (chan.stop2bit) {
data |= CNTL_STOP2;
}
return data;
}
uint32
HD647180ASCIDevice::PeekCNTLB(uint n) const
{
const Channel& chan = channels[n];
uint32 data = 0;
// MPBT, MP: Not implemented
if (chan.nCTS) {
data |= CNTL_nCTS;
}
if (chan.parity_odd) {
data |= CNTL_PEO;
}
if (chan.divratio) {
data |= CNTL_DR;
}
data |= chan.ss;
return data;
}
uint32
HD647180ASCIDevice::PeekSTAT(uint n) const
{
const Channel& chan = channels[n];
uint32 data = 0;
if (chan.rdr_full) {
data |= STAT_RDRF;
}
if (chan.overrun) {
data |= STAT_OVRN;
}
// PE, FE は立たない。
if (chan.rx_intr_enable) {
data |= STAT_RIE;
}
if (n == 0) {
if (nDCD0) {
data |= STAT_nDCD0;
}
} else {
if (cts1_enable) {
data |= STAT_CTS1E;
}
}
if (chan.tdr_empty) {
data |= STAT_TDRE;
}
if (chan.tx_intr_enable) {
data |= STAT_TIE;
}
return data;
}
uint32
HD647180ASCIDevice::PeekTDR(uint n) const
{
const Channel& chan = channels[n];
return chan.tdr;
}
uint32
HD647180ASCIDevice::PeekRDR(uint n) const
{
const Channel& chan = channels[n];
return chan.rdr;
}
void
HD647180ASCIDevice::MonitorScreen(Monitor *, TextScreen& screen)
{
screen.Clear();
MonitorScreenChan(screen, 0, 0);
MonitorScreenChan(screen, 6, 1);
}
void
HD647180ASCIDevice::MonitorScreenChan(TextScreen& screen, int y, int n) const
{
Channel tmp = channels[n];
uint32 cntla = PeekCNTLA(n);
uint32 cntlb = PeekCNTLB(n);
uint32 stat = PeekSTAT(n);
int x;
screen.Print(0, y++, "<Ch.%c>", n + '0');
x = 16;
screen.Print(0, y, "%02XH CNTLA%u=$%02x:", 0 + n, n, cntla);
screen.Puts(x + 0, y, TA::OnOff(cntla & 0x80), "MPE");
screen.Puts(x + 6, y, TA::OnOff(cntla & 0x40), "RE");
screen.Puts(x + 12, y, TA::OnOff(cntla & 0x20), "TE");
screen.Puts(x + 18, y, TA::OnOff(cntla & 0x10),
(n == 0 ? "!RTS0" : "CKA1D"));
screen.Puts(x + 24, y, TA::OnOff(cntla & 0x08), "MPBR");
screen.Puts(x + 30, y, TA::OnOff(cntla & 0x04), "8BIT");
screen.Puts(x + 36, y, TA::OnOff(cntla & 0x02), "ParEn");
screen.Puts(x + 42, y, TA::OnOff(cntla & 0x01), "STOP2");
y++;
screen.Print(0, y, "%02XH CNTLB%u=$%02x:", 2 + n, n, cntlb);
screen.Puts(x + 0, y, TA::OnOff(cntlb & 0x80), "MPBT");
screen.Puts(x + 6, y, TA::OnOff(cntlb & 0x40), "MP");
screen.Puts(x + 12, y, TA::OnOff(cntlb & 0x20), "!CTS");
screen.Puts(x + 18, y, TA::OnOff(cntlb & 0x10), "PEO");
screen.Print(x + 24, y, "DR=%u", tmp.divratio ? 1 : 0);
screen.Print(x + 30, y, "SS[210]=%u", (cntlb & 0x07));
screen.Print(x + 42, y, "PS=%u", tmp.prescale ? 1 : 0);
y++;
screen.Print(0, y, "%02XH STAT%u =$%02x:", 4 + n, n, stat);
screen.Puts(x + 0, y, TA::OnOff(stat & 0x80), "RDRF");
screen.Puts(x + 6, y, TA::OnOff(stat & 0x40), "OVRN");
screen.Puts(x + 12, y, TA::OnOff(stat & 0x20), "PE");
screen.Puts(x + 18, y, TA::OnOff(stat & 0x10), "FE");
screen.Puts(x + 24, y, TA::OnOff(stat & 0x08), "RIE");
screen.Puts(x + 30, y, TA::OnOff(stat & 0x04),
(n == 0 ? "!DCD0" : "CTS1E"));
screen.Puts(x + 36, y, TA::OnOff(stat & 0x02), "TDRE");
screen.Puts(x + 42, y, TA::OnOff(stat & 0x01), "TIE");
y++;
screen.Print(0, y, "%02XH TDR%u =", 6 + n, n);
screen.Print(11, y, (tmp.tdr_empty ? TA::Disable : TA::Normal),
"$%02x", tmp.tdr);
screen.Puts(x, y, "TSR:");
if (__predict_true(tmp.tsr < 0)) {
screen.Puts(x + 4, y, TA::Disable, "---");
} else {
screen.Print(x + 4, y, "$%02x", tmp.tsr);
}
y++;
screen.Print(0, y, "%02XH RDR%u =", 8 + n, n);
screen.Print(11, y, (tmp.rdr_full ? TA::Normal : TA::Disable),
"$%02x", tmp.rdr);
screen.Puts(x, y, "RSR:");
if (__predict_true(tmp.rsr < 0)) {
screen.Puts(x + 4, y, TA::Disable, "---");
} else {
screen.Print(x + 4, y, "$%02x", tmp.rsr);
}
// Param: 19200,N,8,2
screen.Puts(x + 29, y, "Param:");
if (tmp.baudrate == 0) {
screen.Puts(x + 36, y, "(ExtClock)");
} else {
char p;
if (tmp.parity_en) {
p = tmp.parity_odd ? 'O' : 'E';
} else {
p = 'N';
}
screen.Print(x + 36, y, "%5u,%c,%u,%u",
tmp.baudrate, p, (tmp.data8bit ? 8 : 7), (tmp.stop2bit ? 2 : 1));
}
}
// 割り込み状態を更新する。
// RDFn, OVRNn, RIEn, TDREn, TIEn のいずれかでも変更したら呼ぶこと。
void
HD647180ASCIDevice::ChangeInterrupt(uint n)
{
// p.77 Fig 2.10.3 より、片方のチャンネルの割り込み要求ロジック。
// ____
// DCD0(*) --|OR (*) Ch0 のみ
// RDRFn ----|OR
// OVRNn ----|OR-----|& : IEF1
// PEn -----|OR |& : |
// FEn -----|OR |&-----|OR : |
// |& |OR : +--|&
// RIEn -----|& |OR : |&----> ASCIn Int
// |OR-----------|&
// TDREn ----|& |OR :
// |&-----|OR :
// TIEn -----|& :
// :
// この関数でやってるのはここまで : こっちは xp->ChangeASCIInt()
const Channel& chan = channels[n];
// PE, FE (, !DCD0) はない。
bool rcond = chan.rdr_full || chan.overrun;
bool rintr = rcond && chan.rx_intr_enable;
bool tintr = chan.tdr_empty && chan.tx_intr_enable;
bool intr = (rintr || tintr);
// IEF1 は向こうで処理している。
xp->ChangeASCIInt(n, intr);
}
static double
to_freq(uint64 nsec)
{
return (double)1e9 / nsec;
}
// 現在の Prescale, DivideRatio, SS からボーレートを再計算する。
// ここで 1文字分の時間も計算するため、CNTLA の MOD が変わっても呼ぶこと。
void
HD647180ASCIDevice::ChangeBaudRate(uint n)
{
Channel& chan = channels[n];
if (__predict_false(chan.ss == 7)) {
chan.baudrate = 0;
} else {
uint d = (1U << chan.ss);
// PreScaler で 30 か 10 分周が指定出来るが、このうち 10 は計算済み。
if (chan.prescale) {
d *= 3;
}
// DivideRatio で 64 か 16 分周が指定できるが、このうち 16 は計算済み。
if (chan.divratio) {
d *= 4;
}
chan.bit12_ns = xc12_ns * d;
chan.baudrate = to_freq(chan.bit12_ns / 12);
// 1文字の送受信にかかる時間。
uint bits = 1/*start*/ + 7/*data*/ + 1/*stop*/;
if (chan.data8bit) {
bits++;
}
if (chan.parity_en) {
bits++;
}
if (chan.stop2bit) {
bits++;
}
uint64 time = chan.bit12_ns * bits / 12;
txevent[n]->time = time;
rxevent[n]->time = time;
}
}
// 送信イベント。
void
HD647180ASCIDevice::TxCallback(Event *ev)
{
uint n = ev->code;
Channel& chan = channels[n];
// 1文字分の時間が経過したので TSR の文字をホストに送信。
hostcom[n]->Tx(chan.tsr);
if (chan.tdr_empty) {
// 次がなければ終わり。
chan.tsr = -1;
} else {
// 次の文字がある。
chan.tsr = chan.tdr;
chan.tdr_empty = true;
ChangeInterrupt(n);
scheduler->StartEvent(ev);
}
}
// ホストスレッドからの受信通知。
// (HostCOM スレッドで呼ばれる)
void
HD647180ASCIDevice::HostRxCallback(uint32 arg)
{
// XXX mpscc.cpp の同関数のコメント参照。
uint n = arg;
const Event *ev = rxevent[n];
if (ev->IsRunning() == false) {
// スレッドを超えるためにメッセージを投げる。
scheduler->SendMessage(MessageID::HOSTCOM_RX(n + 1));
}
}
// ホストシリアルからの1バイト受信メッセージ
// (VM スレッドで呼ばれる)
void
HD647180ASCIDevice::RxMessage(MessageID msgid, uint32 arg)
{
uint n = msgid - MessageID::HOSTCOM1_RX;
Event *ev = rxevent[n];
// 受信ループが止まっていれば開始。
if (ev->IsRunning() == false) {
scheduler->StartEvent(ev);
}
}
// 受信イベントコールバック。
void
HD647180ASCIDevice::RxCallback(Event *ev)
{
uint n = ev->code;
int data;
data = hostcom[n]->Rx();
if (data >= 0) {
Rx(n, data);
scheduler->StartEvent(ev);
}
}
// 1バイト受信する。
bool
HD647180ASCIDevice::Rx(uint n, uint32 data)
{
Channel& chan = channels[n];
// Rx Enable でなければ受け取らない?
if (chan.rx_enable == false) {
return false;
}
// 受信前 受信後の状態
// RDR RSR RDR RSR
// --- --- --- ---
// なし なし -> data なし
// なし 有り (すぐに1行下の状態に遷移するためこの状態はない)
// 有り なし -> 有り data
// 有り 有り -> 有り 有り (Overrun)
// バッファが一杯なら Overrun。
if (chan.rdr_full && chan.rsr >= 0) {
putlog(2, "Ch%u Rx Overrun", n);
chan.overrun = true;
ChangeInterrupt(n);
return false;
}
// 受信。
putlog(2, "Ch%u recv $%02x", n, data);
if (chan.rdr_full) {
chan.rsr = data;
} else {
chan.rdr = data;
chan.rdr_full = true;
chan.rsr = -1;
ChangeInterrupt(n);
}
return true;
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.