From 9488078c5ab8c3c76878809fcb49698f7a08212b Mon Sep 17 00:00:00 2001 From: Cacodemon345 Date: Fri, 8 Mar 2024 16:45:17 +0600 Subject: [PATCH 1/9] Work-In-Progress modem emulation --- src/86box.c | 7 +- src/device/serial.c | 40 +++ src/include/86box/serial.h | 8 + src/network/CMakeLists.txt | 2 +- src/network/net_modem.c | 706 +++++++++++++++++++++++++++++++++++++ 5 files changed, 759 insertions(+), 4 deletions(-) create mode 100644 src/network/net_modem.c diff --git a/src/86box.c b/src/86box.c index ca0f8b0ff..9cdb5f4e5 100644 --- a/src/86box.c +++ b/src/86box.c @@ -1168,13 +1168,14 @@ pc_reset_hard_init(void) /* note: PLIP LPT side has to be initialized before the network side */ lpt_devices_init(); - /* Reset and reconfigure the Network Card layer. */ - network_reset(); - /* Reset and reconfigure the serial ports. */ + /* note: SLIP COM side has to be initialized before the network side */ serial_standalone_init(); serial_passthrough_init(); + /* Reset and reconfigure the Network Card layer. */ + network_reset(); + /* * Reset the mouse, this will attach it to any port needed. */ diff --git a/src/device/serial.c b/src/device/serial.c index dcdffb71c..0ef959cdd 100644 --- a/src/device/serial.c +++ b/src/device/serial.c @@ -432,6 +432,25 @@ serial_set_dcd(serial_t *dev, uint8_t enabled) } } +void +serial_set_ri(serial_t *dev, uint8_t enabled) +{ + uint8_t prev_state = !!(dev->msr & 0x40); + if (dev->mctrl & 0x10) + return; + + dev->msr &= ~0x40; + dev->msr |= (!!enabled) << 6; + dev->msr_set &= ~0x40; + dev->msr_set |= (!!enabled) << 6; + + if (prev_state == 0 && (!!enabled) == 1) { + dev->msr |= 0x4; + dev->int_status |= SERIAL_INT_MSR; + serial_update_ints(dev); + } +} + void serial_set_clock_src(serial_t *dev, double clock_src) { @@ -570,6 +589,8 @@ serial_write(uint16_t addr, uint8_t val, void *priv) serial_do_irq(dev, 0); if ((val ^ dev->mctrl) & 0x10) serial_reset_fifo(dev); + if (dev->sd && dev->sd->dtr_callback) + dev->sd->dtr_callback(dev, val, dev->sd->priv); dev->mctrl = val; if (val & 0x10) { new_msr = (val & 0x0c) << 4; @@ -797,6 +818,25 @@ serial_attach_ex(int port, return sd->serial; } +serial_t * +serial_attach_ex_2(int port, + void (*rcr_callback)(struct serial_s *serial, void *priv), + void (*dev_write)(struct serial_s *serial, void *priv, uint8_t data), + void (*dtr_callback)(struct serial_s *serial, int status, void *priv), + void *priv) +{ + serial_device_t *sd = &serial_devices[port]; + + sd->rcr_callback = rcr_callback; + sd->dtr_callback = dtr_callback; + sd->dev_write = dev_write; + sd->transmit_period_callback = NULL; + sd->lcr_callback = NULL; + sd->priv = priv; + + return sd->serial; +} + static void serial_speed_changed(void *priv) { diff --git a/src/include/86box/serial.h b/src/include/86box/serial.h index 08f77ea13..99b39f56b 100644 --- a/src/include/86box/serial.h +++ b/src/include/86box/serial.h @@ -92,6 +92,7 @@ typedef struct serial_s { typedef struct serial_device_s { void (*rcr_callback)(struct serial_s *serial, void *priv); + void (*dtr_callback)(struct serial_s *serial, int status, void *priv); void (*dev_write)(struct serial_s *serial, void *priv, uint8_t data); void (*lcr_callback)(struct serial_s *serial, void *priv, uint8_t lcr); void (*transmit_period_callback)(struct serial_s *serial, void *priv, double transmit_period); @@ -112,6 +113,12 @@ extern serial_t *serial_attach_ex(int port, void (*lcr_callback)(struct serial_s *serial, void *priv, uint8_t data_bits), void *priv); +extern serial_t *serial_attach_ex_2(int port, + void (*rcr_callback)(struct serial_s *serial, void *priv), + void (*dev_write)(struct serial_s *serial, void *priv, uint8_t data), + void (*dtr_callback)(struct serial_s *serial, int status, void *priv), + void *priv); + #define serial_attach(port, rcr_callback, dev_write, priv) \ serial_attach_ex(port, rcr_callback, dev_write, NULL, NULL, priv); @@ -129,6 +136,7 @@ extern void serial_device_timeout(void *priv); extern void serial_set_cts(serial_t *dev, uint8_t enabled); extern void serial_set_dsr(serial_t *dev, uint8_t enabled); extern void serial_set_dcd(serial_t *dev, uint8_t enabled); +extern void serial_set_ri(serial_t *dev, uint8_t enabled); extern const device_t ns8250_device; extern const device_t ns8250_pcjr_device; diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index b0ba913d5..5eb091d58 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -15,7 +15,7 @@ set(net_sources) list(APPEND net_sources network.c net_pcap.c net_slirp.c net_dp8390.c net_3c501.c net_3c503.c net_ne2000.c net_pcnet.c net_wd8003.c net_plip.c net_event.c net_null.c - net_eeprom_nmc93cxx.c net_tulip.c net_rtl8139.c net_l80225.c) + net_eeprom_nmc93cxx.c net_tulip.c net_rtl8139.c net_l80225.c net_modem.c) find_package(PkgConfig REQUIRED) pkg_check_modules(SLIRP REQUIRED IMPORTED_TARGET slirp) diff --git a/src/network/net_modem.c b/src/network/net_modem.c new file mode 100644 index 000000000..0137aa9bb --- /dev/null +++ b/src/network/net_modem.c @@ -0,0 +1,706 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#define HAVE_STDARG_H +#include <86box/86box.h> +#include <86box/device.h> +#include <86box/thread.h> +#include <86box/fifo.h> +#include <86box/fifo8.h> +#include <86box/timer.h> +#include <86box/serial.h> +#include <86box/network.h> +#include <86box/plat_unused.h> + +/* From RFC 1055. */ +#define END 0300 /* indicates end of packet */ +#define ESC 0333 /* indicates byte stuffing */ +#define ESC_END 0334 /* ESC ESC_END means END data byte */ +#define ESC_ESC 0335 /* ESC ESC_ESC means ESC data byte */ + +typedef enum ResTypes { + ResNONE, + ResOK, + ResERROR, + ResCONNECT, + ResRING, + ResBUSY, + ResNODIALTONE, + ResNOCARRIER, + ResNOANSWER +} ResTypes; + +enum modem_types +{ + MODEM_TYPE_SLIP = 1, + MODEM_TYPE_PPP = 2, + MODEM_TYPE_TCPIP = 3 +}; + +typedef enum modem_mode_t +{ + MODEM_MODE_COMMAND = 0, + MODEM_MODE_DATA = 1 +} modem_mode_t; + +typedef struct modem_t +{ + uint8_t mac[6]; + serial_t *serial; + double baudrate; + + modem_mode_t mode; + + uint8_t esc_character_expected; + pc_timer_t host_to_serial_timer; + pc_timer_t dtr_timer; + + uint8_t tx_pkt_ser_line[0x10000]; /* SLIP-encoded. */ + uint32_t tx_count; + + Fifo8 rx_data; /* Data received from the network. */ + uint8_t reg[100]; + + Fifo8 data_pending; /* Data yet to be sent to the host. */ + + char cmdbuf[512]; + uint32_t cmdpos; + int plusinc, flowcontrol; + int in_warmup; + + bool connected, ringing; + bool echo, numericresponse; + + int doresponse; + + netcard_t *card; +} modem_t; + +static modem_t *instance; + +#define MREG_AUTOANSWER_COUNT 0 +#define MREG_RING_COUNT 1 +#define MREG_ESCAPE_CHAR 2 +#define MREG_CR_CHAR 3 +#define MREG_LF_CHAR 4 +#define MREG_BACKSPACE_CHAR 5 +#define MREG_GUARD_TIME 12 +#define MREG_DTR_DELAY 25 + +static uint32_t +modem_scan_number(char **scan) +{ + char c = 0; + uint32_t ret = 0; + while (1) { + c = **scan; + if (c == 0) + break; + if (c >= '0' && c <= '9') { + ret*=10; + ret+=c-'0'; + *scan = *scan + 1; + } else + break; + } + return ret; +} + +static uint8_t +modem_fetch_character(char **scan) +{ + uint8_t c = **scan; + *scan = *scan + 1; + return c; +} + +static void +modem_speed_changed(void *priv) +{ + modem_t *dev = (modem_t *) priv; + if (!dev) + return; + + timer_stop(&dev->host_to_serial_timer); + /* FIXME: do something to dev->baudrate */ + timer_on_auto(&dev->host_to_serial_timer, (1000000.0 / dev->baudrate) * 9); +#if 0 + serial_clear_fifo(dev->serial); +#endif +} + +static void +modem_send_line(modem_t* modem, const char* line) +{ + fifo8_push(&modem->data_pending, modem->reg[MREG_CR_CHAR]); + fifo8_push(&modem->data_pending, modem->reg[MREG_LF_CHAR]); + fifo8_push_all(&modem->data_pending, (uint8_t*)line, strlen(line)); + fifo8_push(&modem->data_pending, modem->reg[MREG_CR_CHAR]); + fifo8_push(&modem->data_pending, modem->reg[MREG_LF_CHAR]); +} + + +static void +modem_send_number(modem_t* modem, uint32_t val) +{ + fifo8_push(&modem->data_pending, modem->reg[MREG_CR_CHAR]); + fifo8_push(&modem->data_pending, modem->reg[MREG_LF_CHAR]); + + fifo8_push(&modem->data_pending, val / 100 + '0'); + val = val%100; + fifo8_push(&modem->data_pending, val / 10 + '0'); + val = val%10; + fifo8_push(&modem->data_pending, val + '0'); + + fifo8_push(&modem->data_pending, modem->reg[MREG_CR_CHAR]); + fifo8_push(&modem->data_pending, modem->reg[MREG_LF_CHAR]); +} + +static void +process_tx_packet(modem_t *modem, uint8_t *p, uint32_t len) +{ + int received = 0; + uint32_t pos = 0; + uint8_t *processed_tx_packet = calloc(len, 1); + uint8_t c = 0; + + while (pos < len) { + c = p[pos]; + pos++; + switch (c) { + case END: + if (received) + goto send_tx_packet; + else + break; + + case ESC: + { + c = p[pos]; + pos++; + + switch (c) { + case ESC_END: + c = END; + break; + case ESC_ESC: + c = ESC; + break; + } + } + + default: + if (received < len) + processed_tx_packet[received++] = c; + break; + } + } + +send_tx_packet: + network_tx(modem->card, processed_tx_packet, received); + return; +} + +static void +modem_data_mode_process_byte(modem_t* modem, uint8_t data) +{ + if (modem->reg[MREG_ESCAPE_CHAR] <= 127) { + if (modem->reg[MREG_ESCAPE_CHAR] == data) { + return; + } + } + + if (data == END) { + process_tx_packet(modem, modem->tx_pkt_ser_line, (uint32_t)modem->tx_count + 1ul); + } + else if (modem->tx_count < 0x10000) + modem->tx_pkt_ser_line[modem->tx_count++] = data; +} + +static void +host_to_modem_cb(void *priv) +{ + modem_t* modem = (modem_t*)priv; + + if ((modem->serial->type >= SERIAL_16550) && modem->serial->fifo_enabled) { + if (fifo_get_full(modem->serial->rcvr_fifo)) { + goto no_write_to_machine; + } + } else { + if (modem->serial->lsr & 1) { + goto no_write_to_machine; + } + } + + if (modem->mode == MODEM_MODE_DATA && fifo8_num_used(&modem->rx_data)) { + serial_write_fifo(modem->serial, fifo8_pop(&modem->rx_data)); + } else if (fifo8_num_used(&modem->data_pending)) { + serial_write_fifo(modem->serial, fifo8_pop(&modem->data_pending)); + } + +no_write_to_machine: + timer_on_auto(&modem->host_to_serial_timer, (1000000.0 / modem->baudrate) * (double)9); +} + +static void +modem_write(UNUSED(serial_t *s), void *priv, uint8_t val) +{ + modem_t* modem = (modem_t*)priv; +} + +void modem_send_res(modem_t* modem, const ResTypes response) { + const char* response_str = NULL; + uint32_t code = -1; + switch (response) { + case ResOK: code = 0; response_str = "OK"; break; + case ResCONNECT: code = 1; response_str = "CONNECT 33600"; break; + case ResRING: code = 2; response_str = "RING"; break; + case ResNOCARRIER: code = 3; response_str = "NO CARRIER"; break; + case ResERROR: code = 4; response_str = "ERROR"; break; + case ResNODIALTONE: code = 6; response_str = "NO DIALTONE"; break; + case ResBUSY: code = 7; response_str = "BUSY"; break; + case ResNOANSWER: code = 8; response_str = "NO ANSWER"; break; + case ResNONE: return; + } + + if (modem->doresponse != 1) { + if (modem->doresponse == 2 && (response == ResRING || + response == ResCONNECT || response == ResNOCARRIER)) { + return; + } + if (modem->numericresponse && code != ~0) { + modem_send_number(modem, code); + } else if (response_str != NULL) { + modem_send_line(modem, response_str); + } + + // if(CSerial::CanReceiveByte()) // very fast response + // if(rqueue->inuse() && CSerial::getRTS()) + // { uint8_t rbyte =rqueue->getb(); + // CSerial::receiveByte(rbyte); + // LOG_MSG("SERIAL: Port %" PRIu8 " modem sending byte %2x back to UART2", + // GetPortNumber(), rbyte); + // } + } +} + +void +modem_enter_idle_state(modem_t* modem) +{ + timer_disable(&modem->dtr_timer); + modem->connected = false; + modem->ringing = false; + modem->mode = MODEM_MODE_COMMAND; + modem->in_warmup = 0; + serial_set_cts(modem->serial, 1); + serial_set_dsr(modem->serial, 1); + serial_set_dcd(modem->serial, 0); + serial_set_ri(modem->serial, 0); +} + +void +modem_enter_connected_state(modem_t* modem) +{ + modem_send_res(modem, ResCONNECT); + modem->mode = MODEM_MODE_DATA; + modem->ringing = false; + modem->connected = true; + serial_set_dcd(modem->serial, 1); + serial_set_ri(modem->serial, 0); +} + +void +modem_reset(modem_t* modem) +{ + modem_enter_idle_state(modem); + modem->cmdpos = 0; + modem->cmdbuf[0] = 0; + modem->flowcontrol = 0; + modem->plusinc = 0; + + memset(&modem->reg,0,sizeof(modem->reg)); + modem->reg[MREG_AUTOANSWER_COUNT] = 0; // no autoanswer + modem->reg[MREG_RING_COUNT] = 1; + modem->reg[MREG_ESCAPE_CHAR] = '+'; + modem->reg[MREG_CR_CHAR] = '\r'; + modem->reg[MREG_LF_CHAR] = '\n'; + modem->reg[MREG_BACKSPACE_CHAR] = '\b'; + modem->reg[MREG_GUARD_TIME] = 50; + modem->reg[MREG_DTR_DELAY] = 5; + + modem->echo = true; + modem->doresponse = 0; + modem->numericresponse = false; +} + +void +modem_dial(modem_t* modem, const char* str) +{ + /* TODO: Port TCP/IP support from DOSBox. */ + if (!strncmp(str, "0.0.0.0", sizeof("0.0.0.0") - 1)) { + modem_enter_connected_state(modem); + } else { + modem_send_res(modem, ResNOCARRIER); + modem_enter_idle_state(modem); + } +} + +static bool +is_next_token(const char* a, size_t N, const char *b) +{ + // Is 'b' at least as long as 'a'? + size_t N_without_null = N - 1; + if (strnlen(b, N) < N_without_null) + return false; + return (strncmp(a, b, N_without_null) == 0); +} + +// https://stackoverflow.com/a/122974 +char *trim(char *str) +{ + size_t len = 0; + char *frontp = str; + char *endp = NULL; + + if( str == NULL ) { return NULL; } + if( str[0] == '\0' ) { return str; } + + len = strlen(str); + endp = str + len; + + /* Move the front and back pointers to address the first non-whitespace + * characters from each end. + */ + while( isspace((unsigned char) *frontp) ) { ++frontp; } + if( endp != frontp ) + { + while( isspace((unsigned char) *(--endp)) && endp != frontp ) {} + } + + if( frontp != str && endp == frontp ) + *str = '\0'; + else if( str + len - 1 != endp ) + *(endp + 1) = '\0'; + + /* Shift the string so that it starts at str so that if it's dynamically + * allocated, we can still free it on the returned pointer. Note the reuse + * of endp to mean the front of the string buffer now. + */ + endp = str; + if( frontp != str ) + { + while( *frontp ) { *endp++ = *frontp++; } + *endp = '\0'; + } + + return str; +} + +static void +modem_do_command(modem_t* modem) +{ + int i = 0; + char *scanbuf = NULL; + modem->cmdbuf[modem->cmdpos] = 0; + modem->cmdpos = 0; + for (i = 0; i < sizeof(modem->cmdbuf); i++) { + modem->cmdbuf[i] = toupper(modem->cmdbuf[i]); + } + + /* AT command set interpretation */ + if ((modem->cmdbuf[0] != 'A') || (modem->cmdbuf[1] != 'T')) { + modem_send_res(modem, ResERROR); + return; + } + + scanbuf = &modem->cmdbuf[2]; + + while (1) { + char chr = modem_fetch_character(&scanbuf); + switch (chr) { + case '+': + /* None supported yet. */ + modem_send_res(modem, ResERROR); + return; + case 'D': { // Dial. + char buffer[128]; + char obuffer[128]; + char * foundstr = &scanbuf[0]; + size_t i = 0; + if (*foundstr == 'T' || *foundstr == 'P') + foundstr++; + + if ((!foundstr[0]) || (strlen(foundstr) > 253)) { + modem_send_res(modem, ResERROR); + return; + } + + foundstr = trim(foundstr); + if (strlen(foundstr) >= 12) { + // Check if supplied parameter only consists of digits + bool isNum = true; + size_t fl = strlen(foundstr); + for (i = 0; i < fl; i++) + if (foundstr[i] < '0' || foundstr[i] > '9') + isNum = false; + if (isNum) { + // Parameter is a number with at least 12 digits => this cannot + // be a valid IP/name + // Transform by adding dots + size_t j = 0; + const size_t foundlen = strlen(foundstr); + for (i = 0; i < foundlen; i++) { + buffer[j++] = foundstr[i]; + // Add a dot after the third, sixth and ninth number + if (i == 2 || i == 5 || i == 8) + buffer[j++] = '.'; + // If the string is longer than 12 digits, + // interpret the rest as port + if (i == 11 && foundlen > 12) + buffer[j++] = ':'; + } + buffer[j] = 0; + foundstr = buffer; + + // Remove Zeros from beginning of octets + size_t k = 0; + size_t foundlen2 = strlen(foundstr); + for (i = 0; i < foundlen2; i++) { + if (i == 0 && foundstr[0] == '0') continue; + if (i == 1 && foundstr[0] == '0' && foundstr[1] == '0') continue; + if (foundstr[i] == '0' && foundstr[i-1] == '.') continue; + if (foundstr[i] == '0' && foundstr[i-1] == '0' && foundstr[i-2] == '.') continue; + obuffer[k++] = foundstr[i]; + } + obuffer[k] = 0; + foundstr = obuffer; + } + } + modem_dial(modem, foundstr); + } + case 'I': // Some strings about firmware + switch (modem_scan_number(&scanbuf)) { + case 3: modem_send_line(modem, "86Box Emulated Modem Firmware V1.00"); break; + case 4: modem_send_line(modem, "Modem compiled for 86Box"); break; + } + break; + case 'E': // Echo on/off + switch (modem_scan_number(&scanbuf)) { + case 0: modem->echo = false; break; + case 1: modem->echo = true; break; + } + break; + case 'V': + switch (modem_scan_number(&scanbuf)) { + case 0: modem->numericresponse = true; break; + case 1: modem->numericresponse = false; break; + } + break; + case 'H': // Hang up + switch (modem_scan_number(&scanbuf)) { + case 0: + if (modem->connected) { + modem_send_res(modem, ResNOCARRIER); + modem_enter_idle_state(modem); + return; + } + // else return ok + } + break; + case 'O': // Return to data mode + switch (modem_scan_number(&scanbuf)) { + case 0: + if (modem->connected) { + modem->mode = MODEM_MODE_DATA; + return; + } else { + modem_send_res(modem, ResERROR); + return; + } + } + break; + case 'T': // Tone Dial + case 'P': // Pulse Dial + break; + case 'M': // Monitor + case 'L': // Volume + modem_scan_number(&scanbuf); + break; + case 'A': // Answer call + { + modem_send_res(modem, ResERROR); + return; + } + return; + case 'Z': { // Reset and load profiles + // scan the number away, if any + modem_scan_number(&scanbuf); + if (modem->connected) + modem_send_res(modem, ResNOCARRIER); + modem_reset(modem); + break; + } + case ' ': // skip space + break; + case 'Q': { + // Response options + // 0 = all on, 1 = all off, + // 2 = no ring and no connect/carrier in answermode + const uint32_t val = modem_scan_number(&scanbuf); + if (!(val > 2)) { + modem->doresponse = val; + break; + } else { + modem_send_res(modem, ResERROR); + return; + } + } + } + } +} + +void +modem_dtr_callback(serial_t* serial, int status, void *priv) +{ + modem_t *dev = (modem_t *) priv; + if (status == 1) + timer_disable(&dev->dtr_timer); + else if (!timer_is_enabled(&dev->dtr_timer)) + timer_enable(&dev->dtr_timer); +} + +static void +fifo8_resize_2x(Fifo8* fifo) +{ + uint32_t pos = 0; + uint32_t size = fifo->capacity * 2; + uint32_t used = fifo8_num_used(fifo); + if (!used) + return; + + uint8_t* temp_buf = calloc(fifo->capacity * 2, 1); + if (!temp_buf) { + fatal("net_modem: Out Of Memory!\n"); + } + while (!fifo8_is_empty(fifo)) { + temp_buf[pos] = fifo8_pop(fifo); + pos++; + } + pos = 0; + fifo8_destroy(fifo); + fifo8_create(fifo, size); + fifo8_push_all(fifo, temp_buf, used); + free(temp_buf); +} + + +static int +modem_rx(void *priv, uint8_t *buf, int io_len) +{ + modem_t* modem = (modem_t*)priv; + uint8_t c = 0; + uint32_t i = 0; + + if ((io_len) < (fifo8_num_free(&modem->rx_data) / 2)) { + fifo8_resize_2x(&modem->rx_data); + } + + fifo8_push(&modem->rx_data, END); + for (i = 0; i < io_len; i++) { + switch (buf[i]) { + case END: + fifo8_push(&modem->rx_data, ESC); + fifo8_push(&modem->rx_data, ESC_END); + break; + case ESC: + fifo8_push(&modem->rx_data, ESC); + fifo8_push(&modem->rx_data, ESC_ESC); + break; + default: + fifo8_push(&modem->rx_data, buf[i]); + break; + } + } + fifo8_push(&modem->rx_data, END); + return 1; +} + +static void +modem_rcr_cb(UNUSED(struct serial_s *serial), void *priv) +{ + modem_t *dev = (modem_t *) priv; + + timer_stop(&dev->host_to_serial_timer); + /* FIXME: do something to dev->baudrate */ + timer_on_auto(&dev->host_to_serial_timer, (1000000.0 / dev->baudrate) * (double) 9); +#if 0 + serial_clear_fifo(dev->serial); +#endif +} + +/* Initialize the device for use by the user. */ +static void * +modem_init(const device_t *info) +{ + modem_t* modem = (modem_t*)calloc(1, sizeof(modem_t)); + memset(modem->mac, 0xfc, 6); + + modem->card = network_attach(instance, instance->mac, modem_rx, NULL); + return modem; +} + +void modem_close(void *priv) +{ + free(priv); +} + +static const device_config_t modem_config[] = { + { + .name = "baudrate", + .description = "Baud Rate", + .type = CONFIG_SELECTION, + .default_string = "", + .default_int = 115200, + .file_filter = NULL, + .spinner = { 0 }, + .selection = { +#if 0 + { .description = "256000", .value = 256000 }, + { .description = "128000", .value = 128000 }, +#endif + { .description = "115200", .value = 115200 }, + { .description = "57600", .value = 57600 }, + { .description = "56000", .value = 56000 }, + { .description = "38400", .value = 38400 }, + { .description = "19200", .value = 19200 }, + { .description = "14400", .value = 14400 }, + { .description = "9600", .value = 9600 }, + { .description = "7200", .value = 7200 }, + { .description = "4800", .value = 4800 }, + { .description = "2400", .value = 2400 }, + { .description = "1800", .value = 1800 }, + { .description = "1200", .value = 1200 }, + { .description = "600", .value = 600 }, + { .description = "300", .value = 300 }, + } + }, + { .name = "", .description = "", .type = CONFIG_END } +}; + +const device_t modem_device = { + .name = "Standard Hayes-compliant Modem", + .flags = 0, + .local = 0, + .init = modem_init, + .close = modem_close, + .reset = NULL, + { .poll = NULL }, + .speed_changed = modem_speed_changed, + .force_redraw = NULL, + .config = modem_config +}; From ec8b8f2a920657e2fe9490a6d0bd148057c1b3fc Mon Sep 17 00:00:00 2001 From: Cacodemon345 Date: Sat, 9 Mar 2024 01:37:13 +0600 Subject: [PATCH 2/9] Somewhat finish initial modem emulation --- src/device/serial.c | 4 +- src/include/86box/network.h | 3 + src/network/net_modem.c | 265 +++++++++++++++++++++++++++++++++--- src/network/network.c | 1 + 4 files changed, 252 insertions(+), 21 deletions(-) diff --git a/src/device/serial.c b/src/device/serial.c index 0ef959cdd..b61c8304a 100644 --- a/src/device/serial.c +++ b/src/device/serial.c @@ -589,8 +589,8 @@ serial_write(uint16_t addr, uint8_t val, void *priv) serial_do_irq(dev, 0); if ((val ^ dev->mctrl) & 0x10) serial_reset_fifo(dev); - if (dev->sd && dev->sd->dtr_callback) - dev->sd->dtr_callback(dev, val, dev->sd->priv); + if (dev->sd && dev->sd->dtr_callback && (val ^ dev->mctrl) & 1) + dev->sd->dtr_callback(dev, val & 1, dev->sd->priv); dev->mctrl = val; if (val & 0x10) { new_msr = (val & 0x0c) << 4; diff --git a/src/include/86box/network.h b/src/include/86box/network.h index d0af3f09b..9588de86a 100644 --- a/src/include/86box/network.h +++ b/src/include/86box/network.h @@ -225,6 +225,9 @@ extern const device_t pcnet_am79c970a_device; extern const device_t pcnet_am79c973_device; extern const device_t pcnet_am79c973_onboard_device; +/* Modem */ +extern const device_t modem_device; + /* PLIP */ #ifdef EMU_LPT_H extern const lpt_device_t lpt_plip_device; diff --git a/src/network/net_modem.c b/src/network/net_modem.c index 0137aa9bb..4127158bd 100644 --- a/src/network/net_modem.c +++ b/src/network/net_modem.c @@ -52,13 +52,14 @@ typedef struct modem_t { uint8_t mac[6]; serial_t *serial; - double baudrate; + uint32_t baudrate; modem_mode_t mode; uint8_t esc_character_expected; pc_timer_t host_to_serial_timer; pc_timer_t dtr_timer; + pc_timer_t cmdpause_timer; uint8_t tx_pkt_ser_line[0x10000]; /* SLIP-encoded. */ uint32_t tx_count; @@ -70,13 +71,25 @@ typedef struct modem_t char cmdbuf[512]; uint32_t cmdpos; + uint32_t port; int plusinc, flowcontrol; - int in_warmup; + int in_warmup, dtrmode; bool connected, ringing; bool echo, numericresponse; int doresponse; + int cmdpause; + + struct { + bool binary[2]; + bool echo[2]; + bool supressGA[2]; + bool timingMark[2]; + bool inIAC; + bool recCommand; + uint8_t command; + } telClient; netcard_t *card; } modem_t; @@ -92,6 +105,14 @@ static modem_t *instance; #define MREG_GUARD_TIME 12 #define MREG_DTR_DELAY 25 +static void modem_do_command(modem_t* modem); + +static void +modem_echo(modem_t* modem, uint8_t c) +{ + if (modem->echo && fifo8_num_free(&modem->data_pending)) fifo8_push(&modem->data_pending, c); +} + static uint32_t modem_scan_number(char **scan) { @@ -128,7 +149,7 @@ modem_speed_changed(void *priv) timer_stop(&dev->host_to_serial_timer); /* FIXME: do something to dev->baudrate */ - timer_on_auto(&dev->host_to_serial_timer, (1000000.0 / dev->baudrate) * 9); + timer_on_auto(&dev->host_to_serial_timer, (1000000.0 / (double)dev->baudrate) * 9); #if 0 serial_clear_fifo(dev->serial); #endif @@ -210,16 +231,20 @@ static void modem_data_mode_process_byte(modem_t* modem, uint8_t data) { if (modem->reg[MREG_ESCAPE_CHAR] <= 127) { - if (modem->reg[MREG_ESCAPE_CHAR] == data) { - return; + if (modem->plusinc >= 1 && modem->plusinc <= 3 && modem->reg[MREG_ESCAPE_CHAR] == data) { + modem->plusinc++; + } else { + modem->plusinc = 0; } } - if (data == END) { - process_tx_packet(modem, modem->tx_pkt_ser_line, (uint32_t)modem->tx_count + 1ul); - } - else if (modem->tx_count < 0x10000) + if (modem->tx_count < 0x10000 && modem->connected) { modem->tx_pkt_ser_line[modem->tx_count++] = data; + if (data == END) { + process_tx_packet(modem, modem->tx_pkt_ser_line, (uint32_t)modem->tx_count); + modem->tx_count = 0; + } + } } static void @@ -237,6 +262,9 @@ host_to_modem_cb(void *priv) } } + if (!((modem->serial->mctrl & 2) || modem->flowcontrol != 3)) + goto no_write_to_machine; + if (modem->mode == MODEM_MODE_DATA && fifo8_num_used(&modem->rx_data)) { serial_write_fifo(modem->serial, fifo8_pop(&modem->rx_data)); } else if (fifo8_num_used(&modem->data_pending)) { @@ -244,13 +272,55 @@ host_to_modem_cb(void *priv) } no_write_to_machine: - timer_on_auto(&modem->host_to_serial_timer, (1000000.0 / modem->baudrate) * (double)9); + timer_on_auto(&modem->host_to_serial_timer, (1000000.0 / (double)modem->baudrate) * (double)9); } static void -modem_write(UNUSED(serial_t *s), void *priv, uint8_t val) +modem_write(UNUSED(serial_t *s), void *priv, uint8_t txval) { modem_t* modem = (modem_t*)priv; + + if (modem->mode == MODEM_MODE_COMMAND) { + if (modem->cmdpos < 2) { + // Ignore everything until we see "AT" sequence. + if (modem->cmdpos == 0 && toupper(txval) != 'A') { + return; + } + + if (modem->cmdpos == 1 && toupper(txval) != 'T') { + modem_echo(modem, modem->reg[MREG_BACKSPACE_CHAR]); + modem->cmdpos = 0; + return; + } + } else { + // Now entering command. + if (txval == modem->reg[MREG_BACKSPACE_CHAR]) { + if (modem->cmdpos > 2) { + modem_echo(modem, txval); + modem->cmdpos--; + } + return; + } + + if (txval == modem->reg[MREG_LF_CHAR]) { + return; // Real modem doesn't seem to skip this? + } + + if (txval == modem->reg[MREG_CR_CHAR]) { + modem_echo(modem, txval); + modem_do_command(modem); + return; + } + + if (modem->cmdpos < 99) { + modem_echo(modem, txval); + modem->cmdbuf[modem->cmdpos] = txval; + modem->cmdpos++; + } + } + } else { + modem_data_mode_process_byte(modem, txval); + } } void modem_send_res(modem_t* modem, const ResTypes response) { @@ -310,6 +380,7 @@ modem_enter_connected_state(modem_t* modem) modem->mode = MODEM_MODE_DATA; modem->ringing = false; modem->connected = true; + memset(&modem->telClient, 0, sizeof(modem->telClient)); serial_set_dcd(modem->serial, 1); serial_set_ri(modem->serial, 0); } @@ -322,6 +393,7 @@ modem_reset(modem_t* modem) modem->cmdbuf[0] = 0; modem->flowcontrol = 0; modem->plusinc = 0; + modem->dtrmode = 2; memset(&modem->reg,0,sizeof(modem->reg)); modem->reg[MREG_AUTOANSWER_COUNT] = 0; // no autoanswer @@ -560,6 +632,107 @@ modem_do_command(modem_t* modem) return; } } + + case 'S': { // Registers + const uint32_t index = modem_scan_number(&scanbuf); + if (index >= 100) { + modem_send_res(modem, ResERROR); + return; //goto ret_none; + } + + while (scanbuf[0] == ' ') + scanbuf++; // skip spaces + + if (scanbuf[0] == '=') { // set register + scanbuf++; + while (scanbuf[0] == ' ') + scanbuf++; // skip spaces + const uint32_t val = modem_scan_number(&scanbuf); + modem->reg[index] = val; + break; + } + else if (scanbuf[0] == '?') { // get register + modem_send_number(modem, modem->reg[index]); + scanbuf++; + break; + } + // else + // LOG_MSG("SERIAL: Port %" PRIu8 " print reg %" PRIu32 + // " with %" PRIu8 ".", + // GetPortNumber(), index, reg[index]); + } + break; + case '&': { // & escaped commands + char cmdchar = modem_fetch_character(&scanbuf); + switch(cmdchar) { + case 'K': { + const uint32_t val = modem_scan_number(&scanbuf); + if (val < 5) + modem->flowcontrol = val; + else { + modem_send_res(modem, ResERROR); + return; + } + break; + } + case 'D': { + const uint32_t val = modem_scan_number(&scanbuf); + if (val < 4) + modem->dtrmode = val; + else { + modem_send_res(modem, ResERROR); + return; + } + break; + } + case '\0': + // end of string + modem_send_res(modem, ResERROR); + return; + } + break; + } + case '\\': { // \ escaped commands + char cmdchar = modem_fetch_character(&scanbuf); + switch (cmdchar) { + case 'N': + // error correction stuff - not emulated + if (modem_scan_number(&scanbuf) > 5) { + modem_send_res(modem, ResERROR); + return; + } + break; + case '\0': + // end of string + modem_send_res(modem, ResERROR); + return; + } + break; + } + case '\0': + modem_send_res(modem, ResOK); + return; + } + } +} + +void +modem_dtr_callback_timer(void* priv) +{ + modem_t *dev = (modem_t *) priv; + if (dev->connected) { + switch (dev->dtrmode) { + case 1: + dev->mode = MODEM_MODE_COMMAND; + break; + case 2: + modem_send_res(dev, ResNOCARRIER); + modem_enter_idle_state(dev); + break; + case 3: + modem_send_res(dev, ResNOCARRIER); + modem_reset(dev); + break; } } } @@ -571,7 +744,7 @@ modem_dtr_callback(serial_t* serial, int status, void *priv) if (status == 1) timer_disable(&dev->dtr_timer); else if (!timer_is_enabled(&dev->dtr_timer)) - timer_enable(&dev->dtr_timer); + timer_on_auto(&dev->dtr_timer, 1000000); } static void @@ -606,7 +779,12 @@ modem_rx(void *priv, uint8_t *buf, int io_len) uint8_t c = 0; uint32_t i = 0; - if ((io_len) < (fifo8_num_free(&modem->rx_data) / 2)) { + if (!modem->connected) { + /* Drop packet. */ + return 0; + } + + if ((io_len) <= (fifo8_num_free(&modem->rx_data) / 2)) { fifo8_resize_2x(&modem->rx_data); } @@ -637,12 +815,31 @@ modem_rcr_cb(UNUSED(struct serial_s *serial), void *priv) timer_stop(&dev->host_to_serial_timer); /* FIXME: do something to dev->baudrate */ - timer_on_auto(&dev->host_to_serial_timer, (1000000.0 / dev->baudrate) * (double) 9); + timer_on_auto(&dev->host_to_serial_timer, (1000000.0 / (double)dev->baudrate) * (double) 9); #if 0 serial_clear_fifo(dev->serial); #endif } +static void +modem_cmdpause_timer_callback(void *priv) +{ + modem_t *dev = (modem_t *) priv; + uint32_t guard_threashold = 0; + + dev->cmdpause++; + guard_threashold = (uint32_t)(dev->reg[MREG_GUARD_TIME] * 20); + if (dev->cmdpause > guard_threashold) { + if (dev->plusinc == 0) { + dev->plusinc = 1; + } else if (dev->plusinc == 4) { + dev->mode = MODEM_MODE_COMMAND; + modem_send_res(dev, ResOK); + dev->plusinc = 0; + } + } +} + /* Initialize the device for use by the user. */ static void * modem_init(const device_t *info) @@ -650,16 +847,50 @@ modem_init(const device_t *info) modem_t* modem = (modem_t*)calloc(1, sizeof(modem_t)); memset(modem->mac, 0xfc, 6); + + modem->port = device_get_config_int("port"); + modem->baudrate = device_get_config_int("baudrate"); + + fifo8_create(&modem->data_pending, 0x10000); + fifo8_create(&modem->rx_data, 0x10000); + + timer_add(&modem->dtr_timer, modem_dtr_callback_timer, modem, 0); + timer_add(&modem->host_to_serial_timer, host_to_modem_cb, modem, 0); + timer_add(&modem->cmdpause_timer, modem_cmdpause_timer_callback, modem, 0); + timer_on_auto(&modem->cmdpause_timer, 1000); + modem->serial = serial_attach_ex_2(modem->port, modem_rcr_cb, modem_write, modem_dtr_callback, modem); + + modem_reset(modem); modem->card = network_attach(instance, instance->mac, modem_rx, NULL); return modem; } void modem_close(void *priv) { + modem_t* modem = (modem_t*)priv; + fifo8_destroy(&modem->data_pending); + fifo8_destroy(&modem->rx_data); + netcard_close(modem->card); free(priv); } static const device_config_t modem_config[] = { + { + .name = "port", + .description = "Serial Port", + .type = CONFIG_SELECTION, + .default_string = "", + .default_int = 0, + .file_filter = "", + .spinner = { 0 }, + .selection = { + { .description = "COM1", .value = 0 }, + { .description = "COM2", .value = 1 }, + { .description = "COM3", .value = 2 }, + { .description = "COM4", .value = 3 }, + { .description = "" } + } + }, { .name = "baudrate", .description = "Baud Rate", @@ -669,10 +900,6 @@ static const device_config_t modem_config[] = { .file_filter = NULL, .spinner = { 0 }, .selection = { -#if 0 - { .description = "256000", .value = 256000 }, - { .description = "128000", .value = 128000 }, -#endif { .description = "115200", .value = 115200 }, { .description = "57600", .value = 57600 }, { .description = "56000", .value = 56000 }, @@ -694,7 +921,7 @@ static const device_config_t modem_config[] = { const device_t modem_device = { .name = "Standard Hayes-compliant Modem", - .flags = 0, + .flags = DEVICE_COM, .local = 0, .init = modem_init, .close = modem_close, diff --git a/src/network/network.c b/src/network/network.c index 9eb537e3a..94403c2f4 100644 --- a/src/network/network.c +++ b/src/network/network.c @@ -136,6 +136,7 @@ static const device_t *net_cards[] = { &dec_tulip_21140_vpc_device, &dec_tulip_21040_device, &pcnet_am79c960_vlb_device, + &modem_device, NULL }; From b5db53368ff71b6ea4bfeeaf4180baa70b99f505 Mon Sep 17 00:00:00 2001 From: Cacodemon345 Date: Sat, 9 Mar 2024 14:58:12 +0600 Subject: [PATCH 3/9] Logging and crash fixes --- src/network/net_modem.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/network/net_modem.c b/src/network/net_modem.c index 4127158bd..b845896d3 100644 --- a/src/network/net_modem.c +++ b/src/network/net_modem.c @@ -268,7 +268,9 @@ host_to_modem_cb(void *priv) if (modem->mode == MODEM_MODE_DATA && fifo8_num_used(&modem->rx_data)) { serial_write_fifo(modem->serial, fifo8_pop(&modem->rx_data)); } else if (fifo8_num_used(&modem->data_pending)) { - serial_write_fifo(modem->serial, fifo8_pop(&modem->data_pending)); + uint8_t val = fifo8_pop(&modem->data_pending); + fprintf(stderr, "%c", val); + serial_write_fifo(modem->serial, val); } no_write_to_machine: @@ -281,6 +283,7 @@ modem_write(UNUSED(serial_t *s), void *priv, uint8_t txval) modem_t* modem = (modem_t*)priv; if (modem->mode == MODEM_MODE_COMMAND) { + fprintf(stderr, "%c", txval); if (modem->cmdpos < 2) { // Ignore everything until we see "AT" sequence. if (modem->cmdpos == 0 && toupper(txval) != 'A') { @@ -861,7 +864,7 @@ modem_init(const device_t *info) modem->serial = serial_attach_ex_2(modem->port, modem_rcr_cb, modem_write, modem_dtr_callback, modem); modem_reset(modem); - modem->card = network_attach(instance, instance->mac, modem_rx, NULL); + modem->card = network_attach(modem, modem->mac, modem_rx, NULL); return modem; } @@ -921,6 +924,7 @@ static const device_config_t modem_config[] = { const device_t modem_device = { .name = "Standard Hayes-compliant Modem", + .internal_name = "modem", .flags = DEVICE_COM, .local = 0, .init = modem_init, From af30550d946b097116143f440253410a71b3c601 Mon Sep 17 00:00:00 2001 From: Cacodemon345 Date: Sat, 9 Mar 2024 15:49:08 +0600 Subject: [PATCH 4/9] Fix command mode --- src/network/net_modem.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/network/net_modem.c b/src/network/net_modem.c index b845896d3..97f038157 100644 --- a/src/network/net_modem.c +++ b/src/network/net_modem.c @@ -267,9 +267,9 @@ host_to_modem_cb(void *priv) if (modem->mode == MODEM_MODE_DATA && fifo8_num_used(&modem->rx_data)) { serial_write_fifo(modem->serial, fifo8_pop(&modem->rx_data)); + fprintf(stderr, "(data)\n"); } else if (fifo8_num_used(&modem->data_pending)) { uint8_t val = fifo8_pop(&modem->data_pending); - fprintf(stderr, "%c", val); serial_write_fifo(modem->serial, val); } @@ -283,7 +283,6 @@ modem_write(UNUSED(serial_t *s), void *priv, uint8_t txval) modem_t* modem = (modem_t*)priv; if (modem->mode == MODEM_MODE_COMMAND) { - fprintf(stderr, "%c", txval); if (modem->cmdpos < 2) { // Ignore everything until we see "AT" sequence. if (modem->cmdpos == 0 && toupper(txval) != 'A') { @@ -314,12 +313,12 @@ modem_write(UNUSED(serial_t *s), void *priv, uint8_t txval) modem_do_command(modem); return; } + } - if (modem->cmdpos < 99) { - modem_echo(modem, txval); - modem->cmdbuf[modem->cmdpos] = txval; - modem->cmdpos++; - } + if (modem->cmdpos < 99) { + modem_echo(modem, txval); + modem->cmdbuf[modem->cmdpos] = txval; + modem->cmdpos++; } } else { modem_data_mode_process_byte(modem, txval); @@ -493,6 +492,8 @@ modem_do_command(modem_t* modem) return; } + pclog("Command received: %s (doresponse = %d)\n", modem->cmdbuf, modem->doresponse); + scanbuf = &modem->cmdbuf[2]; while (1) { From bc6b659e02b976430321c053f9d78a5c64759bc3 Mon Sep 17 00:00:00 2001 From: Cacodemon345 Date: Mon, 11 Mar 2024 01:49:58 +0600 Subject: [PATCH 5/9] SLIP works properly now Preparation for modem phonebook files --- src/network/CMakeLists.txt | 2 +- src/network/net_modem.c | 101 +++++++++++++++++++++++++++++++++--- src/network/net_slirp.c | 50 ++++++++++++++++++ src/network/network.c | 8 ++- src/network/utils/getline.c | 81 +++++++++++++++++++++++++++++ 5 files changed, 234 insertions(+), 8 deletions(-) create mode 100644 src/network/utils/getline.c diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 5eb091d58..e407d4364 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -15,7 +15,7 @@ set(net_sources) list(APPEND net_sources network.c net_pcap.c net_slirp.c net_dp8390.c net_3c501.c net_3c503.c net_ne2000.c net_pcnet.c net_wd8003.c net_plip.c net_event.c net_null.c - net_eeprom_nmc93cxx.c net_tulip.c net_rtl8139.c net_l80225.c net_modem.c) + net_eeprom_nmc93cxx.c net_tulip.c net_rtl8139.c net_l80225.c net_modem.c utils/getline.c) find_package(PkgConfig REQUIRED) pkg_check_modules(SLIRP REQUIRED IMPORTED_TARGET slirp) diff --git a/src/network/net_modem.c b/src/network/net_modem.c index 97f038157..2396863bd 100644 --- a/src/network/net_modem.c +++ b/src/network/net_modem.c @@ -1,3 +1,6 @@ + +/* TODO: SLIP support. */ + #include #include #include @@ -14,6 +17,7 @@ #include <86box/fifo8.h> #include <86box/timer.h> #include <86box/serial.h> +#include <86box/plat.h> #include <86box/network.h> #include <86box/plat_unused.h> @@ -48,6 +52,18 @@ typedef enum modem_mode_t MODEM_MODE_DATA = 1 } modem_mode_t; +typedef enum modem_slip_stage_t +{ + MODEM_SLIP_STAGE_USERNAME, + MODEM_SLIP_STAGE_PASSWORD +} modem_slip_stage_t; + +typedef struct modem_phonebook_entry_t +{ + char phone[1024]; + char address[1024]; +} modem_phonebook_entry_t; + typedef struct modem_t { uint8_t mac[6]; @@ -90,6 +106,9 @@ typedef struct modem_t bool recCommand; uint8_t command; } telClient; + + modem_phonebook_entry_t entries[20]; + uint32_t entries_num; netcard_t *card; } modem_t; @@ -107,6 +126,43 @@ static modem_t *instance; static void modem_do_command(modem_t* modem); +extern ssize_t local_getline(char **buf, size_t *bufsiz, FILE *fp); + +static void +modem_read_phonebook_file(modem_t* modem, const char* path) +{ + FILE* file = plat_fopen(path, "r"); + char* buf = NULL; + size_t size = 0; + if (!file) + return; + + modem->entries_num = 0; + + while (local_getline(&buf, &size, file) != -1) { + modem_phonebook_entry_t entry = { { 0 }, { 0 } }; + int res = 0; + buf[strcspn(buf, "\r\n")] = 0; + + res = sscanf(buf, "%s %s", entry.phone, entry.address); + + if (res == 0 || res == 1) { + /* Appears to be a bad line. */ + continue; + } + + if (strspn(entry.phone, "01234567890*=,;#+>") != strlen(entry.phone)) { + /* Invalid characters. */ + continue; + } + + modem->entries[modem->entries_num++] = entry; + if (modem->entries_num >= 20) + break; + } + fclose(file); +} + static void modem_echo(modem_t* modem, uint8_t c) { @@ -190,6 +246,8 @@ process_tx_packet(modem_t *modem, uint8_t *p, uint32_t len) uint8_t *processed_tx_packet = calloc(len, 1); uint8_t c = 0; + pclog("Processing SLIP packet of %u bytes\n", len); + while (pos < len) { c = p[pos]; pos++; @@ -223,7 +281,18 @@ process_tx_packet(modem_t *modem, uint8_t *p, uint32_t len) } send_tx_packet: - network_tx(modem->card, processed_tx_packet, received); + if (received) + { + uint8_t* buf = calloc(received + 14, 1); + buf[0] = buf[1] = buf[2] = buf[3] = buf[4] = buf[5] = 0xFF; + buf[6] = buf[7] = buf[8] = buf[9] = buf[10] = buf[11] = 0xFC; + buf[12] = 0x08; + buf[13] = 0x00; + memcpy(buf + 14, processed_tx_packet, received); + network_tx(modem->card, buf, received + 14); + free(processed_tx_packet); + free(buf); + } return; } @@ -267,7 +336,6 @@ host_to_modem_cb(void *priv) if (modem->mode == MODEM_MODE_DATA && fifo8_num_used(&modem->rx_data)) { serial_write_fifo(modem->serial, fifo8_pop(&modem->rx_data)); - fprintf(stderr, "(data)\n"); } else if (fifo8_num_used(&modem->data_pending)) { uint8_t val = fifo8_pop(&modem->data_pending); serial_write_fifo(modem->serial, val); @@ -326,11 +394,15 @@ modem_write(UNUSED(serial_t *s), void *priv, uint8_t txval) } void modem_send_res(modem_t* modem, const ResTypes response) { + char response_str_connect[256] = { 0 }; const char* response_str = NULL; uint32_t code = -1; + + snprintf(response_str_connect, sizeof(response_str_connect), "CONNECT %u", modem->baudrate); + switch (response) { case ResOK: code = 0; response_str = "OK"; break; - case ResCONNECT: code = 1; response_str = "CONNECT 33600"; break; + case ResCONNECT: code = 1; response_str = response_str_connect; break; case ResRING: code = 2; response_str = "RING"; break; case ResNOCARRIER: code = 3; response_str = "NO CARRIER"; break; case ResERROR: code = 4; response_str = "ERROR"; break; @@ -416,9 +488,13 @@ void modem_dial(modem_t* modem, const char* str) { /* TODO: Port TCP/IP support from DOSBox. */ - if (!strncmp(str, "0.0.0.0", sizeof("0.0.0.0") - 1)) { + if (!strncmp(str, "0.0.0.0", sizeof("0.0.0.0") - 1)) + { + pclog("Turning on SLIP\n"); modem_enter_connected_state(modem); - } else { + } + else + { modem_send_res(modem, ResNOCARRIER); modem_enter_idle_state(modem); } @@ -779,19 +855,31 @@ fifo8_resize_2x(Fifo8* fifo) static int modem_rx(void *priv, uint8_t *buf, int io_len) { +#if 1 modem_t* modem = (modem_t*)priv; uint8_t c = 0; uint32_t i = 0; if (!modem->connected) { /* Drop packet. */ + pclog("Dropping %d bytes\n", io_len - 14); return 0; } - if ((io_len) <= (fifo8_num_free(&modem->rx_data) / 2)) { + if ((io_len) >= (fifo8_num_free(&modem->rx_data))) { fifo8_resize_2x(&modem->rx_data); } + if (!(buf[12] == 0x08 && buf[13] == 0x00)) { + pclog("Dropping %d bytes (non-IP packet (ethtype 0x%02X%02X))\n", io_len - 14, buf[12], buf[13]); + return 0; + } + + pclog("Receiving %d bytes\n", io_len - 14); + /* Strip the Ethernet header. */ + io_len -= 14; + buf += 14; + fifo8_push(&modem->rx_data, END); for (i = 0; i < io_len; i++) { switch (buf[i]) { @@ -810,6 +898,7 @@ modem_rx(void *priv, uint8_t *buf, int io_len) } fifo8_push(&modem->rx_data, END); return 1; +#endif } static void diff --git a/src/network/net_slirp.c b/src/network/net_slirp.c index 6aff76a90..599ee896d 100644 --- a/src/network/net_slirp.c +++ b/src/network/net_slirp.c @@ -37,6 +37,7 @@ #include <86box/ini.h> #include <86box/config.h> #include <86box/video.h> +#include <86box/bswap.h> #define _SSIZE_T_DEFINED #include @@ -76,6 +77,29 @@ typedef struct net_slirp_t { #endif } net_slirp_t; +/* Pulled off from libslirp code. This is only needed for modem. */ +#pragma pack(push, 1) +struct arphdr_local { + unsigned char h_dest[6]; /* destination eth addr */ + unsigned char h_source[6]; /* source ether addr */ + unsigned short h_proto; /* packet type ID field */ + + unsigned short ar_hrd; /* format of hardware address */ + unsigned short ar_pro; /* format of protocol address */ + unsigned char ar_hln; /* length of hardware address */ + unsigned char ar_pln; /* length of protocol address */ + unsigned short ar_op; /* ARP opcode (command) */ + + /* + * Ethernet looks like this : This bit is variable sized however... + */ + uint8_t ar_sha[6]; /* sender hardware address */ + uint32_t ar_sip; /* sender IP address */ + uint8_t ar_tha[6]; /* target hardware address */ + uint32_t ar_tip; /* target IP address */ +}; +#pragma pack(pop) + #ifdef ENABLE_SLIRP_LOG int slirp_do_log = ENABLE_SLIRP_LOG; @@ -455,6 +479,32 @@ net_slirp_init(const netcard_t *card, const uint8_t *mac_addr, UNUSED(void *priv #ifdef _WIN32 slirp->sock_event = CreateEvent(NULL, FALSE, FALSE, NULL); #endif + + if (!strcmp(network_card_get_internal_name(net_cards_conf[net_card_current].device_num), "modem")) { + /* Send a gratuitous ARP here to make SLiRP work properly with SLIP connections. */ + struct arphdr_local arphdr; + /* ARP part. */ + arphdr.ar_hrd = bswap16(1); + arphdr.ar_pro = bswap16(0x0800); + arphdr.ar_hln = 6; + arphdr.ar_pln = 4; + arphdr.ar_op = bswap16(1); + memcpy(&arphdr.ar_sha, mac_addr, 6); + memcpy(&arphdr.ar_tha, mac_addr, 6); + arphdr.ar_sip = dhcp.s_addr; + arphdr.ar_tip = dhcp.s_addr; + + /* Ethernet header part. */ + arphdr.h_proto = bswap16(0x0806); + memset(arphdr.h_dest, 0xff, 6); + memset(arphdr.h_source, 0x52, 6); + arphdr.h_source[2] = 0x0a; + arphdr.h_source[3] = 0x00; + arphdr.h_source[4] = slirp_card_num; + arphdr.h_source[5] = 2; + slirp_input(slirp->slirp, (const uint8_t *)&arphdr, sizeof(struct arphdr_local)); + } + slirp_log("SLiRP: creating thread...\n"); slirp->poll_tid = thread_create(net_slirp_thread, slirp); diff --git a/src/network/network.c b/src/network/network.c index 94403c2f4..fe3bf8489 100644 --- a/src/network/network.c +++ b/src/network/network.c @@ -454,6 +454,7 @@ netcard_t * network_attach(void *card_drv, uint8_t *mac, NETRXCB rx, NETSETLINKSTATE set_link_state) { netcard_t *card = calloc(1, sizeof(netcard_t)); + int net_type = net_cards_conf[net_card_current].net_type; card->queued_pkt.data = calloc(1, NET_MAX_FRAME); card->card_drv = card_drv; card->rx = rx; @@ -470,7 +471,12 @@ network_attach(void *card_drv, uint8_t *mac, NETRXCB rx, NETSETLINKSTATE set_lin network_queue_init(&card->queues[i]); } - switch (net_cards_conf[net_card_current].net_type) { + if (!strcmp(network_card_get_internal_name(net_cards_conf[net_card_current].device_num), "modem") && net_type >= NET_TYPE_PCAP) { + /* Force SLiRP here. Modem only operates on non-Ethernet frames. */ + net_type = NET_TYPE_SLIRP; + } + + switch (net_type) { case NET_TYPE_SLIRP: card->host_drv = net_slirp_drv; card->host_drv.priv = card->host_drv.init(card, mac, NULL, net_drv_error); diff --git a/src/network/utils/getline.c b/src/network/utils/getline.c new file mode 100644 index 000000000..69ee258fd --- /dev/null +++ b/src/network/utils/getline.c @@ -0,0 +1,81 @@ +/*- + * Copyright (c) 2011 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Christos Zoulas. + * + * 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 above copyright + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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 + +ssize_t +local_getdelim(char **buf, size_t *bufsiz, int delimiter, FILE *fp) +{ + char *ptr, *eptr; + + + if (*buf == NULL || *bufsiz == 0) { + *bufsiz = BUFSIZ; + if ((*buf = malloc(*bufsiz)) == NULL) + return -1; + } + + for (ptr = *buf, eptr = *buf + *bufsiz;;) { + int c = fgetc(fp); + if (c == -1) { + if (feof(fp)) { + ssize_t diff = (ssize_t)(ptr - *buf); + if (diff != 0) { + *ptr = '\0'; + return diff; + } + } + return -1; + } + *ptr++ = c; + if (c == delimiter) { + *ptr = '\0'; + return ptr - *buf; + } + if (ptr + 2 >= eptr) { + char *nbuf; + size_t nbufsiz = *bufsiz * 2; + ssize_t d = ptr - *buf; + if ((nbuf = realloc(*buf, nbufsiz)) == NULL) + return -1; + *buf = nbuf; + *bufsiz = nbufsiz; + eptr = nbuf + nbufsiz; + ptr = nbuf + d; + } + } +} + +ssize_t +local_getline(char **buf, size_t *bufsiz, FILE *fp) +{ + return local_getdelim(buf, bufsiz, '\n', fp); +} From 8fd35fccb8316328ca8a3a11408812cf19456584 Mon Sep 17 00:00:00 2001 From: Cacodemon345 Date: Mon, 11 Mar 2024 02:02:49 +0600 Subject: [PATCH 6/9] Make fifo8 resizing code more reliable --- src/network/net_modem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/net_modem.c b/src/network/net_modem.c index 2396863bd..131b87653 100644 --- a/src/network/net_modem.c +++ b/src/network/net_modem.c @@ -866,7 +866,7 @@ modem_rx(void *priv, uint8_t *buf, int io_len) return 0; } - if ((io_len) >= (fifo8_num_free(&modem->rx_data))) { + if ((io_len) >= (fifo8_num_free(&modem->rx_data) / 2)) { fifo8_resize_2x(&modem->rx_data); } From 9f5d2a46bdb21a1ed5ee5a8e692f199b6d1a9742 Mon Sep 17 00:00:00 2001 From: Cacodemon345 Date: Mon, 11 Mar 2024 16:35:57 +0600 Subject: [PATCH 7/9] Platform-specific netwok sockets --- src/include/86box/plat_netsocket.h | 24 +++++ src/qt/CMakeLists.txt | 2 +- src/win/win_netsocket.c | 148 +++++++++++++++++++++++++++++ 3 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 src/include/86box/plat_netsocket.h create mode 100644 src/win/win_netsocket.c diff --git a/src/include/86box/plat_netsocket.h b/src/include/86box/plat_netsocket.h new file mode 100644 index 000000000..269f399ff --- /dev/null +++ b/src/include/86box/plat_netsocket.h @@ -0,0 +1,24 @@ +#ifndef _WIN32 +#define SOCKET int +#else +#include +#include +#endif + +enum net_socket_types +{ + /* Only TCP is supported for now. */ + NET_SOCKET_TCP +}; + +SOCKET plat_netsocket_create(int type); +SOCKET plat_netsocket_create_server(int type, unsigned short port); +void plat_netsocket_close(SOCKET socket); + +SOCKET plat_netsocket_accept(SOCKET socket); +int plat_netsocket_connected(SOCKET socket); +int plat_netsocket_connect(SOCKET socket, const char* hostname, unsigned short port); + +/* Returns 0 in case of inability to send. -1 in case of errors. */ +int plat_netsocket_send(SOCKET socket, const unsigned char* data, unsigned int size, int *wouldblock); +int plat_netsocket_receive(SOCKET socket, unsigned char* data, unsigned int size, int *wouldblock); \ No newline at end of file diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index 4f073cf4a..48b377044 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -221,7 +221,7 @@ if(WIN32 AND NOT MINGW) endif() if(WIN32) - target_sources(plat PRIVATE ../win/win_serial_passthrough.c) + target_sources(plat PRIVATE ../win/win_serial_passthrough.c ../win/win_netsocket.c) else() target_sources(plat PRIVATE ../unix/unix_serial_passthrough.c) endif() diff --git a/src/win/win_netsocket.c b/src/win/win_netsocket.c new file mode 100644 index 000000000..45ca1f760 --- /dev/null +++ b/src/win/win_netsocket.c @@ -0,0 +1,148 @@ +#define _XOPEN_SOURCE 500 +#include +#include +#include +#include +#include +#include + +#include <86box/86box.h> +#include <86box/log.h> +#include <86box/timer.h> +#include <86box/plat.h> +#include <86box/device.h> +#include <86box/plat_netsocket.h> +#include <86box/ui.h> + +#include +#include +#include + +SOCKET plat_netsocket_create(int type) +{ + SOCKET socket = -1; + u_long yes = 1; + + if (type != NET_SOCKET_TCP) + return -1; + + socket = WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED); + if (socket == INVALID_SOCKET) + return -1; + + ioctlsocket(socket, FIONBIO, &yes); + + return socket; +} + +SOCKET plat_netsocket_create_server(int type, unsigned short port) +{ + struct sockaddr_in sock_addr; + SOCKET socket = plat_netsocket_create(type); + if (socket == INVALID_SOCKET) + return (SOCKET)-1; + + memset(&sock_addr, 0, sizeof(struct sockaddr_in)); + + sock_addr.sin_family = AF_INET; + sock_addr.sin_addr.s_addr = INADDR_ANY; + sock_addr.sin_port = port; + + if (bind(socket, (struct sockaddr *)&sock_addr, sizeof(struct sockaddr_in)) == SOCKET_ERROR) { + plat_netsocket_close(socket); + return (SOCKET)-1; + } + + if (listen(socket, 5) == SOCKET_ERROR) { + plat_netsocket_close(socket); + return (SOCKET)-1; + } + + return socket; +} + +void plat_netsocket_close(SOCKET socket) +{ + closesocket((SOCKET)socket); +} + +SOCKET plat_netsocket_accept(SOCKET socket) +{ + SOCKET clientsocket = accept(socket, NULL, NULL); + + if (clientsocket == INVALID_SOCKET) + return -1; + + return clientsocket; +} + +int plat_netsocket_connected(SOCKET socket) +{ + struct sockaddr addr; + socklen_t len = sizeof(struct sockaddr); + if (getpeername(socket, &addr, &len) == SOCKET_ERROR) + return 0; + + return 1; +} + +int plat_netsocket_connect(SOCKET socket, const char* hostname, unsigned short port) +{ + struct sockaddr_in sock_addr; + int res = -1; + + sock_addr.sin_family = AF_INET; + sock_addr.sin_addr.s_addr = inet_addr(hostname); + sock_addr.sin_port = htons(port); + + if (sock_addr.sin_addr.s_addr == -1 || sock_addr.sin_addr.s_addr == 0) { + struct hostent *hp; + + hp = gethostbyname(hostname); + + if (hp) + memcpy(&sock_addr.sin_addr.s_addr, hp->h_addr_list[0], hp->h_length); + else + return -1; + } + + res = connect(socket, (struct sockaddr *)&sock_addr, sizeof(struct sockaddr_in)); + + if (res == SOCKET_ERROR) { + int error = WSAGetLastError(); + + if (error == WSAEISCONN) + return 0; + } + return res; +} + +int plat_netsocket_send(SOCKET socket, const unsigned char* data, unsigned int size, int *wouldblock) +{ + int res = send(socket, (const char*)data, size, 0); + + if (res == SOCKET_ERROR) { + int error = WSAGetLastError(); + + if (wouldblock) + *wouldblock = !!(error == WSAEWOULDBLOCK); + + return -1; + } + return res; +} + +int plat_netsocket_receive(SOCKET socket, unsigned char* data, unsigned int size, int *wouldblock) +{ + int res = recv(socket, (char*)data, size, 0); + + if (res == SOCKET_ERROR) { + int error = WSAGetLastError(); + + if (wouldblock) + *wouldblock = !!(error == WSAEWOULDBLOCK); + + return -1; + } + return res; +} From 10e0dbaafa4e735ca48f2fff33be4f934ccd683d Mon Sep 17 00:00:00 2001 From: Cacodemon345 Date: Tue, 12 Mar 2024 01:55:17 +0600 Subject: [PATCH 8/9] Netsocket changes --- src/include/86box/plat_netsocket.h | 2 +- src/win/win_netsocket.c | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/include/86box/plat_netsocket.h b/src/include/86box/plat_netsocket.h index 269f399ff..9a2181ae1 100644 --- a/src/include/86box/plat_netsocket.h +++ b/src/include/86box/plat_netsocket.h @@ -16,7 +16,7 @@ SOCKET plat_netsocket_create_server(int type, unsigned short port); void plat_netsocket_close(SOCKET socket); SOCKET plat_netsocket_accept(SOCKET socket); -int plat_netsocket_connected(SOCKET socket); +int plat_netsocket_connected(SOCKET socket); /* Returns -1 on trouble. */ int plat_netsocket_connect(SOCKET socket, const char* hostname, unsigned short port); /* Returns 0 in case of inability to send. -1 in case of errors. */ diff --git a/src/win/win_netsocket.c b/src/win/win_netsocket.c index 45ca1f760..044a59860 100644 --- a/src/win/win_netsocket.c +++ b/src/win/win_netsocket.c @@ -80,6 +80,9 @@ int plat_netsocket_connected(SOCKET socket) { struct sockaddr addr; socklen_t len = sizeof(struct sockaddr); + + + if (getpeername(socket, &addr, &len) == SOCKET_ERROR) return 0; @@ -113,6 +116,8 @@ int plat_netsocket_connect(SOCKET socket, const char* hostname, unsigned short p if (error == WSAEISCONN) return 0; + + res = -1; } return res; } From babadfb5c28c1533fa67030caa5de1b0993af161 Mon Sep 17 00:00:00 2001 From: Cacodemon345 Date: Tue, 12 Mar 2024 16:23:35 +0600 Subject: [PATCH 9/9] Handle large packets being sent --- src/network/net_modem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/net_modem.c b/src/network/net_modem.c index 131b87653..120887bcc 100644 --- a/src/network/net_modem.c +++ b/src/network/net_modem.c @@ -866,7 +866,7 @@ modem_rx(void *priv, uint8_t *buf, int io_len) return 0; } - if ((io_len) >= (fifo8_num_free(&modem->rx_data) / 2)) { + while ((io_len) >= (fifo8_num_free(&modem->rx_data) / 2)) { fifo8_resize_2x(&modem->rx_data); }