/* * 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. * * Hayes AT-compliant modem emulation. * * * * Authors: Cacodemon345 * The DOSBox Team * * Copyright 2024 Cacodemon345 * Copyright 2002-2021 The DOSBox Team */ #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/plat.h> #include <86box/network.h> #include <86box/plat_unused.h> #include <86box/plat_netsocket.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 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]; serial_t *serial; 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; 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; uint32_t port; int plusinc, flowcontrol; int in_warmup, dtrmode; bool connected, ringing; bool echo, numericresponse; bool tcpIpMode, tcpIpConnInProgress; uint32_t tcpIpConnCounter; int doresponse; int cmdpause; int listen_port; int ringtimer; SOCKET serversocket; SOCKET clientsocket; SOCKET waitingclientsocket; struct { bool binary[2]; bool echo[2]; bool supressGA[2]; bool timingMark[2]; bool inIAC; bool recCommand; uint8_t command; } telClient; modem_phonebook_entry_t entries[20]; uint32_t entries_num; 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 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) { if (modem->echo && fifo8_num_free(&modem->data_pending)) fifo8_push(&modem->data_pending, c); } 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 / (double)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; pclog("Processing SLIP packet of %u bytes\n", len); 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: 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; } static void modem_data_mode_process_byte(modem_t* modem, uint8_t data) { if (modem->reg[MREG_ESCAPE_CHAR] <= 127) { if (modem->plusinc >= 1 && modem->plusinc <= 3 && modem->reg[MREG_ESCAPE_CHAR] == data) { modem->plusinc++; } else { modem->plusinc = 0; } } if (modem->tx_count < 0x10000 && modem->connected) { modem->tx_pkt_ser_line[modem->tx_count++] = data; if (data == END && !modem->tcpIpMode) { process_tx_packet(modem, modem->tx_pkt_ser_line, (uint32_t)modem->tx_count); modem->tx_count = 0; } } } static void host_to_modem_cb(void *priv) { modem_t* modem = (modem_t*)priv; if (modem->in_warmup) goto no_write_to_machine; 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->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)) { uint8_t val = fifo8_pop(&modem->data_pending); serial_write_fifo(modem->serial, val); } no_write_to_machine: 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 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) { 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 = 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; 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; modem->tcpIpConnInProgress = 0; modem->tcpIpConnCounter = 0; if (modem->waitingclientsocket != (SOCKET)-1) plat_netsocket_close(modem->waitingclientsocket); if (modem->clientsocket != (SOCKET)-1) plat_netsocket_close(modem->clientsocket); modem->clientsocket = modem->waitingclientsocket = (SOCKET)-1; if (modem->serversocket != (SOCKET)-1) { modem->waitingclientsocket = plat_netsocket_accept(modem->serversocket); while (modem->waitingclientsocket != (SOCKET)-1) { plat_netsocket_close(modem->waitingclientsocket); modem->waitingclientsocket = plat_netsocket_accept(modem->serversocket); } plat_netsocket_close(modem->serversocket); modem->serversocket = (SOCKET)-1; } if (modem->waitingclientsocket != (SOCKET)-1) plat_netsocket_close(modem->waitingclientsocket); modem->waitingclientsocket = (SOCKET)-1; modem->tcpIpMode = false; modem->tcpIpConnInProgress = false; if (modem->listen_port) { modem->serversocket = plat_netsocket_create_server(NET_SOCKET_TCP, modem->listen_port); if (modem->serversocket == (SOCKET)-1) { pclog("Failed to set up server on port %d\n", modem->listen_port); } } 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; modem->tcpIpMode = true; plat_netsocket_close(modem->serversocket); modem->serversocket = -1; memset(&modem->telClient, 0, sizeof(modem->telClient)); 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; modem->dtrmode = 2; 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. */ modem->tcpIpConnCounter = 0; modem->tcpIpMode = false; if (!strncmp(str, "0.0.0.0", sizeof("0.0.0.0") - 1)) { pclog("Turning on SLIP\n"); modem_enter_connected_state(modem); modem->tcpIpMode = false; } else { char buf[128] = ""; const char *destination = buf; strcpy(buf, str); // Scan host for port uint16_t port; char * hasport = strrchr(buf,':'); if (hasport) { *hasport++ = 0; port = (uint16_t)atoi(hasport); } else { port = 23; } modem->clientsocket = plat_netsocket_create(NET_SOCKET_TCP); if (modem->clientsocket == -1) { pclog("Failed to create client socket\n"); modem_send_res(modem, ResNOCARRIER); modem_enter_idle_state(modem); return; } if (-1 == plat_netsocket_connect(modem->clientsocket, buf, port)) { pclog("Failed to connect to %s\n", buf); modem_send_res(modem, ResNOCARRIER); modem_enter_idle_state(modem); return; } modem->tcpIpConnInProgress = 1; modem->tcpIpConnCounter = 0; } } 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 const char *modem_get_address_from_phonebook(modem_t* modem, const char *input) { int i = 0; for (i = 0; i < modem->entries_num; i++) { if (strcmp(input, modem->entries[i].phone) == 0) return modem->entries[i].address; } return NULL; } 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; } pclog("Command received: %s (doresponse = %d)\n", modem->cmdbuf, modem->doresponse); 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]; const char *mappedaddr = NULL; 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); mappedaddr = modem_get_address_from_phonebook(modem, foundstr); if (mappedaddr) { modem_dial(modem, mappedaddr); return; } 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); break; } 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; } } 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; } } } 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_on_auto(&dev->dtr_timer, 1000000); } 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) { #if 1 modem_t* modem = (modem_t*)priv; uint8_t c = 0; uint32_t i = 0; if (modem->tcpIpMode) return 0; if (!modem->connected) { /* Drop packet. */ pclog("Dropping %d bytes\n", io_len - 14); return 0; } while ((io_len) >= (fifo8_num_free(&modem->rx_data) / 2)) { 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]) { 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; #endif } 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 / (double)dev->baudrate) * (double) 9); #if 0 serial_clear_fifo(dev->serial); #endif } static void modem_accept_incoming_call(modem_t* modem) { if (modem->waitingclientsocket != -1) { modem->clientsocket = modem->waitingclientsocket; modem->waitingclientsocket = -1; modem_enter_connected_state(modem); modem->in_warmup = 250; } else { modem_enter_idle_state(modem); } } static void modem_cmdpause_timer_callback(void *priv) { modem_t *modem = (modem_t *) priv; uint32_t guard_threashold = 0; timer_on_auto(&modem->cmdpause_timer, 1000); if (modem->tcpIpConnInProgress) { do { int status = plat_netsocket_connected(modem->clientsocket); if (status == -1) { plat_netsocket_close(modem->clientsocket); modem->clientsocket = -1; modem_enter_idle_state(modem); modem_send_res(modem, ResNOCARRIER); modem->tcpIpConnInProgress = 0; break; } else if (status == 1) { modem_enter_connected_state(modem); modem->tcpIpConnInProgress = 0; break; } modem->tcpIpConnCounter++; if (status < 0 || (status == 0 && modem->tcpIpConnCounter >= 5000)) { plat_netsocket_close(modem->clientsocket); modem->clientsocket = -1; modem_enter_idle_state(modem); modem_send_res(modem, ResNOANSWER); modem->tcpIpConnInProgress = 0; modem->tcpIpMode = 0; break; } } while (0); } if (!modem->connected && modem->waitingclientsocket == -1 && modem->serversocket != -1) { modem->waitingclientsocket = plat_netsocket_accept(modem->serversocket); if (modem->waitingclientsocket != -1) { if (!(modem->serial->mctrl & 1) && modem->dtrmode != 0) { modem_enter_idle_state(modem); } else { modem->ringing = true; modem_send_res(modem, ResRING); serial_set_ri(modem->serial, !serial_get_ri(modem->serial)); modem->ringtimer = 3000; modem->reg[MREG_RING_COUNT] = 0; } } } if (modem->ringing) { if (modem->ringtimer <= 0) { modem->reg[MREG_RING_COUNT]++; if ((modem->reg[MREG_AUTOANSWER_COUNT] > 0) && (modem->reg[MREG_RING_COUNT] >= modem->reg[MREG_AUTOANSWER_COUNT])) { modem_accept_incoming_call(modem); return; } modem_send_res(modem, ResRING); serial_set_ri(modem->serial, !serial_get_ri(modem->serial)); modem->ringtimer = 3000; } --modem->ringtimer; } if (modem->in_warmup) { modem->in_warmup--; if (modem->in_warmup == 0) { modem->tx_count = 0; fifo8_reset(&modem->rx_data); } } else if (modem->connected && modem->tcpIpMode) { if (modem->tx_count) { int wouldblock = 0; int res = plat_netsocket_send(modem->clientsocket, modem->tx_pkt_ser_line, modem->tx_count, &wouldblock); if (res <= 0 && !wouldblock) { /* No bytes sent or error. */ modem->tx_count = 0; modem_enter_idle_state(modem); modem_send_res(modem, ResNOCARRIER); } else if (res > 0) { if (res == modem->tx_count) { modem->tx_count = 0; } else { memmove(modem->tx_pkt_ser_line, &modem->tx_pkt_ser_line[res], modem->tx_count - res); modem->tx_count -= res; } } } if (modem->connected) { uint8_t buffer[16]; int wouldblock = 0; int res = plat_netsocket_receive(modem->clientsocket, buffer, sizeof(buffer), &wouldblock); if (res > 0) { fifo8_push_all(&modem->rx_data, buffer, res); } else if (res == 0) { modem->tx_count = 0; modem_enter_idle_state(modem); modem_send_res(modem, ResNOCARRIER); } else if (!wouldblock) { modem->tx_count = 0; modem_enter_idle_state(modem); modem_send_res(modem, ResNOCARRIER); } } } modem->cmdpause++; guard_threashold = (uint32_t)(modem->reg[MREG_GUARD_TIME] * 20); if (modem->cmdpause > guard_threashold) { if (modem->plusinc == 0) { modem->plusinc = 1; } else if (modem->plusinc == 4) { modem->mode = MODEM_MODE_COMMAND; modem_send_res(modem, ResOK); modem->plusinc = 0; } } } /* 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)); const char* phonebook_file = NULL; memset(modem->mac, 0xfc, 6); modem->port = device_get_config_int("port"); modem->baudrate = device_get_config_int("baudrate"); modem->listen_port = device_get_config_int("listen_port"); modem->clientsocket = modem->serversocket = modem->waitingclientsocket = -1; 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(modem, modem->mac, modem_rx, NULL); phonebook_file = device_get_config_string("phonebook_file"); if (phonebook_file && phonebook_file[0] != 0) { modem_read_phonebook_file(modem, phonebook_file); } return modem; } void modem_close(void *priv) { modem_t* modem = (modem_t*)priv; modem->listen_port = 0; modem_reset(modem); 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", .type = CONFIG_SELECTION, .default_string = "", .default_int = 115200, .file_filter = NULL, .spinner = { 0 }, .selection = { { .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 = "listen_port", .description = "TCP/IP listening port", .type = CONFIG_SPINNER, .spinner = { .min = 0, .max = 32767 }, .default_int = 0 }, { .name = "phonebook_file", .description = "Phonebook File", .type = CONFIG_FNAME, .default_string = "", .file_filter = "Text files (*.txt)|*.txt" }, { .name = "", .description = "", .type = CONFIG_END } }; const device_t modem_device = { .name = "Standard Hayes-compliant Modem", .internal_name = "modem", .flags = DEVICE_COM, .local = 0, .init = modem_init, .close = modem_close, .reset = NULL, { .poll = NULL }, .speed_changed = modem_speed_changed, .force_redraw = NULL, .config = modem_config };