diff --git a/CMakeLists.txt b/CMakeLists.txt index 683d63a..6e3424f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ add_executable(ceda src/int.c src/keyboard.c src/main.c + src/serial.c src/sio2.c src/speaker.c src/time.c diff --git a/src/ceda.c b/src/ceda.c index 55b1974..f44b5df 100644 --- a/src/ceda.c +++ b/src/ceda.c @@ -11,6 +11,7 @@ #include "limits.h" #include "macro.h" #include "module.h" +#include "serial.h" #include "sio2.h" #include "speaker.h" #include "upd8255.h" @@ -29,10 +30,11 @@ static CEDAModule mod_video; static CEDAModule mod_speaker; static CEDAModule mod_sio2; static CEDAModule mod_int; +static CEDAModule mod_serial; static CEDAModule *modules[] = { - &mod_cli, &mod_gui, &mod_bus, &mod_cpu, - &mod_video, &mod_speaker, &mod_int, &mod_sio2, + &mod_cli, &mod_gui, &mod_bus, &mod_cpu, &mod_video, + &mod_speaker, &mod_int, &mod_serial, &mod_sio2, }; void ceda_init(void) { @@ -48,6 +50,7 @@ void ceda_init(void) { bus_init(&mod_bus); cpu_init(&mod_cpu); int_init(&mod_int); + serial_init(&mod_serial); sio2_init(&mod_sio2); } diff --git a/src/cli.c b/src/cli.c index e725506..5dd36a4 100644 --- a/src/cli.c +++ b/src/cli.c @@ -8,6 +8,7 @@ #include "floppy.h" #include "int.h" #include "macro.h" +#include "serial.h" #include "time.h" #include "tokenizer.h" @@ -756,6 +757,34 @@ static ceda_string_t *cli_out(const char *arg) { return NULL; } +static ceda_string_t *cli_serial(const char *arg) { + char word[LINE_BUFFER_SIZE]; + ceda_string_t *msg = ceda_string_new(0); + + // skip argv[0] + arg = tokenizer_next_word(word, arg, LINE_BUFFER_SIZE); + + // extract command + arg = tokenizer_next_word(word, arg, LINE_BUFFER_SIZE); + + if (arg == NULL) { + ceda_string_cpy(msg, USER_BAD_ARG_STR "missing command\n"); + return msg; + } + + if (strcmp(word, "open") == 0) { + serial_open(0); + } else if (strcmp(word, "close") == 0) { + serial_close(); + } else { + ceda_string_cpy(msg, USER_BAD_ARG_STR "expected open or close\n"); + return msg; + } + + ceda_string_delete(msg); + return NULL; +} + /* A cli_command_handler_t is a command line handler. It takes a pointer to the line buffer. @@ -793,6 +822,7 @@ static const cli_command cli_commands[] = { {"mount", "load floppy image in from specified drive (default is 0)", cli_mount}, {"umount", "unload floppy from specified drive (default is 0)", cli_umount}, + {"serial", "open tcp socket to emulate serial port", cli_serial}, {"load", "load binary from file", cli_load}, {"run", "load binary from file and run", cli_run}, {"save", "save memory dump to file", cli_save}, diff --git a/src/serial.c b/src/serial.c new file mode 100644 index 0000000..e2f8ce7 --- /dev/null +++ b/src/serial.c @@ -0,0 +1,206 @@ +#include "serial.h" + +#include "fifo.h" +#include "sio2.h" +#include "time.h" + +#include +#include +#include +#include +#include +#include +#include + +#define LOG_LEVEL LOG_LVL_INFO +#include "log.h" + +#define SERIAL_TCP_PORT (0xCEDB) +#define SERIAL_NETWORK_BUFFER_SIZE 64U + +DECLARE_FIFO_TYPE(char, SerialFifo, 64); +static int sockfd = -1; +static int connfd = -1; +static SerialFifo tx_fifo; +static SerialFifo rx_fifo; + +static bool serial_getChar(uint8_t *c) { + if (FIFO_ISEMPTY(&rx_fifo)) + return false; + + *c = (uint8_t)FIFO_POP(&rx_fifo); + return true; +} + +static bool serial_putChar(uint8_t c) { + if (FIFO_ISFULL(&tx_fifo)) + return false; + + LOG_DEBUG("serial: transmitting: %02x (%c)\n", (unsigned int)c, + isprint(c) ? c : ' '); + + FIFO_PUSH(&tx_fifo, (char)c); + return true; +} + +static void serial_poll(void) { + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 0; + + if (connfd == -1) { + fd_set accept_set; + FD_ZERO(&accept_set); + FD_SET(sockfd, &accept_set); + int ret = select(sockfd + 1, &accept_set, NULL, NULL, &timeout); + if (ret == -1) { + LOG_ERR( + "serial: error during select while accepting new client: %s\n", + strerror(errno)); + return; + } + if (ret == 0) // timeout + return; + + connfd = accept(sockfd, NULL, NULL); + if (connfd != -1) { + LOG_INFO("serial: accept client\n"); + } + } else { + int ret = -1; + fd_set read_set; + fd_set write_set; + FD_ZERO(&read_set); + FD_ZERO(&write_set); + FD_SET(connfd, &read_set); + FD_SET(connfd, &write_set); + + ret = select(connfd + 1, &read_set, &write_set, NULL, &timeout); + if (ret == -1) { + LOG_ERR("serial: select error while reading from client: %s\n", + strerror(errno)); + close(connfd); + connfd = -1; + return; + } + if (ret == 0) // timeout + return; + + // check file descriptors ready for read + if (FD_ISSET(connfd, &read_set)) { + char buffer[SERIAL_NETWORK_BUFFER_SIZE]; + const size_t to_receive = MIN((size_t)SERIAL_NETWORK_BUFFER_SIZE, + (size_t)FIFO_FREE(&rx_fifo)); + if (to_receive > 0) { + ssize_t ret = recv(connfd, buffer, to_receive, 0); + if (ret == -1) { + LOG_ERR( + "serial: recv error while reading from client: %s\n", + strerror(errno)); + LOG_ERR("serial: connection reset\n"); + close(connfd); + connfd = -1; + return; + } + if (ret == 0) { + // client disconnection + close(connfd); + connfd = -1; + LOG_INFO("serial: client disconnected\n"); + return; + } + // data available + for (ssize_t i = 0; i < ret && !FIFO_ISFULL(&rx_fifo); ++i) + FIFO_PUSH(&rx_fifo, buffer[i]); + } + } + + // check file descriptors ready for write + if (FD_ISSET(connfd, &write_set)) { + char buffer[SERIAL_NETWORK_BUFFER_SIZE]; + size_t n = 0; + while (n < SERIAL_NETWORK_BUFFER_SIZE && !FIFO_ISEMPTY(&tx_fifo)) + buffer[n++] = FIFO_POP(&tx_fifo); + ssize_t ret = send(connfd, buffer, n, 0); + + if (ret == -1) { + LOG_ERR("serial: send error while writing to client: %s\n", + strerror(errno)); + LOG_ERR("serial: connection reset\n"); + close(connfd); + connfd = -1; + return; + } + } + } +} + +bool serial_open(uint16_t port) { + if (sockfd >= 0) { + LOG_INFO("serial: port already open\n"); + return false; + } + + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd == -1) { + LOG_ERR("serial: unable to socket(): %s\n", strerror(errno)); + return false; + } + + if (port == 0) + port = SERIAL_TCP_PORT; + + struct sockaddr_in server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = htonl(INADDR_ANY); + server_addr.sin_port = htons(port); + + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &(int){true}, + sizeof(int)) != 0) { + LOG_ERR("serial: unable to setsockopt(): %s\n", strerror(errno)); + return false; + } + + if (bind(sockfd, (const struct sockaddr *)&server_addr, + sizeof(server_addr)) != 0) { + LOG_ERR("serial: unable to bind(): %s\n", strerror(errno)); + return false; + } + + if (listen(sockfd, 1) != 0) { + LOG_ERR("serial: unable to listen(): %s\n", strerror(errno)); + return false; + } + + FIFO_INIT(&tx_fifo); + FIFO_INIT(&rx_fifo); + + sio2_attachPeripheral(SIO_CHANNEL_A, serial_getChar, serial_putChar); + + LOG_INFO("serial: open ok\n"); + return true; +} + +void serial_close(void) { + sio2_detachPeripheral(SIO_CHANNEL_A); + + if (connfd != -1) + close(connfd); + + if (sockfd != -1) + close(sockfd); + + LOG_INFO("serial: close ok\n"); +} + +static void serial_cleanup(void) { + serial_close(); +} + +void serial_init(CEDAModule *mod) { + memset(mod, 0, sizeof(*mod)); + mod->init = serial_init; + mod->poll = serial_poll; + mod->cleanup = serial_cleanup; +} diff --git a/src/serial.h b/src/serial.h new file mode 100644 index 0000000..51130d5 --- /dev/null +++ b/src/serial.h @@ -0,0 +1,14 @@ +#ifndef CEDA_SERIAL_PORT_H +#define CEDA_SERIAL_PORT_H + +#include "module.h" + +#include +#include + +void serial_init(CEDAModule *mod); + +bool serial_open(uint16_t port); +void serial_close(void); + +#endif // CEDA_SERIAL_PORT_H diff --git a/src/sio2.c b/src/sio2.c index 02b2809..ecbb399 100644 --- a/src/sio2.c +++ b/src/sio2.c @@ -1,7 +1,6 @@ #include "sio2.h" #include -#include #include #include "bus.h" @@ -13,9 +12,6 @@ #define LOG_LEVEL LOG_LVL_DEBUG #include "log.h" -typedef bool (*sio_channel_try_read_t)(uint8_t *c); -typedef bool (*sio_channel_try_write_t)(uint8_t c); - DECLARE_FIFO_TYPE(uint8_t, SIOFIFO, (3 + 1)); typedef struct SIOChannel { @@ -57,9 +53,7 @@ typedef struct SIOChannel { // Read Register 2 // Only available for Channel B, holds the interrupt vector octet. -#define CHANNEL_A (0) -#define CHANNEL_B (1) -static SIOChannel channels[2]; +static SIOChannel channels[SIO_CHANNEL_CNT]; // vector byte to pass back to Z80 when an interrupt must be generated static uint8_t sio_interrupt_vector = 0; @@ -77,6 +71,7 @@ static void sio_channel_reinit(SIOChannel *channel) { memset(&channel->read_regs, 0, sizeof(channel->read_regs)); FIFO_INIT(&channel->rx_fifo); FIFO_INIT(&channel->tx_fifo); + channel->read_regs[0] |= (1 << TX_BUFFER_EMPTY_BIT); channel->rx_enabled = false; channel->tx_enabled = false; channel->rx_int_enabled = false; @@ -127,15 +122,15 @@ static void sio_channel_write_data(SIOChannel *channel, uint8_t value) { FIFO_PUSH(&channel->tx_fifo, value); - if (FIFO_ISFULL(&channel->tx_fifo)) { - // TODO(giomba): change shift register free status bit + if (!FIFO_ISFULL(&channel->tx_fifo)) { + channel->read_regs[0] |= (1 << TX_BUFFER_EMPTY_BIT); } } static uint8_t sio_channel_read_control(SIOChannel *channel) { #if 0 LOG_DEBUG("read control: channel = %c, reg_index = %d\n", - (channel == &channels[CHANNEL_A]) ? 'A' : 'B', + (channel == &channels[SIO_CHANNEL_A]) ? 'A' : 'B', channel->reg_index); #endif @@ -203,7 +198,7 @@ static void write_register_1(SIOChannel *channel, uint8_t value) { case 0: // RX interrupt disable LOG_DEBUG("sio2: disable interrupts channel %c\n", - (channel == &channels[CHANNEL_A]) ? 'A' : 'B'); + (channel == &channels[SIO_CHANNEL_A]) ? 'A' : 'B'); channel->rx_int_enabled = false; break; case 1: @@ -221,7 +216,7 @@ static void write_register_1(SIOChannel *channel, uint8_t value) { // RX interrupt on all received characters. // (parity does not affect vector) LOG_DEBUG("sio2: enable interrupts channel %c\n", - (channel == &channels[CHANNEL_A]) ? 'A' : 'B'); + (channel == &channels[SIO_CHANNEL_A]) ? 'A' : 'B'); channel->rx_int_enabled = true; break; } @@ -298,18 +293,16 @@ uint8_t sio2_in(ceda_ioaddr_t address) { #endif if (address == SIO2_CHA_DATA_REG) { - // TODO(giomba): read external RS232 - return sio_channel_read_data(&channels[CHANNEL_A]); + return sio_channel_read_data(&channels[SIO_CHANNEL_A]); } if (address == SIO2_CHA_CONTROL_REG) { - return sio_channel_read_control(&channels[CHANNEL_A]); + return sio_channel_read_control(&channels[SIO_CHANNEL_A]); } if (address == SIO2_CHB_DATA_REG) { - // TODO(giomba): read keyboard input - return sio_channel_read_data(&channels[CHANNEL_B]); + return sio_channel_read_data(&channels[SIO_CHANNEL_B]); } if (address == SIO2_CHB_CONTROL_REG) { - return sio_channel_read_control(&channels[CHANNEL_B]); + return sio_channel_read_control(&channels[SIO_CHANNEL_B]); } assert(0); @@ -322,15 +315,14 @@ void sio2_out(ceda_ioaddr_t address, uint8_t value) { LOG_DEBUG("sio2 out: address = %02x, value = %02x\n", address, value); if (address == SIO2_CHA_DATA_REG) { - // TODO(giomba): write external RS232 - sio_channel_write_data(&channels[CHANNEL_A], value); + sio_channel_write_data(&channels[SIO_CHANNEL_A], value); } else if (address == SIO2_CHA_CONTROL_REG) { - sio_channel_write_control(&channels[CHANNEL_A], value); + sio_channel_write_control(&channels[SIO_CHANNEL_A], value); } else if (address == SIO2_CHB_DATA_REG) { // TODO(giomba): write to keyboard/auxiliary serial - sio_channel_write_data(&channels[CHANNEL_B], value); + sio_channel_write_data(&channels[SIO_CHANNEL_B], value); } else if (address == SIO2_CHB_CONTROL_REG) { - sio_channel_write_control(&channels[CHANNEL_B], value); + sio_channel_write_control(&channels[SIO_CHANNEL_B], value); } else { assert(0); } @@ -372,7 +364,8 @@ static void sio2_poll(void) { if (!channel->rx_enabled) continue; - LOG_DEBUG("sio2: channel %zu: received char: %02x\n", i, c); + LOG_DEBUG("sio2: channel %zu: received char: %02x (%c)\n", i, c, + isprint(c) ? c : ' '); // put char in RX fifo FIFO_PUSH(&channel->rx_fifo, c); @@ -418,6 +411,20 @@ static void sio2_poll(void) { } } +void sio2_attachPeripheral(sio_channel_idx_t channel, + sio_channel_try_read_t getc, + sio_channel_try_write_t putc) { + assert(channel < SIO_CHANNEL_CNT); + channels[channel].getc = getc; + channels[channel].putc = putc; +} + +void sio2_detachPeripheral(sio_channel_idx_t channel) { + assert(channel < SIO_CHANNEL_CNT); + channels[channel].getc = NULL; + channels[channel].putc = NULL; +} + void sio2_init(CEDAModule *mod) { mod->init = sio2_init; mod->start = sio2_start; @@ -429,5 +436,5 @@ void sio2_init(CEDAModule *mod) { sio_channel_init(&channels[i]); // attach keyboard to channel B - channels[CHANNEL_B].getc = keyboard_getChar; + channels[SIO_CHANNEL_B].getc = keyboard_getChar; } diff --git a/src/sio2.h b/src/sio2.h index 2d9ccdc..4d3a660 100644 --- a/src/sio2.h +++ b/src/sio2.h @@ -4,10 +4,29 @@ #include "module.h" #include "type.h" +#include +#include + #include +typedef enum sio_channel_idx_t { + SIO_CHANNEL_A = 0, + SIO_CHANNEL_B = 1, + + SIO_CHANNEL_CNT, +} sio_channel_idx_t; + +typedef bool (*sio_channel_try_read_t)(uint8_t *c); +typedef bool (*sio_channel_try_write_t)(uint8_t c); + void sio2_init(CEDAModule *mod); uint8_t sio2_in(ceda_ioaddr_t address); void sio2_out(ceda_ioaddr_t address, uint8_t value); +void sio2_attachPeripheral(sio_channel_idx_t channel, + sio_channel_try_read_t getc, + sio_channel_try_write_t putc); + +void sio2_detachPeripheral(sio_channel_idx_t channel); + #endif // CEDA_SIO2_H