/* * VARCem Virtual ARchaeological Computer EMulator. * 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 the network module. * * NOTE The definition of the netcard_t is currently not optimal; * it should be malloc'ed and then linked to the NETCARD def. * Will be done later. * * * * Author: Fred N. van Kempen, * * Copyright 2017-2019 Fred N. van Kempen. * * Redistribution and use in source and binary forms, with * or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the entire * above notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names * of its contributors may be used to endorse or promote * products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #define HAVE_STDARG_H #include <86box/86box.h> #include <86box/device.h> #include <86box/timer.h> #include <86box/plat.h> #include <86box/thread.h> #include <86box/ui.h> #include <86box/timer.h> #include <86box/network.h> #include <86box/net_3c503.h> #include <86box/net_ne2000.h> #include <86box/net_pcnet.h> #include <86box/net_plip.h> #include <86box/net_wd8003.h> #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #include #endif static const device_t net_none_device = { .name = "None", .internal_name = "none", .flags = 0, .local = NET_TYPE_NONE, .init = NULL, .close = NULL, .reset = NULL, { .available = NULL }, .speed_changed = NULL, .force_redraw = NULL, .config = NULL }; static const device_t *net_cards[] = { &net_none_device, &threec503_device, &pcnet_am79c960_device, &pcnet_am79c961_device, &ne1000_device, &ne2000_device, &pcnet_am79c960_eb_device, &rtl8019as_device, &wd8003e_device, &wd8003eb_device, &wd8013ebt_device, &plip_device, ðernext_mc_device, &wd8003eta_device, &wd8003ea_device, &wd8013epa_device, &pcnet_am79c973_device, &pcnet_am79c970a_device, &rtl8029as_device, &pcnet_am79c960_vlb_device, NULL }; netcard_conf_t net_cards_conf[NET_CARD_MAX]; int net_card_current = 0; /* Global variables. */ int network_ndev; netdev_t network_devs[NET_HOST_INTF_MAX]; /* Local variables. */ #ifdef ENABLE_NETWORK_LOG int network_do_log = ENABLE_NETWORK_LOG; static FILE *network_dump = NULL; static mutex_t *network_dump_mutex; static void network_log(const char *fmt, ...) { va_list ap; if (network_do_log) { va_start(ap, fmt); pclog_ex(fmt, ap); va_end(ap); } } static void network_dump_packet(netpkt_t *pkt) { if (!network_dump) return; struct timeval tv; gettimeofday(&tv, NULL); struct { uint32_t ts_sec, ts_usec, incl_len, orig_len; } pcap_packet_hdr = { tv.tv_sec, tv.tv_usec, pkt->len, pkt->len }; if (network_dump_mutex) thread_wait_mutex(network_dump_mutex); size_t written; if ((written = fwrite(&pcap_packet_hdr, 1, sizeof(pcap_packet_hdr), network_dump)) < sizeof(pcap_packet_hdr)) { network_log("NETWORK: failed to write dump packet header\n"); fseek(network_dump, -written, SEEK_CUR); } else { if ((written = fwrite(pkt->data, 1, pkt->len, network_dump)) < pkt->len) { network_log("NETWORK: failed to write dump packet data\n"); fseek(network_dump, -written - sizeof(pcap_packet_hdr), SEEK_CUR); } fflush(network_dump); } if (network_dump_mutex) thread_release_mutex(network_dump_mutex); } #else #define network_log(fmt, ...) #define network_dump_packet(pkt) #endif #ifdef _WIN32 static void network_winsock_clean(void) { WSACleanup(); } #endif /* * Initialize the configured network cards. * * This function gets called only once, from the System * Platform initialization code (currently in pc.c) to * set our local stuff to a known state. */ void network_init(void) { int i; #ifdef _WIN32 WSADATA Data; WSAStartup(MAKEWORD(2, 0), &Data); atexit(network_winsock_clean); #endif /* Create a first device entry that's always there, as needed by UI. */ strcpy(network_devs[0].device, "none"); strcpy(network_devs[0].description, "None"); network_ndev = 1; /* Initialize the Pcap system module, if present. */ i = net_pcap_prepare(&network_devs[network_ndev]); if (i > 0) network_ndev += i; #ifdef ENABLE_NETWORK_LOG /* Start packet dump. */ network_dump = fopen("network.pcap", "wb"); struct { uint32_t magic_number; uint16_t version_major, version_minor; int32_t thiszone; uint32_t sigfigs, snaplen, network; } pcap_hdr = { 0xa1b2c3d4, 2, 4, 0, 0, 65535, 1 }; fwrite(&pcap_hdr, sizeof(pcap_hdr), 1, network_dump); fflush(network_dump); #endif } static void network_queue_init(netqueue_t *queue) { queue->size = NET_QUEUE_LEN; queue->head = queue->tail = 0; for (int i=0; isize; i++) { queue->packets[i].data = calloc(1, NET_MAX_FRAME); queue->packets[i].len = 0; } } static bool network_queue_full(netqueue_t *queue) { return ((queue->head + 1) % queue->size) == queue->tail; } static bool network_queue_empty(netqueue_t *queue) { return (queue->head == queue->tail); } int network_queue_put(netqueue_t *queue, uint8_t *data, int len) { if (len > NET_MAX_FRAME || network_queue_full(queue)) { return 0; } netpkt_t *pkt = &queue->packets[queue->head]; memcpy(pkt->data, data, len); pkt->len = len; queue->head = (queue->head + 1) % queue->size; return 1; } static int network_queue_get(netqueue_t *queue, netpkt_t *dst_pkt) { if (network_queue_empty(queue)) return 0; netpkt_t *pkt = &queue->packets[queue->tail]; memcpy(dst_pkt->data, pkt->data, pkt->len); dst_pkt->len = pkt->len; queue->tail = (queue->tail + 1) % queue->size; return 1; } static int network_queue_move(netqueue_t *dst_q, netqueue_t *src_q) { if (network_queue_empty(src_q)) return 0; if (network_queue_full(dst_q)) { return 0; } netpkt_t *src_pkt = &src_q->packets[src_q->tail]; netpkt_t *dst_pkt = &dst_q->packets[dst_q->head]; uint8_t *tmp_dat = dst_pkt->data; dst_pkt->data = src_pkt->data; dst_pkt->len = src_pkt->len; dst_q->head = (dst_q->head + 1) % dst_q->size; src_pkt->data = tmp_dat; src_pkt->len = 0; src_q->tail = (src_q->tail + 1) % src_q->size; return 1; } static void network_queue_clear(netqueue_t *queue) { for (int i=0; isize; i++) { free(queue->packets[i].data); queue->packets[i].len = 0; } queue->tail = queue->head = 0; } static void network_rx_queue(void *priv) { netcard_t *card = (netcard_t *)priv; double timer_period; int ret = 0; bool activity = false; if (card->queued_pkt.len == 0) { thread_wait_mutex(card->rx_mutex); network_queue_get(&card->queues[NET_QUEUE_RX], &card->queued_pkt); thread_release_mutex(card->rx_mutex); } if (card->queued_pkt.len > 0) { network_dump_packet(&card->queued_pkt); ret = card->rx(card->card_drv, card->queued_pkt.data, card->queued_pkt.len); } if (ret) { activity = true; timer_period = 0.762939453125 * ((card->queued_pkt.len >= 128) ? ((double) card->queued_pkt.len) : 128.0); card->queued_pkt.len = 0; } else { timer_period = 0.762939453125 * 128.0; } timer_on_auto(&card->timer, timer_period); /* Transmission. */ thread_wait_mutex(card->tx_mutex); ret = network_queue_move(&card->queues[NET_QUEUE_TX_HOST], &card->queues[NET_QUEUE_TX_VM]); thread_release_mutex(card->tx_mutex); if (ret) { /* Notify host that a packet is available in the TX queue */ card->host_drv.notify_in(card->host_drv.priv); activity = true; } ui_sb_update_icon(SB_NETWORK, activity); } /* * Attach a network card to the system. * * This function is called by a hardware driver ("card") after it has * finished initializing itself, to link itself to the platform support * modules. */ netcard_t * network_attach(void *card_drv, uint8_t *mac, NETRXCB rx, NETWAITCB wait, NETSETLINKSTATE set_link_state) { netcard_t *card = calloc(1, sizeof(netcard_t)); card->queued_pkt.data = calloc(1, NET_MAX_FRAME); card->card_drv = card_drv; card->rx = rx; card->wait = wait; card->set_link_state = set_link_state; card->tx_mutex = thread_create_mutex(); card->rx_mutex = thread_create_mutex(); for (int i=0; i<3; i++) { network_queue_init(&card->queues[i]); } switch (net_cards_conf[net_card_current].net_type) { case NET_TYPE_SLIRP: default: card->host_drv = net_slirp_drv; card->host_drv.priv = card->host_drv.init(card, mac, NULL); break; case NET_TYPE_PCAP: card->host_drv = net_pcap_drv; card->host_drv.priv = card->host_drv.init(card, mac, net_cards_conf[net_card_current].host_dev_name); break; } if (!card->host_drv.priv) { thread_close_mutex(card->tx_mutex); thread_close_mutex(card->rx_mutex); for (int i=0; i<3; i++) { network_queue_clear(&card->queues[i]); } free(card->queued_pkt.data); free(card); return NULL; } timer_add(&card->timer, network_rx_queue, card, 0); timer_on_auto(&card->timer, 0.762939453125 * 2.0); return card; } void netcard_close(netcard_t *card) { timer_stop(&card->timer); card->host_drv.close(card->host_drv.priv); thread_close_mutex(card->tx_mutex); thread_close_mutex(card->rx_mutex); for (int i=0; i<3; i++) { network_queue_clear(&card->queues[i]); } free(card->queued_pkt.data); free(card); } /* Stop any network activity. */ void network_close(void) { #ifdef ENABLE_NETWORK_LOG thread_close_mutex(network_dump_mutex); network_dump_mutex = NULL; #endif network_log("NETWORK: closed.\n"); } /* * Reset the network card(s). * * This function is called each time the system is reset, * either a hard reset (including power-up) or a soft reset * including C-A-D reset.) It is responsible for connecting * everything together. */ void network_reset(void) { int i = -1; ui_sb_update_icon(SB_NETWORK, 0); #ifdef ENABLE_NETWORK_LOG network_dump_mutex = thread_create_mutex(); #endif for (i = 0; i < NET_CARD_MAX; i++) { if (!net_cards_conf[i].device_num || net_cards_conf[i].net_type == NET_TYPE_NONE || (net_cards_conf[i].net_type == NET_TYPE_PCAP && !strcmp(net_cards_conf[i].host_dev_name, "none"))) { continue; } net_card_current = i; device_add_inst(net_cards[net_cards_conf[i].device_num], i + 1); } } /* Queue a packet for transmission to one of the network providers. */ void network_tx(netcard_t *card, uint8_t *bufp, int len) { network_queue_put(&card->queues[NET_QUEUE_TX_VM], bufp, len); } int network_tx_pop(netcard_t *card, netpkt_t *out_pkt) { int ret = 0; thread_wait_mutex(card->tx_mutex); ret = network_queue_get(&card->queues[NET_QUEUE_TX_HOST], out_pkt); thread_release_mutex(card->tx_mutex); return ret; } int network_rx_put(netcard_t *card, uint8_t *bufp, int len) { int ret = 0; thread_wait_mutex(card->rx_mutex); ret = network_queue_put(&card->queues[NET_QUEUE_RX], bufp, len); thread_release_mutex(card->rx_mutex); return ret; } int network_dev_to_id(char *devname) { int i = 0; for (i=0; i 0) && (net_cards_conf[i].net_type != NET_TYPE_NONE); } return available; } /* UI */ int network_card_available(int card) { if (net_cards[card]) return(device_available(net_cards[card])); return(1); } /* UI */ const device_t * network_card_getdevice(int card) { return(net_cards[card]); } /* UI */ int network_card_has_config(int card) { if (!net_cards[card]) return(0); return(device_has_config(net_cards[card]) ? 1 : 0); } /* UI */ char * network_card_get_internal_name(int card) { return device_get_internal_name(net_cards[card]); } /* UI */ int network_card_get_from_internal_name(char *s) { int c = 0; while (net_cards[c] != NULL) { if (! strcmp((char *)net_cards[c]->internal_name, s)) return(c); c++; } return 0; }