From 123462f1d9da6521cbf2d4ce801e555eae5e02ac Mon Sep 17 00:00:00 2001 From: RichardG867 Date: Sat, 17 Oct 2020 01:31:53 -0300 Subject: [PATCH] Add PLIP network device --- src/include/86box/lpt.h | 5 + src/include/86box/net_plip.h | 24 ++ src/include/86box/network.h | 1 + src/lpt.c | 4 +- src/network/net_plip.c | 507 +++++++++++++++++++++++++++++++++++ src/win/Makefile.mingw | 3 +- 6 files changed, 542 insertions(+), 2 deletions(-) create mode 100644 src/include/86box/net_plip.h create mode 100644 src/network/net_plip.c diff --git a/src/include/86box/lpt.h b/src/include/86box/lpt.h index 43a91a09a..f219c12b0 100644 --- a/src/include/86box/lpt.h +++ b/src/include/86box/lpt.h @@ -1,3 +1,6 @@ +#ifndef EMU_LPT_H +# define EMU_LPT_H + typedef struct { const char *name; @@ -58,3 +61,5 @@ extern const lpt_device_t lpt_dac_device; extern const lpt_device_t lpt_dac_stereo_device; extern const lpt_device_t dss_device; + +#endif /*EMU_LPT_H*/ \ No newline at end of file diff --git a/src/include/86box/net_plip.h b/src/include/86box/net_plip.h new file mode 100644 index 000000000..6f26d85e4 --- /dev/null +++ b/src/include/86box/net_plip.h @@ -0,0 +1,24 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * Definitions for the PLIP parallel port network device. + * + * + * + * Author: RichardG, + * Copyright 2020 RichardG. + */ +#ifndef NET_PLIP_H +# define NET_PLIP_H +# include <86box/device.h> +# include <86box/lpt.h> + +extern const lpt_device_t lpt_plip_device; +extern const device_t plip_device; + +#endif /*NET_PLIP_H*/ diff --git a/src/include/86box/network.h b/src/include/86box/network.h index 37c66013e..c6bb348df 100644 --- a/src/include/86box/network.h +++ b/src/include/86box/network.h @@ -101,6 +101,7 @@ extern "C" { /* Global variables. */ extern int nic_do_log; /* config */ extern int network_ndev; +extern int network_rx_pause; extern netdev_t network_devs[32]; diff --git a/src/lpt.c b/src/lpt.c index 9d1228082..b304f03a1 100644 --- a/src/lpt.c +++ b/src/lpt.c @@ -11,6 +11,7 @@ #include <86box/pic.h> #include <86box/sound.h> #include <86box/prt_devs.h> +#include <86box/net_plip.h> lpt_port_t lpt_ports[3]; @@ -30,6 +31,7 @@ static const struct {"Generic Text Printer", "text_prt", &lpt_prt_text_device}, {"Generic ESC/P Dot-Matrix", "dot_matrix", &lpt_prt_escp_device}, {"Generic PostScript Printer", "postscript", &lpt_prt_ps_device}, + {"PLIP Network", "plip", &lpt_plip_device}, {"", "", NULL} }; @@ -140,7 +142,7 @@ lpt_read(uint16_t port, void *priv) case 1: if (dev->dt && dev->dt->read_status) - ret = dev->dt->read_status(dev->priv) | 0x0f; + ret = dev->dt->read_status(dev->priv); else ret = 0xdf; break; diff --git a/src/network/net_plip.c b/src/network/net_plip.c new file mode 100644 index 000000000..ca58b5824 --- /dev/null +++ b/src/network/net_plip.c @@ -0,0 +1,507 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * Emulation of a PLIP parallel port network device. + * + * Tested against the Linux plip.c driver and the DOS plip.com + * packet driver. PLIP is not particularly fast, as it's a 4-bit + * half-duplex protocol operating over SPP. + * + * + * + * Author: RichardG, + * Copyright 2020 RichardG. + */ +#include +#include +#include +#include +#include +#include +#include +#define HAVE_STDARG_H +#include <86box/86box.h> +#include <86box/language.h> +#include <86box/lpt.h> +#include <86box/timer.h> +#include <86box/pit.h> +#include <86box/device.h> +#include <86box/network.h> +#include <86box/net_plip.h> + + +enum { + PLIP_START = 0x00, + PLIP_TX_LEN_LSB_LOW = 0x10, + PLIP_TX_LEN_LSB_HIGH, + PLIP_TX_LEN_MSB_LOW, + PLIP_TX_LEN_MSB_HIGH, + PLIP_TX_DATA_LOW, + PLIP_TX_DATA_HIGH, + PLIP_TX_CHECKSUM_LOW, + PLIP_TX_CHECKSUM_HIGH, + PLIP_RX_LEN_LSB_LOW = 0x20, + PLIP_RX_LEN_LSB_HIGH, + PLIP_RX_LEN_MSB_LOW, + PLIP_RX_LEN_MSB_HIGH, + PLIP_RX_DATA_LOW, + PLIP_RX_DATA_HIGH, + PLIP_RX_CHECKSUM_LOW, + PLIP_RX_CHECKSUM_HIGH, + PLIP_END = 0x40 +}; + +typedef struct +{ + uint8_t mac[6]; + + void *lpt; + pc_timer_t rx_timer; + pc_timer_t timeout_timer; + uint8_t status, ctrl; + + uint8_t state, ack, tx_checksum, tx_checksum_calc, *tx_pkt; + uint16_t tx_len, tx_ptr; + + uint8_t *rx_pkt, rx_checksum, rx_return_state; + uint16_t rx_len, rx_ptr; +} plip_t; + + +static void plip_receive_packet(plip_t *dev); + +plip_t *instance; + + +#ifdef ENABLE_PLIP_LOG +int plip_do_log = ENABLE_PLIP_LOG; + +static void +plip_log(uint8_t lvl, const char *fmt, ...) +{ + va_list ap; + + if (plip_do_log >= lvl) { + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); + } +} +#else +#define plip_log(lvl, fmt, ...) +#endif + + +static void +timeout_timer(void *priv) +{ + plip_t *dev = (plip_t *) priv; + + plip_log(1, "PLIP: timeout at state %d status %02X\n", dev->state, dev->status); + + /* Throw everything out the window. */ + dev->state = PLIP_START; + dev->status = 0x80; + + if (dev->tx_pkt) { + free(dev->tx_pkt); + dev->tx_pkt = NULL; + } + if (dev->rx_pkt) { + free(dev->rx_pkt); + dev->rx_pkt = NULL; + } + + timer_disable(&dev->timeout_timer); +} + + +static void +plip_write_data(uint8_t val, void *priv) +{ + plip_t *dev = (plip_t *) priv; + + plip_log(3, "PLIP: write_data(%02X)\n", val); + + switch (dev->state) { + case PLIP_START: + if (val == 0x08) { /* D3/ACK wakes us up */ + plip_log(2, "PLIP: ACK wakeup\n"); + dev->state = PLIP_TX_LEN_LSB_LOW; + dev->status = 0x08; + break; + } + return; + + case PLIP_TX_LEN_LSB_LOW: + if (!(val & 0x10)) + return; /* D4/BUSY not high yet */ + dev->tx_len = val & 0xf; + plip_log(2, "PLIP: tx_len = %04X (1/4)\n", dev->tx_len); + dev->state = PLIP_TX_LEN_LSB_HIGH; + dev->status &= ~0x88; + break; + + case PLIP_TX_LEN_LSB_HIGH: + if (val & 0x10) + return; /* D4/BUSY not low yet */ + dev->tx_len |= (val & 0xf) << 4; + plip_log(2, "PLIP: tx_len = %04X (2/4)\n", dev->tx_len); + dev->state = PLIP_TX_LEN_MSB_LOW; + dev->status |= 0x80; + break; + + case PLIP_TX_LEN_MSB_LOW: + if (!(val & 0x10)) + return; /* D4/BUSY not high yet */ + dev->tx_len |= (val & 0xf) << 8; + plip_log(2, "PLIP: tx_len = %04X (3/4)\n", dev->tx_len); + dev->state = PLIP_TX_LEN_MSB_HIGH; + dev->status &= ~0x80; + break; + + case PLIP_TX_LEN_MSB_HIGH: + if (val & 0x10) + return; /* D4/BUSY not low yet */ + dev->tx_len |= (val & 0xf) << 12; + plip_log(2, "PLIP: tx_len = %04X (4/4)\n", dev->tx_len); + + /* We have the length, allocate a packet. */ + if (!(dev->tx_pkt = malloc(dev->tx_len))) /* unlikely */ + fatal("PLIP: unable to allocate tx_pkt\n"); + dev->tx_ptr = 0; + dev->tx_checksum_calc = 0; + + dev->state = PLIP_TX_DATA_LOW; + dev->status |= 0x80; + break; + + case PLIP_TX_DATA_LOW: + if (!(val & 0x10)) + return; /* D4/BUSY not high yet */ + dev->tx_pkt[dev->tx_ptr] = val & 0x0f; + plip_log(2, "PLIP: tx_pkt[%d] = %02X (1/2)\n", dev->tx_ptr, dev->tx_pkt[dev->tx_ptr]); + dev->state = PLIP_TX_DATA_HIGH; + dev->status &= ~0x80; + break; + + case PLIP_TX_DATA_HIGH: + if (val & 0x10) + return; /* D4/BUSY not low yet */ + dev->tx_pkt[dev->tx_ptr] |= (val & 0x0f) << 4; + plip_log(2, "PLIP: tx_pkt[%d] = %02X (2/2)\n", dev->tx_ptr, dev->tx_pkt[dev->tx_ptr]); + dev->tx_checksum_calc += dev->tx_pkt[dev->tx_ptr++]; + + /* Are we done yet? */ + if (dev->tx_ptr < dev->tx_len) /* no, receive another byte */ + dev->state = PLIP_TX_DATA_LOW; + else /* yes, move on to checksum */ + dev->state = PLIP_TX_CHECKSUM_LOW; + dev->status |= 0x80; + break; + + case PLIP_TX_CHECKSUM_LOW: + if (!(val & 0x10)) + return; /* D4/BUSY not high yet */ + dev->tx_checksum = val & 0x0f; + plip_log(2, "PLIP: tx_checksum = %02X (1/2)\n", dev->tx_checksum); + dev->state = PLIP_TX_CHECKSUM_HIGH; + dev->status &= ~0x80; + break; + + case PLIP_TX_CHECKSUM_HIGH: + if (val & 0x10) + return; /* D4/BUSY not low yet */ + dev->tx_checksum |= (val & 0x0f) << 4; + plip_log(2, "PLIP: tx_checksum = %02X (2/2)\n", dev->tx_checksum); + + /* Verify checksum. */ + if (dev->tx_checksum_calc == dev->tx_checksum) { + /* Make sure we know the other end's MAC address. */ + memcpy(dev->mac, dev->tx_pkt + 6, 6); + + /* Transmit packet. */ + plip_log(2, "PLIP: transmitting %d-byte packet\n", dev->tx_len); + network_tx(dev->tx_pkt, dev->tx_len); + } else { + plip_log(1, "PLIP: checksum error: expected %02X, got %02X\n", dev->tx_checksum_calc, dev->tx_checksum); + } + + /* We're done with this packet. */ + free(dev->tx_pkt); + dev->tx_pkt = NULL; + dev->tx_len = 0; + + dev->state = PLIP_END; + dev->status |= 0x80; + break; + + case PLIP_RX_LEN_LSB_LOW: + if (!(val & 0x01)) + return; /* D3/ACK not high yet */ + plip_log(2, "PLIP: rx_len = %04X (1/4)\n", dev->rx_len); + dev->status = (dev->rx_len & 0x0f) << 3; + dev->state = PLIP_RX_LEN_LSB_HIGH; + break; + + case PLIP_RX_LEN_LSB_HIGH: + if (!(val & 0x10)) + return; /* D4/BUSY not high yet */ + plip_log(2, "PLIP: rx_len = %04X (2/4)\n", dev->rx_len); + dev->status = ((dev->rx_len >> 4) & 0x0f) << 3; + dev->status |= 0x80; + dev->state = PLIP_RX_LEN_MSB_LOW; + break; + + case PLIP_RX_LEN_MSB_LOW: + if (val & 0x10) + return; /* D4/BUSY not low yet */ + plip_log(2, "PLIP: rx_len = %04X (3/4)\n", dev->rx_len); + dev->status = ((dev->rx_len >> 8) & 0x0f) << 3; + dev->state = PLIP_RX_LEN_MSB_HIGH; + break; + + case PLIP_RX_LEN_MSB_HIGH: + if (!(val & 0x10)) + return; /* D4/BUSY not high yet */ + plip_log(2, "PLIP: rx_len = %04X (4/4)\n", dev->rx_len); + dev->status = ((dev->rx_len >> 12) & 0x0f) << 3; + dev->status |= 0x80; + + dev->rx_ptr = 0; + dev->rx_checksum = 0; + dev->state = PLIP_RX_DATA_LOW; + break; + + case PLIP_RX_DATA_LOW: + if (val & 0x10) + return; /* D4/BUSY not low yet */ + plip_log(2, "PLIP: rx_pkt[%d] = %02X (1/2)\n", dev->rx_ptr, dev->rx_pkt[dev->rx_ptr]); + dev->status = (dev->rx_pkt[dev->rx_ptr] & 0x0f) << 3; + dev->state = PLIP_RX_DATA_HIGH; + break; + + case PLIP_RX_DATA_HIGH: + if (!(val & 0x10)) + return; /* D4/BUSY not high yet */ + plip_log(2, "PLIP: rx_pkt[%d] = %02X (2/2)\n", dev->rx_ptr, dev->rx_pkt[dev->rx_ptr]); + dev->status = ((dev->rx_pkt[dev->rx_ptr] >> 4) & 0x0f) << 3; + dev->status |= 0x80; + dev->rx_checksum += dev->rx_pkt[dev->rx_ptr++]; + + /* Are we done yet? */ + if (dev->rx_ptr < dev->rx_len) /* no, send another byte */ + dev->state = PLIP_RX_DATA_LOW; + else /* yes, move on to checksum */ + dev->state = PLIP_RX_CHECKSUM_LOW; + break; + + case PLIP_RX_CHECKSUM_LOW: + if (val & 0x10) + return; /* D4/BUSY not low yet */ + plip_log(2, "PLIP: rx_checksum = %02X (1/2)\n", dev->rx_checksum); + dev->status = (dev->rx_checksum & 0x0f) << 3; + dev->state = PLIP_RX_CHECKSUM_HIGH; + break; + + case PLIP_RX_CHECKSUM_HIGH: + if (!(val & 0x10)) + return; /* D4/BUSY not high yet */ + plip_log(2, "PLIP: rx_checksum = %02X (2/2)\n", dev->rx_checksum); + dev->status = ((dev->rx_checksum >> 4) & 0x0f) << 3; + dev->status |= 0x80; + + /* We're done with this packet. */ + free(dev->rx_pkt); + dev->rx_pkt = NULL; + dev->rx_len = 0; + + dev->state = PLIP_END; + break; + + case PLIP_END: + if (val == 0x00) { /* written after TX or RX is done */ + plip_log(2, "PLIP: end\n"); + dev->status = 0x80; + dev->state = PLIP_START; + + timer_set_delay_u64(&dev->rx_timer, ISACONST); /* for DOS */ + } + + /* Disengage timeout timer. */ + timer_disable(&dev->timeout_timer); + return; + } + + /* Engage timeout timer unless otherwise specified. */ + timer_set_delay_u64(&dev->timeout_timer, 1000000 * TIMER_USEC); +} + + +static void +plip_write_ctrl(uint8_t val, void *priv) +{ + plip_t *dev = (plip_t *) priv; + + plip_log(3, "PLIP: write_ctrl(%02X)\n", val); + + dev->ctrl = val; + + if (val & 0x10) /* for Linux */ + timer_set_delay_u64(&dev->rx_timer, ISACONST); +} + + +static uint8_t +plip_read_status(void *priv) +{ + plip_t *dev = (plip_t *) priv; + + plip_log(3, "PLIP: read_status() = %02X\n", dev->status); + + return dev->status; +} + + +static void +plip_receive_packet(plip_t *dev) +{ + /* At least the Linux driver supports being interrupted + in the PLIP_TX_LEN_LSB_LOW state, but let's be safe. */ + if (dev->state > PLIP_START) { + plip_log(3, "PLIP: cannot receive, operation already in progress\n"); + return; + } + + if (!dev->rx_pkt || !dev->rx_len) { /* unpause RX queue if there's no packet to receive */ + network_rx_pause = 0; + return; + } + + if (!(dev->ctrl & 0x10)) { /* checking this is essential to avoid collisions */ + plip_log(3, "PLIP: cannot receive, interrupts are off\n"); + return; + } + + plip_log(2, "PLIP: receiving %d-byte packet\n", dev->rx_len); + + /* Set up to receive a packet. */ + dev->status = 0xc7; /* DOS expects exactly 0xc7, while Linux masks the 7 off */ + dev->state = PLIP_RX_LEN_LSB_LOW; + + /* Engage timeout timer. */ + timer_set_delay_u64(&dev->timeout_timer, 1000000 * TIMER_USEC); + + /* Wake the other end up. */ + lpt_irq(dev->lpt, 1); +} + + +/* This timer defers a call to plip_receive_packet to + the next ISA clock, in order to avoid IRQ weirdness. */ +static void +rx_timer(void *priv) +{ + plip_t *dev = (plip_t *) priv; + + plip_receive_packet(dev); + + timer_disable(&dev->rx_timer); +} + + +static void +plip_rx(void *priv, uint8_t *buf, int io_len) +{ + plip_t *dev = (plip_t *) priv; + + plip_log(2, "PLIP: incoming %d-byte packet\n", io_len); + + if (dev->rx_pkt) { /* shouldn't really happen with the RX queue paused */ + plip_log(3, "PLIP: already have a packet to receive"); + return; + } + + if (!(dev->rx_pkt = malloc(io_len))) /* unlikely */ + fatal("PLIP: unable to allocate rx_pkt\n"); + + network_rx_pause = 1; /* make sure we don't get any more packets while processing this one */ + + /* Copy this packet to our buffer. */ + dev->rx_len = io_len; + memcpy(dev->rx_pkt, buf, dev->rx_len); + + /* Dispatch this packet immediately if we're doing nothing. */ + plip_receive_packet(dev); +} + + +static void * +plip_lpt_init(void *lpt) +{ + plip_t *dev = (plip_t *) malloc(sizeof(plip_t)); + memset(dev, 0, sizeof(plip_t)); + + plip_log(1, "PLIP: lpt_init()\n"); + + dev->lpt = lpt; + memset(dev->mac, 0xfc, 6); /* static MAC used by Linux; just a placeholder */ + + dev->status = 0x80; + + timer_add(&dev->rx_timer, rx_timer, dev, 0); + timer_add(&dev->timeout_timer, timeout_timer, dev, 0); + + instance = dev; + + return dev; +} + + +static void * +plip_net_init(const device_t *info) +{ + plip_log(1, "PLIP: net_init()"); + + if (!instance) { + plip_log(1, " (not attached to LPT)\n"); + return NULL; + } + + plip_log(1, " (attached to LPT)\n"); + network_attach(instance, instance->mac, plip_rx, NULL, NULL); + + return instance; +} + + +static void +plip_close(void *priv) +{ + free(priv); +} + + +const lpt_device_t lpt_plip_device = { + .name = "Parallel Line Internet Protocol (LPT)", + .init = plip_lpt_init, + .close = plip_close, + .write_data = plip_write_data, + .write_ctrl = plip_write_ctrl, + .read_data = NULL, + .read_status = plip_read_status, + .read_ctrl = NULL +}; + +const device_t plip_device = +{ + "Parallel Line Internet Protocol (Network)", + 0, 0, + plip_net_init, NULL, + NULL, NULL, NULL, NULL +}; diff --git a/src/win/Makefile.mingw b/src/win/Makefile.mingw index fd05954c2..76a7b1986 100644 --- a/src/win/Makefile.mingw +++ b/src/win/Makefile.mingw @@ -721,7 +721,8 @@ NETOBJ := network.o \ tcp_output.o tcp_subr.o tcp_timer.o udp.o util.o version.o \ net_dp8390.o \ net_3c503.o net_ne2000.o \ - net_pcnet.o net_wd8003.o + net_pcnet.o net_wd8003.o \ + net_plip.o PRINTOBJ := png.o prt_cpmap.o \ prt_escp.o prt_text.o prt_ps.o