/* * mii_ssc.c * * Copyright (C) 2023 Michel Pollet * * SPDX-License-Identifier: MIT * */ /* Theory of operation: There is a thread that is started for any/all cards in the system. Cards themselves communicate via one 'command' FIFO to start/stop themselves, which add/remove them from the private list of cards kept by the thread. The cards attempts not to open the device (and start the thread) until they are actually used, so it monitors for access from the ROM area, (that will be happening if PR#x and IN#x are used), OR when the PC calls into the Cx00-CxFF area. Once running, the thread will monitor the 'tty' file descriptor for each and will deal with 2 'data' FIFOs to send and receive data from the 6502. The SSC driver itself just monitors the FIFO state and update the status register, and raise IRQs as needed. */ /* git clone https://github.com/colinleroy/a2tools.git cd a2tools/src/surl-server sudo apt-get install libcurl4-gnutls-dev libgumbo-dev libpng-dev libjq-dev libsdl-image1.2-dev make && A2_TTY=/dev/tntX ./surl-server */ #include #include #include #include #include #include #include #include "mii.h" #include "mii_bank.h" #include "mii_sw.h" #include "mii_ssc.h" #include "fifo_declare.h" #include "bsd_queue.h" #define INCBIN_STYLE INCBIN_STYLE_SNAKE #define INCBIN_PREFIX mii_ #include "incbin.h" INCBIN(ssc_rom, "roms/mii_rom_scc_3410065a.bin"); #include #include #include static const mii_ssc_setconf_t _mii_ssc_default_conf = { .baud = 9600, .bits = 8, .parity = 0, .stop = 1, .handshake = 0, .is_device = 1, .is_socket = 0, .is_pty = 0, .socket_port = 0, .device = "/dev/tnt0", }; // SW1-4 SW1 is MSB, switches are inverted ? (0=on, 1=off) static const int _mii_ssc_to_baud[16] = { [0] = B1152000, [1] = B50, [2] = B75, [3] = B110, [4] = B134, [5] = B150, [6] = B300, [7] = B600, [8] = B1200, [9] = B1800, [10] = B2400, [12] = B4800, [14] = B9600, [15] = B19200, }; static const unsigned int _mii_ssc_to_baud_rate[16] = { [0] = 1152000, [1] = 50, [2] = 75, [3] = 110, [4] = 134, [5] = 150, [6] = 300, [7] = 600, [8] = 1200, [9] = 1800, [10] = 2400, [11] = -3600, [12] = 4800, [13] = -7200, [14] = 9600, [15] = 19200, }; enum { SSC_6551_CONTROL_BAUD = 0, // see table SSC_6551_CONTROL_CLOCK = 4, // always 1 SSC_6551_CONTROL_WLEN = 5, // 0=8 bits, 1=7 bits, 2=6 bits, 3=5 bits SSC_6551_CONTROL_STOP = 7, // 0 = 1 stop bit, 1 = 2 stop bits SSC_6551_CONTROL_RESET = 0, SSC_6551_COMMAND_DTR = 0, // 0=Disable Receiver (DTR), 1=Enable SSC_6551_COMMAND_IRQ_R = 1, // 0=IRQ Enabled, 1=IRQ Disabled // 0:IRQ_TX=0 + RTS=1 // 1:IRQ_TX=1 + RTS=0 // 2:IRQ_TX=0 + RTS=0 // 3:IRQ_TX=0 + RTS=0 + BRK SSC_6551_COMMAND_IRQ_T = 2, SSC_6551_COMMAND_ECHO = 4, // 0=off, 1=on SSC_6551_COMMAND_PARITY = 5, // None, Odd, Even, Mark, Space SSC_6551_COMMAND_RESET = (1 << SSC_6551_COMMAND_IRQ_R), SSC_6551_PARITY_ERROR = 0, SSC_6551_FRAMING_ERROR = 1, SSC_6551_OVERRUN = 2, SSC_6551_RX_FULL = 3, SSC_6551_TX_EMPTY = 4, SSC_6551_DCD = 5, SSC_6551_DSR = 6, SSC_6551_IRQ = 7, SSC_6551_STATUS_RESET = (1 << SSC_6551_TX_EMPTY), }; enum { SSC_SW2_STOPBITS = 1 << 7, SSC_SW2_DATABITS = 1 << 6, SSC_SW2_IRQEN = 1 << 0, }; // SW2-1 is stop bits OFF = Two, ON = One (inverted) static const unsigned int _mii_ssc_to_stop[2] = { [0] = 0, [1] = CSTOPB, }; // SW2-2 is data bits static const unsigned int _mii_ssc_to_bits[4] = { [0] = CS8, [1] = CS7, [2] = CS6, [3] = CS5, }; static const int _mii_scc_to_bits_count[4] = { [0] = 8, [1] = 7, [2] = 6, [3] = 5, }; // SW2-3-4 is parity static const unsigned int _mii_ssc_to_parity[4] = { [0] = 0, [1] = PARODD, [2] = PARENB, [3] = PARENB|PARODD, }; enum { MII_SSC_STATE_INIT = 0, MII_SSC_STATE_START, MII_SSC_STATE_RUNNING, MII_SSC_STATE_STOP, MII_SSC_STATE_STOPPED, MII_THREAD_TERMINATE, }; struct mii_card_ssc_t; typedef struct mii_ssc_cmd_t { int cmd; union { struct mii_card_ssc_t * card; }; } mii_ssc_cmd_t; DECLARE_FIFO(mii_ssc_cmd_t, mii_ssc_cmd_fifo, 8); DEFINE_FIFO(mii_ssc_cmd_t, mii_ssc_cmd_fifo); DECLARE_FIFO(uint8_t, mii_ssc_fifo, 16); DEFINE_FIFO(uint8_t, mii_ssc_fifo); typedef struct mii_card_ssc_t { // queued when first allocated, to keep a list of all cards STAILQ_ENTRY(mii_card_ssc_t) self; // queued when started, for the thread STAILQ_ENTRY(mii_card_ssc_t) started; struct mii_slot_t * slot; struct mii_bank_t * rom; mii_t * mii; uint8_t slot_offset; mii_ssc_setconf_t conf; int state; // current state, MII_SSC_STATE_* char tty_path[128]; int tty_fd; // <= 0 is not opened yet char human_config[32]; // global counter of bytes sent/received. No functional use uint32_t total_rx, total_tx; uint8_t timer_check; uint32_t timer_delay; mii_ssc_fifo_t rx,tx; // 6551 registers uint8_t dipsw1, dipsw2, control, command, status; } mii_card_ssc_t; STAILQ_HEAD(, mii_card_ssc_t) _mii_card_ssc_slots = STAILQ_HEAD_INITIALIZER(_mii_card_ssc_slots); /* * These bits are only meant to communicate with the thread */ STAILQ_HEAD(, mii_card_ssc_t) _mii_card_ssc_started = STAILQ_HEAD_INITIALIZER(_mii_card_ssc_started); pthread_t _mii_ssc_thread_id = 0; mii_ssc_cmd_fifo_t _mii_ssc_cmd = {}; static int _mii_scc_set_conf( mii_card_ssc_t *c, const mii_ssc_setconf_t *conf, int re_open); static void* _mii_ssc_thread( void *param) { printf("%s: start\n", __func__); // ignore the signal, we use it to wake up the thread sigaction(SIGUSR1, &(struct sigaction){ .sa_handler = SIG_IGN, .sa_flags = SA_RESTART, }, NULL); do { /* * Get commands from the MII running thread. Add/remove cards * from the 'running' list, and a TERMINATE to kill the thread */ while (!mii_ssc_cmd_fifo_isempty(&_mii_ssc_cmd)) { mii_ssc_cmd_t cmd = mii_ssc_cmd_fifo_read(&_mii_ssc_cmd); switch (cmd.cmd) { case MII_SSC_STATE_START: { mii_card_ssc_t *c = cmd.card; printf("%s: start slot %d\n", __func__, c->slot->id); STAILQ_INSERT_TAIL(&_mii_card_ssc_started, c, self); c->state = MII_SSC_STATE_RUNNING; } break; case MII_SSC_STATE_STOP: { mii_card_ssc_t *c = cmd.card; printf("%s: stop slot %d\n", __func__, c->slot->id); STAILQ_REMOVE(&_mii_card_ssc_started, c, mii_card_ssc_t, self); c->state = MII_SSC_STATE_STOPPED; } break; case MII_THREAD_TERMINATE: printf("%s: terminate\n", __func__); return NULL; } } /* here we use select. This is not optimal on linux, but it is portable to other OSes -- perhaps I'll add an epoll() version later */ fd_set rfds, wfds; FD_ZERO(&rfds); FD_ZERO(&wfds); int maxfd = 0; mii_card_ssc_t *c = NULL, *safe; STAILQ_FOREACH_SAFE(c, &_mii_card_ssc_started, self, safe) { // guy might be being reconfigured, or perhaps had an error if (c->tty_fd < 0) continue; if (!mii_ssc_fifo_isempty(&c->tx)) { FD_SET(c->tty_fd, &wfds); if (c->tty_fd > maxfd) maxfd = c->tty_fd; } if (!mii_ssc_fifo_isfull(&c->rx)) { FD_SET(c->tty_fd, &rfds); if (c->tty_fd > maxfd) maxfd = c->tty_fd; } } struct timeval tv = { .tv_sec = 0, .tv_usec = 1000 }; int res = select(maxfd + 1, &rfds, &wfds, NULL, &tv); if (res < 0) { // there are OK errors, we just ignore them if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue; printf("%s ssc select: %s\n", __func__, strerror(errno)); break; } if (res == 0) // timeout continue; STAILQ_FOREACH(c, &_mii_card_ssc_started, self) { /* Here we know the read fifo isn't full, otherwise we wouldn'ty have asked for more data. See what space we have in the fifo, try reading as much as that, and push it to the FIFO */ if (FD_ISSET(c->tty_fd, &rfds)) { uint8_t buf[mii_ssc_cmd_fifo_fifo_size]; int max = mii_ssc_fifo_get_write_size(&c->rx); int res = read(c->tty_fd, buf, max); if (res < 0) { if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) break; c->tty_fd = -1; printf("%s ssc read: %s\n", __func__, strerror(errno)); break; } for (int i = 0; i < res; i++) mii_ssc_fifo_write(&c->rx, buf[i]); } /* here as well, this wouldn't be set if we hadn't got stuff to send -- see what's in the fifo, 'peek' the bytes into an aligned buffer, try to write it all, and then *actually* remove them (read) the one that were sent from the fifo */ if (FD_ISSET(c->tty_fd, &wfds)) { uint8_t buf[mii_ssc_cmd_fifo_fifo_size]; int max = mii_ssc_fifo_get_read_size(&c->tx); for (int i = 0; i < max; i++) buf[i] = mii_ssc_fifo_read_at(&c->tx, i); res = write(c->tty_fd, buf, max); if (res < 0) { if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) break; printf("%s ssc write: %s\n", __func__, strerror(errno)); break; } // flush what we've just written while (res--) mii_ssc_fifo_read(&c->tx); } } } while (1); return NULL; } static void _mii_ssc_thread_start( mii_card_ssc_t *c) { if (c->state > MII_SSC_STATE_INIT && c->state < MII_SSC_STATE_STOP) return; if (c->tty_fd < 0) { printf("%s TTY not open, skip\n", __func__); return; } c->state = MII_SSC_STATE_START; mii_ssc_cmd_t cmd = { .cmd = MII_SSC_STATE_START, .card = c }; mii_ssc_cmd_fifo_write(&_mii_ssc_cmd, cmd); // start timer that'll check out card status mii_timer_set(c->mii, c->timer_check, c->timer_delay); // start, or kick the thread awake if (!_mii_ssc_thread_id) { printf("%s: starting thread\n", __func__); pthread_create(&_mii_ssc_thread_id, NULL, _mii_ssc_thread, NULL); } else { pthread_kill(_mii_ssc_thread_id, SIGUSR1); } } /* * This is called when the CPU touches the CX00-CXFF ROM area, and we * need to install the secondary part of the ROM. * Also, if the access was from the ROM, we start the card as there's no * other way to know when the card is started -- we don't want to create * a thread fro SSC if the card is being ignored by the Apple IIe */ static bool _mii_ssc_select( struct mii_bank_t *bank, void *param, uint16_t addr, uint8_t * byte, bool write) { if (bank == NULL) // this is normal, called on dispose return false; mii_card_ssc_t *c = param; if (c->slot->aux_rom_selected) return false; uint16_t pc = c->mii->cpu.PC; printf("SSC%d SELECT auxrom PC:%04x\n", c->slot->id+1, pc); /* Supports when the ROM starts prodding into the ROM */ if (c->state != MII_SSC_STATE_RUNNING) { printf("SSC%d: start card from ROM poke? (PC $%04x)?\n", c->slot->id+1, pc); if ((pc & 0xff00) == (0xc100 + (c->slot->id << 8)) || (pc >> 12) >= 0xc) { _mii_scc_set_conf(c, &c->conf, 1); _mii_ssc_thread_start(c); } } mii_bank_write(c->rom, 0xc800, mii_ssc_rom_data, 2048); c->slot->aux_rom_selected = true; return false; } /* Called a some sort of proportional number of cycles related to the baudrate to check the FIFOs and update the status/raise IRQs. this doesn't have to be exact, it just have to be often enough to not miss any data. */ static uint64_t _mii_ssc_timer_cb( mii_t * mii, void * param ) { mii_card_ssc_t *c = param; // stop timer if (c->state != MII_SSC_STATE_RUNNING) return 0; // check the FIFOs -- not technically 'true' we raise an IRQ as soon as // theres some bytes to proceed, but we'll do it here for simplicity uint8_t rx_full = !mii_ssc_fifo_isempty(&c->rx); // what it really mean is 'there room for more data', not 'full' uint8_t tx_empty = !mii_ssc_fifo_isfull(&c->tx); uint8_t old = c->status; c->status = (c->status & ~(1 << SSC_6551_RX_FULL)) | (rx_full << SSC_6551_RX_FULL); c->status = (c->status & ~(1 << SSC_6551_TX_EMPTY)) | (tx_empty << SSC_6551_TX_EMPTY); uint8_t irq = 0;//(c->status & (1 << SSC_6551_IRQ)); uint8_t t_irqen = ((c->command >> SSC_6551_COMMAND_IRQ_T) & 3) == 1; uint8_t r_irqen = !(c->command & (1 << SSC_6551_COMMAND_IRQ_R)); if (old != c->status) printf("SSC%d New Status %08b RX:%2d TX:%2d t_irqen:%d r_irqen:%d\n", c->slot->id+1, c->status, mii_ssc_fifo_get_read_size(&c->rx), mii_ssc_fifo_get_write_size(&c->tx), t_irqen, r_irqen); // we set the IRQ flag even if the real IRQs are disabled. // rising edge triggers the IRQR if (!irq && rx_full) { // raise the IRQ if (r_irqen) { irq = 1; printf("SSC%d: IRQ RX\n", c->slot->id+1); mii->cpu_state.irq = 1; } } if (!irq && (tx_empty)) { // raise the IRQ if (t_irqen) { irq = 1; printf("SSC%d: IRQ TX\n", c->slot->id+1); mii->cpu_state.irq = 1; } } if (irq) c->status |= 1 << SSC_6551_IRQ; return c->timer_delay; } static int _mii_scc_set_conf( mii_card_ssc_t *c, const mii_ssc_setconf_t *conf, int re_open) { if (conf == NULL) conf = &_mii_ssc_default_conf; if (!re_open && strcmp(c->conf.device, conf->device) == 0 && c->conf.baud == conf->baud && c->conf.bits == conf->bits && c->conf.parity == conf->parity && c->conf.stop == conf->stop && c->conf.handshake == conf->handshake && c->conf.is_device == conf->is_device && c->conf.is_socket == conf->is_socket && c->conf.is_pty == conf->is_pty && c->conf.socket_port == conf->socket_port) return 0; if (re_open || strcmp(c->conf.device, conf->device) != 0) { if (c->tty_fd > 0) { int tty = c->tty_fd; c->tty_fd = -1; // this will wake up the thread as well close(tty); } } c->conf = *conf; strcpy(c->tty_path, conf->device); if (c->tty_fd < 0) { int new_fd = -1; if (conf->is_pty) { int master = 0; int res = openpty(&master, &new_fd, c->tty_path, NULL, NULL); if (res < 0) { printf("SSC%d openpty: %s\n", c->slot->id+1, strerror(errno)); return -1; } } else { int res = open(c->tty_path, O_RDWR | O_NOCTTY | O_NONBLOCK); if (res < 0) { printf("SSC%d open(%s): %s\n", c->slot->id+1, c->tty_path, strerror(errno)); return -1; } new_fd = res; } // set non-blocking mode int flags = fcntl(new_fd, F_GETFL, 0); fcntl(new_fd, F_SETFL, flags | O_NONBLOCK); c->tty_fd = new_fd; } if (c->tty_fd < 0) { printf("SSC%d: %s TTY not open, skip\n", c->slot->id+1, __func__); return -1; } // get current terminal settings struct termios tio; tcgetattr(c->tty_fd, &tio); // set raw mode cfmakeraw(&tio); c->human_config[0] = 0; // set speed for (int i = 0; i < 16; i++) { if (_mii_ssc_to_baud_rate[i] == conf->baud) { c->dipsw1 = 0x80 | i; cfsetospeed(&tio, _mii_ssc_to_baud[i]); cfsetispeed(&tio, _mii_ssc_to_baud[i]); sprintf(c->human_config, "Baud:%d ", conf->baud); break; } } // set 8N1 tio.c_cflag = (tio.c_cflag & ~PARENB) | _mii_ssc_to_parity[conf->parity]; tio.c_cflag = (tio.c_cflag & ~CSTOPB) | _mii_ssc_to_stop[conf->stop]; tio.c_cflag = (tio.c_cflag & ~CSIZE); tio.c_cflag |= _mii_ssc_to_bits[conf->bits]; sprintf(c->human_config + strlen(c->human_config), "%d", _mii_scc_to_bits_count[conf->bits]); static const char *parity = "noeb"; sprintf(c->human_config + strlen(c->human_config), "%c%c", parity[conf->parity], conf->stop ? '2' : '1'); // Hardware Handshake tio.c_cflag = (tio.c_cflag & ~CRTSCTS) | (conf->handshake ? CRTSCTS : 0); // set the new settings tcsetattr(c->tty_fd, TCSANOW, &tio); c->dipsw1 = 0x80 | conf->baud; c->dipsw2 = SSC_SW2_IRQEN; c->control = 0; mii_ssc_fifo_reset(&c->rx); mii_ssc_fifo_reset(&c->tx); return 0; } static int _mii_ssc_init( mii_t * mii, struct mii_slot_t *slot ) { mii_card_ssc_t *c = calloc(1, sizeof(*c)); c->slot = slot; slot->drv_priv = c; c->mii = mii; c->slot_offset = slot->id + 1 + 0xc0; uint16_t addr = 0xc100 + (slot->id * 0x100); c->rom = &mii->bank[MII_BANK_CARD_ROM]; mii_bank_write(c->rom, addr, mii_ssc_rom_data + 7*256, 256); /* * install a callback that will be called for every access to the * ROM area, we need this to re-install the secondary part of the ROM * when the card 'slot' rom is accessed. */ mii_bank_install_access_cb(c->rom, _mii_ssc_select, c, addr >> 8, addr >> 8); /* * And this is the timer that will check the status of FIFOs and update * the status of the card, and raise IRQs if needed */ char name[32]; snprintf(name, sizeof(name), "SSC %d", slot->id+1); c->timer_check = mii_timer_register(mii, _mii_ssc_timer_cb, c, 0, strdup(name)); // fastest speed we could get to? c->timer_delay = 11520; c->tty_fd = -1; STAILQ_INSERT_TAIL(&_mii_card_ssc_slots, c, self); c->dipsw1 = 0x80 | 14; // communication mode, 9600 // in case progs read that to decide to use IRQs or not c->dipsw2 = SSC_SW2_IRQEN; c->state = MII_SSC_STATE_INIT; c->status = SSC_6551_STATUS_RESET; c->command = SSC_6551_COMMAND_RESET; c->control = 0; _mii_scc_set_conf(c, &c->conf, 1); return 0; } static void _mii_ssc_dispose( mii_t * mii, struct mii_slot_t *slot ) { mii_card_ssc_t *c = slot->drv_priv; STAILQ_REMOVE(&_mii_card_ssc_slots, c, mii_card_ssc_t, self); if (c->state == MII_SSC_STATE_RUNNING) { mii_ssc_cmd_t cmd = { .cmd = MII_SSC_STATE_STOP, .card = c }; mii_ssc_cmd_fifo_write(&_mii_ssc_cmd, cmd); pthread_kill(_mii_ssc_thread_id, SIGUSR1); while (c->state == MII_SSC_STATE_RUNNING) usleep(1000); printf("SSC%d: stopped\n", c->slot->id+1); } if (STAILQ_FIRST(&_mii_card_ssc_slots) == NULL && _mii_ssc_thread_id) { printf("SSC%d: stopping thread\n", c->slot->id+1); pthread_t id = _mii_ssc_thread_id; _mii_ssc_thread_id = 0; mii_ssc_cmd_t cmd = { .cmd = MII_THREAD_TERMINATE }; mii_ssc_cmd_fifo_write(&_mii_ssc_cmd, cmd); pthread_kill(id, SIGUSR1); pthread_join(id, NULL); printf("SSC%d: thread stopped\n", c->slot->id+1); } free(c); slot->drv_priv = NULL; } static void _mii_ssc_command_set( mii_card_ssc_t *c, uint8_t byte) { mii_t * mii = c->mii; if (!(c->command & (1 << SSC_6551_COMMAND_DTR)) && (byte & (1 << SSC_6551_COMMAND_DTR))) { _mii_scc_set_conf(c, &c->conf, 1); _mii_ssc_thread_start(c); } if (c->tty_fd < 0) { printf("SSC%d: %s TTY not open, skip\n", c->slot->id+1, __func__); return; } /* This triggers the IRQ if it enabled when there is a IRQ flag on, * this make it behave more like a 'level' IRQ instead of an edge IRQ */ if ((c->command & (1 << SSC_6551_COMMAND_IRQ_R)) && !(byte & (1 << SSC_6551_COMMAND_IRQ_R))) { if (c->status & (1 << SSC_6551_IRQ)) mii->cpu_state.irq = 1; } int status; if (ioctl(c->tty_fd, TIOCMGET, &status) == -1) { printf("SSC%d: DTR/RTS: %s\n", c->slot->id+1, strerror(errno)); } int old = status; status = (status & ~TIOCM_DTR) | ((byte & (1 << SSC_6551_COMMAND_DTR)) ? TIOCM_DTR : 0); switch ((byte >> SSC_6551_COMMAND_IRQ_T) & 3) { case 0: // IRQ_TX=0 + RTS=1 status |= TIOCM_RTS; break; case 1: // IRQ_TX=1 + RTS=0 status &= ~TIOCM_RTS; break; case 2: // IRQ_TX=0 + RTS=0 status &= ~TIOCM_RTS; break; case 3: // IRQ_TX=0 + RTS=0 + BRK status &= ~TIOCM_RTS; break; } if (old != status) { printf("%s%d: $%04x DTR %d RTS %d\n", __func__, c->slot->id+1, c->mii->cpu.PC, (status & TIOCM_DTR) ? 1 : 0, (status & TIOCM_RTS) ? 1 : 0); // 0=on, 1=off if (ioctl(c->tty_fd, TIOCMSET, &status) == -1) { printf("SSC%d: DTR/RTS: %s\n", c->slot->id+1, strerror(errno)); } } c->command = byte; } static uint8_t _mii_ssc_access( mii_t * mii, struct mii_slot_t *slot, uint16_t addr, uint8_t byte, bool write) { mii_card_ssc_t *c = slot->drv_priv; uint8_t res = 0; int psw = addr & 0x0F; switch (psw) { case 0x1: // DIPSW1 if (!write) { printf("%s%d: $%04x read DIPSW1 : %02x\n", __func__, slot->id+1, mii->cpu.PC, c->dipsw1); res = c->dipsw1; /* this handle access by the ROM via PR#x and IN#x */ if (c->state == MII_SSC_STATE_INIT && (mii->cpu.PC & 0xff00) == 0xcb00) _mii_ssc_thread_start(c); } break; case 0x2: // DIPSW2 if (!write) { printf("%s%d: $%04x read DIPSW2 : %02x\n", __func__, slot->id+1, mii->cpu.PC, c->dipsw2); res = c->dipsw2; } break; case 0x8: { // TD/RD if (c->state != MII_SSC_STATE_RUNNING) break; if (write) { bool tx_empty = mii_ssc_fifo_isempty(&c->tx); // printf("%s: write %02x '%c'\n", __func__, // byte, byte <= ' ' ? '.' : byte); c->total_tx++; mii_ssc_fifo_write(&c->tx, byte); if (tx_empty) // wake thread if it's sleeping pthread_kill(_mii_ssc_thread_id, SIGUSR1); bool isfull = mii_ssc_fifo_isfull(&c->tx); if (isfull) { c->status &= ~(1 << SSC_6551_TX_EMPTY); } } else { if (mii_ssc_fifo_isempty(&c->rx)) { res = 0; } else { c->total_rx++; bool wasfull = mii_ssc_fifo_isfull(&c->rx); res = mii_ssc_fifo_read(&c->rx); bool isempty = mii_ssc_fifo_isempty(&c->rx); if (isempty) { c->status &= ~(1 << SSC_6551_RX_FULL); } else { if (wasfull) // wake thread to read more pthread_kill(_mii_ssc_thread_id, SIGUSR1); // send another irq? uint8_t r_irqen = !(c->command & (1 << SSC_6551_COMMAND_IRQ_R)); if (r_irqen) { mii->cpu_state.irq = 1; } } } } } break; case 0x9: {// STATUS if (write) { printf("SSC%d: RESET requesdt\n",c->slot->id+1); _mii_ssc_command_set(c, 0x10); break; } res = c->status; // if it was set before, clear it. c->status &= ~(1 << SSC_6551_IRQ); } break; case 0xa: {// COMMAND if (!write) { res = c->command; break; } _mii_ssc_command_set(c, byte); } break; case 0xb: { // CONTROL if (!write) { res = c->control; break; } c->control = byte; struct termios tio; tcgetattr(c->tty_fd, &tio); // Update speed int baud = _mii_ssc_to_baud[c->control & 0x0F]; cfsetospeed(&tio, baud); cfsetispeed(&tio, baud); // Update stop bits bit 7: 0 = 1 stop bit, 1 = 2 stop bits tio.c_cflag &= ~CSTOPB; tio.c_cflag |= _mii_ssc_to_stop[(c->control >> 7) & 1]; // Update data bits bit 5-6 0=8 bits, 1=7 bits, 2=6 bits, 3=5 bits tio.c_cflag &= ~CSIZE; tio.c_cflag |= _mii_ssc_to_bits[(c->control >> 6) & 3]; // parity are in c->command, bits 5-7, // 0=None, 1=Odd, 2=Even, 3=Mark, 4=Space tio.c_cflag &= ~PARENB; tio.c_cflag &= ~PARODD; tio.c_cflag |= _mii_ssc_to_parity[(c->command >> 5) & 3]; tcsetattr(c->tty_fd, TCSANOW, &tio); printf("SSC%d: set %02x baud %d stop %d bits %d parity %d\n", c->slot->id+1, byte, _mii_ssc_to_baud_rate[c->control & 0x0F], (c->control >> 7) & 1 ? 2 : 1, _mii_scc_to_bits_count[(c->control >> 5) & 3], (c->command >> 5) & 3); } break; default: // printf("%s PC:%04x addr %04x %02x wr:%d\n", __func__, // mii->cpu.PC, addr, byte, write); break; } return res; } static int _mii_ssc_command( mii_t * mii, struct mii_slot_t *slot, uint32_t cmd, void * param) { // mii_card_ssc_t *c = slot->drv_priv; int res = -1; switch (cmd) { case MII_SLOT_SSC_SET_TTY: { const mii_ssc_setconf_t * conf = param; mii_card_ssc_t *c = slot->drv_priv; res = _mii_scc_set_conf(c, conf, 0); printf("SSC%d: set tty %s: %s\n", slot->id+1, conf->device, c->human_config); } break; } return res; } static mii_slot_drv_t _driver = { .name = "ssc", .desc = "Super Serial card", .init = _mii_ssc_init, .dispose = _mii_ssc_dispose, .access = _mii_ssc_access, .command = _mii_ssc_command, }; MI_DRIVER_REGISTER(_driver); #include "mish.h" static void _mii_mish_ssc( void * param, int argc, const char * argv[]) { if (!argv[1] || !strcmp(argv[1], "status")) { mii_card_ssc_t *c; printf("SSC: cards:\n"); STAILQ_FOREACH(c, &_mii_card_ssc_slots, self) { printf("SSC %d: %s FD: %2d path:%s %s\n", c->slot->id+1, c->state == MII_SSC_STATE_RUNNING ? "running" : "stopped", c->tty_fd, c->tty_path, c->human_config); // print FIFO status, fd status, registers etc printf(" RX: %2d/%2d TX: %2d/%2d -- total rx:%6d tx:%6d\n", mii_ssc_fifo_get_read_size(&c->rx), mii_ssc_fifo_get_write_size(&c->rx), mii_ssc_fifo_get_read_size(&c->tx), mii_ssc_fifo_get_write_size(&c->tx), c->total_rx, c->total_tx); printf(" DIPSW1: %08b DIPSW2: %08b\n", c->dipsw1, c->dipsw2); printf(" CONTROL: %08b COMMAND: %08b STATUS: %08b\n", c->control, c->command, c->status); } return; } } MISH_CMD_NAMES(_ssc, "ssc"); MISH_CMD_HELP(_ssc, "ssc: Super Serial internals", " : dump status" ); MII_MISH(_ssc, _mii_mish_ssc);