Work-In-Progress modem emulation

This commit is contained in:
Cacodemon345
2024-03-08 16:45:17 +06:00
parent 3e6b4aa9e1
commit 9488078c5a
5 changed files with 759 additions and 4 deletions

View File

@@ -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.
*/

View File

@@ -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)
{

View File

@@ -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;

View File

@@ -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)

706
src/network/net_modem.c Normal file
View File

@@ -0,0 +1,706 @@
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <wchar.h>
#include <stdbool.h>
#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
};