This repository has been archived on 2025-05-24. You can view files and clone it, but cannot push or open issues or pull requests.
Files
VARCem/src/devices/ports/serial.c

473 lines
11 KiB
C
Raw Normal View History

2018-02-20 21:44:51 -05:00
/*
2018-03-08 15:58:46 -05:00
* VARCem Virtual ARchaeological Computer EMulator.
2018-02-20 21:44:51 -05:00
* An emulator of (mostly) x86-based PC systems and devices,
* using the ISA,EISA,VLB,MCA and PCI system buses, roughly
* spanning the era between 1981 and 1995.
*
* This file is part of the VARCem Project.
*
* Implementation of 8250-style serial port.
*
* Version: @(#)serial.c 1.0.7 2018/05/06
2018-02-20 21:44:51 -05:00
*
* Authors: Fred N. van Kempen, <decwiz@yahoo.com>
* Miran Grca, <mgrca8@gmail.com>
* Sarah Walker, <tommowalker@tommowalker.co.uk>
*
* Copyright 2017,2018 Fred N. van Kempen.
* Copyright 2016-2018 Miran Grca.
* Copyright 2008-2018 Sarah Walker.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the:
*
* Free Software Foundation, Inc.
* 59 Temple Place - Suite 330
* Boston, MA 02111-1307
* USA.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
2018-02-20 21:44:51 -05:00
#include <wchar.h>
#define HAVE_STDARG_H
#include "../../emu.h"
#include "../../machines/machine.h"
#include "../../io.h"
#include "../../mem.h"
#include "../../rom.h"
#include "../../timer.h"
#include "../../device.h"
#include "../../devices/system/pic.h"
#include "serial.h"
2018-02-20 21:44:51 -05:00
enum {
SERIAL_INT_LSR = 1,
SERIAL_INT_RECEIVE = 2,
SERIAL_INT_TRANSMIT = 4,
SERIAL_INT_MSR = 8
};
#ifdef ENABLE_SERIAL_LOG
int serial_do_log = ENABLE_SERIAL_LOG;
#endif
2018-02-20 21:44:51 -05:00
static const struct {
uint16_t addr;
int8_t irq;
int8_t pad;
} addr_list[] = { /* valid port addresses */
{ SERIAL1_ADDR, 4 },
{ SERIAL2_ADDR, 3 }
};
static SERIAL ports[SERIAL_MAX]; /* the ports */
2018-02-20 21:44:51 -05:00
static void
serlog(const char *fmt, ...)
{
#ifdef ENABLE_SERIAL_LOG
va_list ap;
if (serial_do_log) {
va_start(ap, fmt);
pclog_ex(fmt, ap);
va_end(ap);
}
#endif
}
static void
update_ints(SERIAL *dev)
2018-02-20 21:44:51 -05:00
{
int stat = 0;
dev->iir = 1;
2018-02-20 21:44:51 -05:00
if ((dev->ier & 4) && (dev->int_status & SERIAL_INT_LSR)) {
/*Line status interrupt*/
2018-02-20 21:44:51 -05:00
stat = 1;
dev->iir = 6;
} else if ((dev->ier & 1) && (dev->int_status & SERIAL_INT_RECEIVE)) {
/*Recieved data available*/
2018-02-20 21:44:51 -05:00
stat = 1;
dev->iir = 4;
} else if ((dev->ier & 2) && (dev->int_status & SERIAL_INT_TRANSMIT)) {
/*Transmit data empty*/
2018-02-20 21:44:51 -05:00
stat = 1;
dev->iir = 2;
} else if ((dev->ier & 8) && (dev->int_status & SERIAL_INT_MSR)) {
/*Modem status interrupt*/
2018-02-20 21:44:51 -05:00
stat = 1;
dev->iir = 0;
2018-02-20 21:44:51 -05:00
}
if (stat && ((dev->mcr & 8) || PCJR))
picintlevel(1 << dev->irq);
2018-02-20 21:44:51 -05:00
else
picintc(1 << dev->irq);
2018-02-20 21:44:51 -05:00
}
static void
clear_fifo(SERIAL *dev)
2018-02-20 21:44:51 -05:00
{
memset(dev->fifo, 0x00, 256);
dev->fifo_read = dev->fifo_write = 0;
2018-02-20 21:44:51 -05:00
}
static void
write_fifo(SERIAL *dev, uint8_t *ptr, uint8_t len)
2018-02-20 21:44:51 -05:00
{
while (len-- > 0) {
dev->fifo[dev->fifo_write] = *ptr++;
dev->fifo_write = (dev->fifo_write + 1) & 0xff;
/*OVERFLOW NOT DETECTED*/
}
if (! (dev->lsr & 1)) {
dev->lsr |= 1;
dev->int_status |= SERIAL_INT_RECEIVE;
update_ints(dev);
2018-02-20 21:44:51 -05:00
}
}
static uint8_t
read_fifo(SERIAL *dev)
2018-02-20 21:44:51 -05:00
{
if (dev->fifo_read != dev->fifo_write) {
dev->dat = dev->fifo[dev->fifo_read];
dev->fifo_read = (dev->fifo_read + 1) & 0xff;
2018-02-20 21:44:51 -05:00
}
return(dev->dat);
2018-02-20 21:44:51 -05:00
}
static void
receive_callback(void *priv)
{
SERIAL *dev = (SERIAL *)priv;
dev->delay = 0;
if (dev->fifo_read != dev->fifo_write) {
dev->lsr |= 1;
dev->int_status |= SERIAL_INT_RECEIVE;
update_ints(dev);
}
}
static void
reset_port(SERIAL *dev)
2018-02-20 21:44:51 -05:00
{
dev->iir = dev->ier = dev->lcr = 0;
dev->fifo_read = dev->fifo_write = 0;
dev->int_status = 0x00;
}
2018-02-20 21:44:51 -05:00
static void
serial_write(uint16_t addr, uint8_t val, void *priv)
{
SERIAL *dev = (SERIAL *)priv;
switch (addr & 7) {
2018-02-20 21:44:51 -05:00
case 0:
if (dev->lcr & 0x80) {
dev->dlab1 = val;
2018-02-20 21:44:51 -05:00
return;
}
dev->thr = val;
dev->lsr |= 0x20;
dev->int_status |= SERIAL_INT_TRANSMIT;
update_ints(dev);
if (dev->mcr & 0x10)
write_fifo(dev, &val, 1);
2018-02-20 21:44:51 -05:00
break;
2018-02-20 21:44:51 -05:00
case 1:
if (dev->lcr & 0x80) {
dev->dlab2 = val;
2018-02-20 21:44:51 -05:00
return;
}
dev->ier = val & 0xf;
update_ints(dev);
2018-02-20 21:44:51 -05:00
break;
2018-02-20 21:44:51 -05:00
case 2:
dev->fcr = val;
2018-02-20 21:44:51 -05:00
break;
2018-02-20 21:44:51 -05:00
case 3:
dev->lcr = val;
2018-02-20 21:44:51 -05:00
break;
2018-02-20 21:44:51 -05:00
case 4:
if ((val & 2) && !(dev->mcr & 2)) {
if (dev->rts_callback)
dev->rts_callback(dev, dev->rts_callback_p);
2018-02-20 21:44:51 -05:00
}
dev->mcr = val;
2018-02-20 21:44:51 -05:00
if (val & 0x10) {
uint8_t new_msr;
2018-02-20 21:44:51 -05:00
new_msr = (val & 0x0c) << 4;
new_msr |= (val & 0x02) ? 0x10: 0;
new_msr |= (val & 0x01) ? 0x20: 0;
if ((dev->msr ^ new_msr) & 0x10)
2018-02-20 21:44:51 -05:00
new_msr |= 0x01;
if ((dev->msr ^ new_msr) & 0x20)
2018-02-20 21:44:51 -05:00
new_msr |= 0x02;
if ((dev->msr ^ new_msr) & 0x80)
2018-02-20 21:44:51 -05:00
new_msr |= 0x08;
if ((dev->msr & 0x40) && !(new_msr & 0x40))
2018-02-20 21:44:51 -05:00
new_msr |= 0x04;
dev->msr = new_msr;
2018-02-20 21:44:51 -05:00
}
break;
2018-02-20 21:44:51 -05:00
case 5:
dev->lsr = val;
if (dev->lsr & 0x01)
dev->int_status |= SERIAL_INT_RECEIVE;
if (dev->lsr & 0x1e)
dev->int_status |= SERIAL_INT_LSR;
if (dev->lsr & 0x20)
dev->int_status |= SERIAL_INT_TRANSMIT;
update_ints(dev);
2018-02-20 21:44:51 -05:00
break;
2018-02-20 21:44:51 -05:00
case 6:
dev->msr = val;
if (dev->msr & 0x0f)
dev->int_status |= SERIAL_INT_MSR;
update_ints(dev);
2018-02-20 21:44:51 -05:00
break;
2018-02-20 21:44:51 -05:00
case 7:
dev->scratch = val;
2018-02-20 21:44:51 -05:00
break;
}
}
static uint8_t
serial_read(uint16_t addr, void *priv)
2018-02-20 21:44:51 -05:00
{
SERIAL *dev = (SERIAL *)priv;
uint8_t ret = 0x00;
2018-02-20 21:44:51 -05:00
switch (addr & 7) {
2018-02-20 21:44:51 -05:00
case 0:
if (dev->lcr & 0x80) {
ret = dev->dlab1;
2018-02-20 21:44:51 -05:00
break;
}
dev->lsr &= ~1;
dev->int_status &= ~SERIAL_INT_RECEIVE;
update_ints(dev);
ret = read_fifo(dev);
if (dev->fifo_read != dev->fifo_write) {
dev->delay = 1000LL * TIMER_USEC;
2018-02-20 21:44:51 -05:00
}
break;
2018-02-20 21:44:51 -05:00
case 1:
if (dev->lcr & 0x80)
ret = dev->dlab2;
2018-02-20 21:44:51 -05:00
else
ret = dev->ier;
2018-02-20 21:44:51 -05:00
break;
2018-02-20 21:44:51 -05:00
case 2:
ret = dev->iir;
if ((ret & 0xe) == 2) {
dev->int_status &= ~SERIAL_INT_TRANSMIT;
update_ints(dev);
2018-02-20 21:44:51 -05:00
}
if (dev->fcr & 1)
ret |= 0xc0;
2018-02-20 21:44:51 -05:00
break;
2018-02-20 21:44:51 -05:00
case 3:
ret = dev->lcr;
2018-02-20 21:44:51 -05:00
break;
2018-02-20 21:44:51 -05:00
case 4:
ret = dev->mcr;
2018-02-20 21:44:51 -05:00
break;
2018-02-20 21:44:51 -05:00
case 5:
if (dev->lsr & 0x20)
dev->lsr |= 0x40;
dev->lsr |= 0x20;
ret = dev->lsr;
if (dev->lsr & 0x1f)
dev->lsr &= ~0x1e;
dev->int_status &= ~SERIAL_INT_LSR;
update_ints(dev);
2018-02-20 21:44:51 -05:00
break;
2018-02-20 21:44:51 -05:00
case 6:
ret = dev->msr;
dev->msr &= ~0x0f;
dev->int_status &= ~SERIAL_INT_MSR;
update_ints(dev);
2018-02-20 21:44:51 -05:00
break;
2018-02-20 21:44:51 -05:00
case 7:
ret = dev->scratch;
2018-02-20 21:44:51 -05:00
break;
}
return(ret);
2018-02-20 21:44:51 -05:00
}
static void *
serial_init(const device_t *info)
2018-02-20 21:44:51 -05:00
{
SERIAL *dev;
2018-02-20 21:44:51 -05:00
/* Get the correct device. */
dev = &ports[info->local - 1];
2018-02-20 21:44:51 -05:00
/* Set up callback functions. */
dev->clear_fifo = clear_fifo;
dev->write_fifo = write_fifo;
2018-02-20 21:44:51 -05:00
/* Clear port. */
reset_port(dev);
2018-02-20 21:44:51 -05:00
/* Enable the I/O handler for this port. */
io_sethandler(dev->base, 8,
serial_read,NULL,NULL, serial_write,NULL,NULL, dev);
timer_add(receive_callback, &dev->delay, &dev->delay, dev);
2018-02-20 21:44:51 -05:00
pclog("SERIAL: COM%d (I/O=%04X, IRQ=%d)\n",
info->local, dev->base, dev->irq);
2018-02-20 21:44:51 -05:00
return(dev);
}
2018-02-20 21:44:51 -05:00
static void
serial_close(void *priv)
{
SERIAL *dev = (SERIAL *)priv;
/* Remove the I/O handler. */
io_removehandler(dev->base, 8,
serial_read,NULL,NULL, serial_write,NULL,NULL, dev);
/* Clear port. */
reset_port(dev);
2018-02-20 21:44:51 -05:00
}
const device_t serial_1_device = {
"COM1:",
0,
1,
serial_init, serial_close, NULL,
NULL, NULL, NULL, NULL,
NULL
};
const device_t serial_2_device = {
"COM2:",
0,
2,
serial_init, serial_close, NULL,
NULL, NULL, NULL, NULL,
NULL,
};
/* (Re-)initialize all serial ports. */
2018-02-20 21:44:51 -05:00
void
serial_reset(void)
2018-02-20 21:44:51 -05:00
{
SERIAL *dev;
int i;
2018-02-20 21:44:51 -05:00
#ifdef ENABLE_SERIAL_LOG
serlog("SERIAL: reset ([%d] [%d])\n", serial_enabled[0], serial_enabled[1]);
#endif
2018-02-20 21:44:51 -05:00
for (i = 0; i < SERIAL_MAX; i++) {
dev = &ports[i];
2018-02-20 21:44:51 -05:00
memset(dev, 0x00, sizeof(SERIAL));
2018-02-20 21:44:51 -05:00
dev->base = addr_list[i].addr;
dev->irq = addr_list[i].irq;
2018-02-20 21:44:51 -05:00
/* Clear port. */
reset_port(dev);
2018-02-20 21:44:51 -05:00
}
}
/* Set up (the address/IRQ of) one of the serial ports. */
2018-02-20 21:44:51 -05:00
void
serial_setup(int id, uint16_t port, int8_t irq)
2018-02-20 21:44:51 -05:00
{
SERIAL *dev = &ports[id-1];
2018-02-20 21:44:51 -05:00
#if defined(ENABLE_SERIAL_LOG) && defined(_DEBUG)
serlog("SERIAL: setting up COM%d as %04X [enabled=%d]\n",
id, port, serial_enabled[id-1]);
#endif
if (! serial_enabled[id-1]) return;
dev->base = port;
dev->irq = irq;
}
/* Attach another device (MOUSE) to a serial port. */
SERIAL *
serial_attach(int port, void *func, void *arg)
{
SERIAL *dev;
/* No can do if port not enabled. */
if (! serial_enabled[port-1]) return(NULL);
/* Grab the desired port block. */
dev = &ports[port-1];
/* Set up callback info. */
dev->rts_callback = func;
dev->rts_callback_p = arg;
return(dev);
2018-02-20 21:44:51 -05:00
}