/* * 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; } network_rx_pause = 0; 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 };