From 9488078c5ab8c3c76878809fcb49698f7a08212b Mon Sep 17 00:00:00 2001 From: Cacodemon345 Date: Fri, 8 Mar 2024 16:45:17 +0600 Subject: [PATCH] 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 +};