Cycle-accurate Paula serial port emulation.

This commit is contained in:
Toni Wilen 2022-09-16 20:50:25 +03:00
parent ffac943c03
commit 5721bcfc70
7 changed files with 264 additions and 75 deletions

View File

@ -7720,7 +7720,7 @@ void rethink_uae_int(void)
static void rethink_intreq(void)
{
serial_check_irq();
serial_rethink();
devices_rethink();
}

View File

@ -399,6 +399,22 @@ void event2_newevent_xx (int no, evt_t t, uae_u32 data, evfunc2 func)
MISC_handler();
}
void event2_newevent_x_replace_exists(evt_t t, uae_u32 data, evfunc2 func)
{
for (int i = 0; i < ev2_max; i++) {
if (eventtab2[i].active && eventtab2[i].handler == func) {
eventtab2[i].active = false;
if (t <= 0) {
func(data);
return;
}
event2_newevent_xx(-1, t * CYCLE_UNIT, data, func);
return;
}
}
}
void event2_newevent_x_replace(evt_t t, uae_u32 data, evfunc2 func)
{
for (int i = 0; i < ev2_max; i++) {

View File

@ -133,6 +133,7 @@ STATIC_INLINE bool cycles_in_range(evt_t endcycles)
extern void MISC_handler(void);
extern void event2_newevent_xx(int no, evt_t t, uae_u32 data, evfunc2 func);
extern void event2_newevent_x_replace(evt_t t, uae_u32 data, evfunc2 func);
extern void event2_newevent_x_replace_exists(evt_t t, uae_u32 data, evfunc2 func);
STATIC_INLINE void event2_newevent_x(int no, evt_t t, uae_u32 data, evfunc2 func)
{

View File

@ -32,8 +32,8 @@ extern int doreadser, serstat;
extern void serial_flush_buffer (void);
extern void serial_hsynchandler (void);
extern void serial_check_irq (void);
extern void serial_hsynchandler(void);
extern void serial_rethink(void);
extern int uaeser_getdatalength (void);
extern int uaeser_getbytespending (void*);

View File

@ -1604,6 +1604,13 @@ int enumserialports (void)
comports[cnt]->name = my_strdup (_T("WinUAE inter-process serial port"));
cnt++;
}
if (cnt < MAX_SERPAR_PORTS) {
comports[cnt] = xcalloc(struct serparportinfo, 1);
comports[cnt]->dev = my_strdup (SERIAL_LOOPBACK);
comports[cnt]->cfgname = my_strdup (comports[cnt]->dev);
comports[cnt]->name = my_strdup (_T("WinUAE loopback serial port"));
cnt++;
}
if (cnt < MAX_SERPAR_PORTS) {
comports[cnt] = xcalloc(struct serparportinfo, 1);

View File

@ -32,6 +32,7 @@ bool shmem_serial_create(void);
int shmem_serial_state(void);
#define SERIAL_INTERNAL _T("INTERNAL_SERIAL")
#define SERIAL_LOOPBACK _T("LOOPBACK_SERIAL")
#define TIOCM_CAR 1
#define TIOCM_DSR 2

View File

@ -52,6 +52,9 @@ static HANDLE sermap_handle;
static uae_u8 *sermap_data;
static bool sermap_master;
static bool sermap_enabled;
static bool serloop_enabled;
static bool serempty_enabled;
static uae_u8 serstatus;
#define SER_MEMORY_MAPPING _T("WinUAE_Serial")
@ -164,12 +167,17 @@ bool shmem_serial_create(void)
#endif
static int data_in_serdat; /* new data written to SERDAT */
static evt_t data_in_serdat_delay;
static evt_t serper_tx_evt;
static int serper_tx_per, serper_tx_bits, serper_tx_cycles;
static int data_in_serdatr; /* new data received */
static int data_in_sershift; /* data transferred from SERDAT to shift register */
static int break_in_serdatr; /* break state */
static int break_delay;
static uae_u16 serdatshift; /* serial shift register */
static int serdatshift_bits;
static uae_u16 serdatshift_masked; /* stop bit masked */
static evt_t serdatshift_start;
static int ovrun;
static int dtr;
static int serial_period_hsyncs, serial_period_hsync_counter;
@ -189,28 +197,32 @@ void serial_open (void);
void serial_close (void);
uae_u16 serper, serdat, serdatr;
static bool serper_set = false;
static const int allowed_baudrates[] =
{ 0, 110, 300, 600, 1200, 2400, 4800, 9600, 14400,
19200, 31400, 38400, 57600, 115200, 128000, 256000, -1 };
{
0, 110, 300, 600, 1200, 2400, 4800, 9600, 14400,
19200, 31400, 38400, 57600, 115200, 128000, 256000, -1
};
static void sersend_end(uae_u32 v);
void SERPER (uae_u16 w)
{
int baud = 0, i, per;
int oldper = serper;
static int warned;
if (serper == w) /* don't set baudrate if it's already ok */
if (serper == w && serper_set) /* don't set baudrate if it's already ok */
return;
serper_set = true;
ninebit = 0;
serper = w;
if (w & 0x8000)
ninebit = 1;
w &= 0x7fff;
if (w < 13)
w = 13;
per = w;
if (per == 0)
per = 1;
@ -221,6 +233,9 @@ void SERPER (uae_u16 w)
while (allowed_baudrates[i] >= 0 && per > allowed_baudrates[i] * 100 / 97)
i++;
baud = allowed_baudrates[i];
if (baud <= 0) {
baud = allowed_baudrates[1];
}
serial_period_hsyncs = (((serper & 0x7fff) + 1) * (1 + 8 + ninebit + 1 - 1)) / maxhpos;
if (serial_period_hsyncs <= 0)
@ -239,7 +254,9 @@ void SERPER (uae_u16 w)
serial_period_hsync_counter = 0;
write_log (_T("SERIAL: period=%d, baud=%d, hsyncs=%d, bits=%d, PC=%x\n"), w, baud, serial_period_hsyncs, ninebit ? 9 : 8, M68K_GETPC);
if (!serloop_enabled || seriallog > 0) {
write_log(_T("SERIAL: period=%d, baud=%d, hsyncs=%d, bits=%d, PC=%x\n"), w, baud, serial_period_hsyncs, ninebit ? 9 : 8, M68K_GETPC);
}
if (ninebit)
baud *= 2;
@ -248,11 +265,53 @@ void SERPER (uae_u16 w)
baud = 115200;
serial_period_hsyncs = 1;
}
serial_recv_previous = -1;
serial_send_previous = -1;
#ifdef SERIAL_PORT
setbaud (baud);
#endif
// mid transmit period change
if ((serloop_enabled || serempty_enabled) && currprefs.cpu_memory_cycle_exact) {
evt_t c = get_cycles();
evt_t n = serper_tx_evt + serper_tx_cycles * CYCLE_UNIT;
if (n > c) {
int cycles_transmitted = (int)((c - serper_tx_evt) / CYCLE_UNIT);
serper_tx_cycles -= cycles_transmitted;
if (serper_tx_cycles >= 0) {
int serper_tx_cycles_mod = serper_tx_cycles % serper_tx_per;
serper_tx_cycles /= serper_tx_per;
serper_tx_per = (serper & 0x7fff) + 1;
serper_tx_cycles *= serper_tx_per;
serper_tx_cycles += serper_tx_cycles_mod;
serper_tx_evt = c;
event2_newevent_x_replace_exists(serper_tx_cycles, 0, sersend_end);
}
}
}
}
static void serial_rx_irq(void)
{
// Data in receive buffer
data_in_serdatr = 1;
if (currprefs.cpu_memory_cycle_exact) {
INTREQ_INT(11, 9);
} else {
INTREQ_INT(11, 0);
}
}
void serial_rethink(void)
{
if (!currprefs.cpu_memory_cycle_exact) {
if (data_in_serdatr) {
INTREQ_0(0x8000 | (1 << 11));
}
}
}
static TCHAR docharlog(int v)
@ -310,9 +369,8 @@ static void checkreceive_enet (void)
serdatr |= 0x200;
else
serdatr |= 0x100;
data_in_serdatr = 1;
serdatr_last_got = 0;
serial_check_irq ();
serial_rx_irq();
#if SERIALDEBUG > 2
write_log (_T("SERIAL: received %02X (%c)\n"), serdatr & 0xff, dochar (serdatr));
#endif
@ -450,9 +508,8 @@ static void checkreceive_serial (void)
}
}
data_in_serdatr = 1;
serdatr_last_got = 0;
serial_check_irq();
serial_rx_irq();
#if SERIALDEBUG > 2
write_log(_T("SERIAL: received %02X (%c)\n"), serdatr & 0xff, dochar(serdatr));
#endif
@ -464,8 +521,12 @@ static void serdatcopy(void);
static void checksend(void)
{
if (data_in_sershift != 1 && data_in_sershift != 2)
if (data_in_sershift != 1 && data_in_sershift != 2) {
return;
}
if (serloop_enabled) {
return;
}
#ifdef ARCADIA
if (alg_flag || currprefs.genlock_image >= 7) {
@ -475,6 +536,9 @@ static void checksend(void)
if (cubo_enabled) {
touch_serial_read(serdatshift);
}
if (serempty_enabled) {
return;
}
#ifdef SERIAL_MAP
if (sermap_data && sermap_enabled)
shmem_serial_send(serdatshift);
@ -533,6 +597,22 @@ static bool checkshiftempty(void)
return false;
}
static void sersend_end(uae_u32 v)
{
data_in_sershift = 0;
serdatcopy();
data_in_serdat = 0;
data_in_serdat_delay = get_cycles() + CYCLE_UNIT;
}
static void sersend_serloop(uae_u32 v)
{
serdatr = serdatshift;
serial_rx_irq();
event2_newevent_xx(-1, v * CYCLE_UNIT, 0, sersend_end);
}
static void sersend_ce(uae_u32 v)
{
if (checkshiftempty()) {
@ -547,15 +627,15 @@ static void serdatcopy(void)
{
int bits;
if (data_in_sershift || !data_in_serdat)
if (data_in_sershift > 0 || !data_in_serdat)
return;
serdatshift = serdat;
bits = 8;
if ((serdatshift & 0xff80) == 0x80) {
bits = 7;
}
serdatshift_start = get_cycles();
serdatshift_masked = serdatshift & ((1 << bits) - 1);
data_in_sershift = 1;
data_in_serdat = 0;
if (seriallog > 0 || (consoleopen && seriallog < 0)) {
@ -578,32 +658,58 @@ static void serdatcopy(void)
}
}
if (data_in_sershift == 0) {
INTREQ_INT(0, 1);
}
data_in_sershift = 1;
serdatshift_bits = 16;
for (int i = 15; i >= 0; i--) {
if (serdatshift & (1 << i))
break;
serdatshift_bits--;
}
// if someone uses serial port as some kind of timer..
if (currprefs.cpu_memory_cycle_exact) {
int per;
int sper = (serper & 0x7fff) + 1;
int per = sper * (serdatshift_bits + 1);
bits = 16 + 1;
for (int i = 15; i >= 0; i--) {
if (serdatshift & (1 << i))
break;
bits--;
serper_tx_evt = get_cycles();
serper_tx_per = sper;
serper_tx_bits = serdatshift_bits + 1;
serper_tx_cycles = per;
// not connected, emulate only TX
if (serempty_enabled) {
event2_newevent_xx(-1, per * CYCLE_UNIT, 0, sersend_end);
return;
}
// assuming when last bit goes out, transmit buffer
// becomes empty, not when last bit has finished
// transmitting.
per = ((serper & 0x7fff) + 1) * (bits - 1);
// loopback receive complete
if (serloop_enabled) {
int recper = sper * (ninebit ? 10 : 9) + (sper - 1) / 2;
int perdiff = per - recper;
if (perdiff < 0) {
perdiff = 0;
}
// TX to RX delay (1488/1489 chip delays)
recper += 3;
event2_newevent_xx(-1, recper * CYCLE_UNIT, perdiff, sersend_serloop);
return;
}
if (lastbitcycle_active_hsyncs) {
// if last bit still transmitting, add remaining time.
int extraper = (int)(lastbitcycle - get_cycles());
if (extraper > 0)
per += extraper / CYCLE_UNIT;
int extraper = (int)((lastbitcycle - get_cycles()) / CYCLE_UNIT);
per += extraper;
}
if (per < 4)
per = 4;
event2_newevent_x_replace(per, 0, sersend_ce);
}
INTREQ(0x8000 | 0x0001);
checksend();
}
@ -618,9 +724,8 @@ void serial_hsynchandler (void)
int ch = ld_serial_write();
if (ch >= 0) {
serdatr = ch | 0x100;
data_in_serdatr = 1;
serdatr_last_got = 0;
serial_check_irq ();
serial_rx_irq();
}
}
#endif
@ -628,17 +733,15 @@ void serial_hsynchandler (void)
int ch = touch_serial_write();
if (ch >= 0) {
serdatr = ch | 0x100;
data_in_serdatr = 1;
serdatr_last_got = 0;
serial_check_irq();
serial_rx_irq();
}
}
if (seriallog > 1 && !data_in_serdatr && gotlogwrite) {
int ch = read_log();
if (ch > 0) {
serdatr = ch | 0x100;
data_in_serdatr = 1;
serial_check_irq ();
serial_rx_irq();
}
}
@ -649,8 +752,7 @@ void serial_hsynchandler (void)
uae_u16 v = shmem_serial_receive();
if (v != 0xffff) {
serdatr = v;
data_in_serdatr = 1;
serial_check_irq();
serial_rx_irq();
}
}
#endif
@ -677,50 +779,83 @@ void serial_hsynchandler (void)
}
}
void SERDAT (uae_u16 w)
static void SERDAT_send(uae_u32 v)
{
uae_u16 w = (uae_u16)v;
#if SERIALDEBUG > 2
write_log(_T("SERIAL: SERDAT write 0x%04x (%c) PC=%x\n"), w, dochar(w), M68K_GETPC);
#endif
serdatcopy();
if (currprefs.cpu_memory_cycle_exact) {
serdat = w;
data_in_serdat = 1;
if (!data_in_sershift) {
if (serloop_enabled || serempty_enabled) {
data_in_sershift = -1;
INTREQ_INT(0, 1);
}
serdatcopy();
}
serdat = w;
} else {
serdatcopy();
if (!w) {
serdat = w;
if (!w) {
#if SERIALDEBUG > 1
write_log (_T("SERIAL: zero serial word written?! PC=%x\n"), M68K_GETPC);
write_log (_T("SERIAL: zero serial word written?! PC=%x\n"), M68K_GETPC);
#endif
return;
}
return;
}
#if SERIALDEBUG > 1
if (data_in_serdat) {
write_log (_T("SERIAL: program wrote to SERDAT but old byte wasn't fetched yet\n"));
}
if (data_in_serdat) {
write_log (_T("SERIAL: program wrote to SERDAT but old byte wasn't fetched yet\n"));
}
#endif
data_in_serdat = 1;
serdatcopy();
data_in_serdat = 1;
serdatcopy();
}
}
uae_u16 SERDATR(void)
{
serdatr &= 0x03ff;
if (!data_in_serdat) {
serdatr |= 0x2000;
if (!data_in_serdat && get_cycles() >= data_in_serdat_delay) {
serdatr |= 0x2000; // TBE (Transmit buffer empty)
}
if (!data_in_sershift) {
serdatr |= 0x1000;
if (!data_in_sershift && (serdatr & 0x2000)) {
serdatr |= 0x1000; // TSRE (Transmit shift register empty)
}
if (data_in_serdatr) {
serdatr |= 0x4000;
serdatr |= 0x4000; // RBF (Receive buffer full)
}
if (ovrun) {
serdatr |= 0x8000;
serdatr |= 0x8000; // OVRUN (Receiver overrun)
}
if (break_in_serdatr <= 0) {
serdatr |= 0x0800;
if (serloop_enabled) {
int per = ((serper & 0x7fff) + 1) * CYCLE_UNIT;
evt_t sper = per * serdatshift_bits;
evt_t c = get_cycles();
evt_t diff = serdatshift_start + sper - c;
if (break_in_serdatr < 0) {
serdatr |= 0x0800;
} else if (diff > 0) {
int bit = (int)(c - serdatshift_start) / per;
if (bit > 0 && !(serdatshift & (1 << (bit - 1)))) {
serdatr |= 0x0800;
}
} else {
if (break_in_serdatr <= 0) {
serdatr |= 0x0800;
}
}
} else {
if (break_in_serdatr <= 0) {
serdatr |= 0x0800; // RXD
}
}
#if SERIALDEBUG > 2
write_log (_T("SERIAL: read 0x%04x (%c) %x\n"), serdatr, dochar (serdatr), M68K_GETPC);
@ -729,18 +864,20 @@ uae_u16 SERDATR(void)
return serdatr;
}
void SERDAT(uae_u16 w)
{
if (currprefs.cpu_memory_cycle_exact) {
event2_newevent_xx(-1, 1 * CYCLE_UNIT, w, SERDAT_send);
} else {
SERDAT_send(w);
}
}
void serial_rbf_clear(void)
{
ovrun = 0;
}
void serial_check_irq (void)
{
// Data in receive buffer
if (data_in_serdatr)
INTREQ_0(0x8000 | 0x0800);
}
void serial_dtr_on (void)
{
#if SERIALHSDEBUG > 0
@ -787,9 +924,19 @@ uae_u8 serial_readstatus (uae_u8 dir)
int status = 0;
uae_u8 serbits = oldserbits;
if (serloop_enabled) {
if (serstatus & 0x80) { // DTR -> DSR + CD
status |= TIOCM_DSR | TIOCM_CAR;
}
if (serstatus & 0x10) { // RTS -> CTS
status |= TIOCM_CTS;
}
} else {
#ifdef SERIAL_PORT
getserstat (&status);
getserstat(&status);
#endif
}
if (!(status & TIOCM_CAR)) {
if (!(serbits & 0x20)) {
serbits |= 0x20;
@ -851,6 +998,8 @@ uae_u8 serial_writestatus (uae_u8 newstate, uae_u8 dir)
{
static int logcnt = 10;
serstatus = newstate & dir;
#ifdef SERIAL_PORT
if (((oldserbits ^ newstate) & 0x80) && (dir & 0x80)) {
if (newstate & 0x80)
@ -925,6 +1074,10 @@ void serial_open (void)
} else if (!_tcsicmp(currprefs.sername, SERIAL_INTERNAL)) {
sermap_enabled = true;
#endif
} else if (!_tcsicmp(currprefs.sername, SERIAL_LOOPBACK)) {
serloop_enabled = true;
} else if (!currprefs.sername[0]) {
serempty_enabled = true;
} else {
if(!openser (currprefs.sername)) {
write_log (_T("SERIAL: Could not open device %s\n"), currprefs.sername);
@ -935,27 +1088,32 @@ void serial_open (void)
#endif
}
void serial_close (void)
void serial_close(void)
{
#ifdef SERIAL_PORT
closeser ();
closeser();
#ifdef SERIAL_ENET
enet_close ();
enet_close();
#endif
serdev = 0;
#ifdef SERIAL_MAP
sermap_deactivate();
#endif
#endif
serloop_enabled = false;
serempty_enabled = false;
serper_set = false;
}
void serial_init (void)
void serial_init(void)
{
#ifdef SERIAL_PORT
if (!currprefs.use_serial)
if (!currprefs.use_serial) {
serial_open();
return;
}
if (!currprefs.serial_demand)
serial_open ();
serial_open();
#endif
}
@ -966,6 +1124,12 @@ void serial_exit (void)
#endif
dtr = 0; /* just closes *opened* filehandles which is ok */
oldserbits = 0; /* when exiting. */
data_in_serdat = 0;
data_in_serdatr = 0;
data_in_sershift = 0;
break_in_serdatr = 0;
ovrun = 0;
serdatr_last_got = 0;
}
void serial_uartbreak (int v)