/* * 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. * * Definitions for platform specific serial to host passthrough * * * Authors: Andreas J. Reichel , * Jasmine Iwanek * * Copyright 2021 Andreas J. Reichel. * Copyright 2021-2022 Jasmine Iwanek. */ #ifndef __APPLE__ # define _XOPEN_SOURCE 500 # define _DEFAULT_SOURCE 1 # define _BSD_SOURCE 1 #endif #include #include #include #include #include #include #include #include #include <86box/86box.h> #include <86box/log.h> #include <86box/plat.h> #include <86box/device.h> #include <86box/serial_passthrough.h> #include <86box/plat_serial_passthrough.h> #include #include #define LOG_PREFIX "serial_passthrough: " int plat_serpt_read(void *p, uint8_t *data) { serial_passthrough_t *dev = (serial_passthrough_t *) p; int res; struct timeval tv; fd_set rdfds; switch (dev->mode) { case SERPT_MODE_VCON: case SERPT_MODE_HOSTSER: FD_ZERO(&rdfds); FD_SET(dev->master_fd, &rdfds); tv.tv_sec = 0; tv.tv_usec = 0; res = select(dev->master_fd + 1, &rdfds, NULL, NULL, &tv); if (res <= 0 || !FD_ISSET(dev->master_fd, &rdfds)) { return 0; } if (read(dev->master_fd, data, 1) > 0) { return 1; } break; default: break; } return 0; } void plat_serpt_close(void *p) { serial_passthrough_t *dev = (serial_passthrough_t *) p; if (dev->mode == SERPT_MODE_HOSTSER) { tcsetattr(dev->master_fd, TCSANOW, (struct termios *) dev->backend_priv); free(dev->backend_priv); } close(dev->master_fd); } static void plat_serpt_write_vcon(serial_passthrough_t *dev, uint8_t data) { #if 0 fd_set wrfds; int res; #endif /* We cannot use select here, this would block the hypervisor! */ #if 0 FD_ZERO(&wrfds); FD_SET(ctx->master_fd, &wrfds); res = select(ctx->master_fd + 1, NULL, &wrfds, NULL, NULL); if (res <= 0) { return; } #endif /* just write it out */ if (dev->mode == SERPT_MODE_HOSTSER) { int res = 0; do { res = write(dev->master_fd, &data, 1); } while (res == 0 || (res == -1 && (errno == EAGAIN || res == EWOULDBLOCK))); } else write(dev->master_fd, &data, 1); } void plat_serpt_set_params(void *p) { serial_passthrough_t *dev = (serial_passthrough_t *) p; if (dev->mode == SERPT_MODE_HOSTSER) { struct termios term_attr; tcgetattr(dev->master_fd, &term_attr); #define BAUDRATE_RANGE(baud_rate, min, max, val) \ if (baud_rate >= min && baud_rate < max) { \ cfsetispeed(&term_attr, val); \ cfsetospeed(&term_attr, val); \ } BAUDRATE_RANGE(dev->baudrate, 50, 75, B50); BAUDRATE_RANGE(dev->baudrate, 75, 110, B75); BAUDRATE_RANGE(dev->baudrate, 110, 134, B110); BAUDRATE_RANGE(dev->baudrate, 134, 150, B134); BAUDRATE_RANGE(dev->baudrate, 150, 200, B150); BAUDRATE_RANGE(dev->baudrate, 200, 300, B200); BAUDRATE_RANGE(dev->baudrate, 300, 600, B300); BAUDRATE_RANGE(dev->baudrate, 600, 1200, B600); BAUDRATE_RANGE(dev->baudrate, 1200, 1800, B1200); BAUDRATE_RANGE(dev->baudrate, 1800, 2400, B1800); BAUDRATE_RANGE(dev->baudrate, 2400, 4800, B2400); BAUDRATE_RANGE(dev->baudrate, 4800, 9600, B4800); BAUDRATE_RANGE(dev->baudrate, 9600, 19200, B9600); BAUDRATE_RANGE(dev->baudrate, 19200, 38400, B19200); BAUDRATE_RANGE(dev->baudrate, 38400, 57600, B38400); BAUDRATE_RANGE(dev->baudrate, 57600, 115200, B57600); BAUDRATE_RANGE(dev->baudrate, 115200, 0xFFFFFFFF, B115200); term_attr.c_cflag &= CSIZE; switch (dev->data_bits) { case 8: default: term_attr.c_cflag |= CS8; break; case 7: term_attr.c_cflag |= CS7; break; case 6: term_attr.c_cflag |= CS6; break; case 5: term_attr.c_cflag |= CS5; break; } term_attr.c_cflag &= CSTOPB; if (dev->serial->lcr & 0x04) term_attr.c_cflag |= CSTOPB; #ifdef __APPLE__ term_attr.c_cflag &= PARENB | PARODD; #else term_attr.c_cflag &= PARENB | PARODD | CMSPAR; #endif if (dev->serial->lcr & 0x08) { term_attr.c_cflag |= PARENB; if (!(dev->serial->lcr & 0x10)) term_attr.c_cflag |= PARODD; #ifndef __APPLE__ if ((dev->serial->lcr & 0x20)) term_attr.c_cflag |= CMSPAR; #endif } tcsetattr(dev->master_fd, TCSANOW, &term_attr); #undef BAUDRATE_RANGE } } void plat_serpt_write(void *p, uint8_t data) { serial_passthrough_t *dev = (serial_passthrough_t *) p; switch (dev->mode) { case SERPT_MODE_VCON: case SERPT_MODE_HOSTSER: plat_serpt_write_vcon(dev, data); break; default: break; } } static int open_pseudo_terminal(serial_passthrough_t *dev) { int master_fd = open("/dev/ptmx", O_RDWR | O_NONBLOCK); char *ptname; struct termios term_attr_raw; if (!master_fd) { return 0; } /* get name of slave device */ if (!(ptname = ptsname(master_fd))) { pclog(LOG_PREFIX "could not get name of slave pseudo terminal"); close(master_fd); return 0; } memset(dev->slave_pt, 0, sizeof(dev->slave_pt)); strncpy(dev->slave_pt, ptname, sizeof(dev->slave_pt) - 1); fprintf(stderr, LOG_PREFIX "Slave side is %s\n", dev->slave_pt); if (grantpt(master_fd)) { pclog(LOG_PREFIX "error in grantpt()\n"); close(master_fd); return 0; } if (unlockpt(master_fd)) { pclog(LOG_PREFIX "error in unlockpt()\n"); close(master_fd); return 0; } tcgetattr(master_fd, &term_attr_raw); cfmakeraw(&term_attr_raw); tcsetattr(master_fd, TCSANOW, &term_attr_raw); dev->master_fd = master_fd; return master_fd; } static int open_host_serial_port(serial_passthrough_t *dev) { struct termios *term_attr = NULL; struct termios term_attr_raw = {}; int fd = open(dev->host_serial_path, O_RDWR | O_NOCTTY | O_NONBLOCK); if (fd == -1) { return 0; } if (!isatty(fd)) { return 0; } term_attr = calloc(1, sizeof(struct termios)); if (!term_attr) { close(fd); return 0; } if (tcgetattr(fd, term_attr) == -1) { free(term_attr); close(fd); return 0; } term_attr_raw = *term_attr; /* "Raw" mode. */ cfmakeraw(&term_attr_raw); term_attr_raw.c_cflag &= CSIZE; switch (dev->data_bits) { case 8: default: term_attr_raw.c_cflag |= CS8; break; case 7: term_attr_raw.c_cflag |= CS7; break; case 6: term_attr_raw.c_cflag |= CS6; break; case 5: term_attr_raw.c_cflag |= CS5; break; } tcsetattr(fd, TCSANOW, &term_attr_raw); dev->backend_priv = term_attr; dev->master_fd = fd; pclog(LOG_PREFIX "Opened host TTY/serial port %s\n", dev->host_serial_path); return 1; } int plat_serpt_open_device(void *p) { serial_passthrough_t *dev = (serial_passthrough_t *) p; switch (dev->mode) { case SERPT_MODE_VCON: if (!open_pseudo_terminal(dev)) { return 1; } break; case SERPT_MODE_HOSTSER: if (!open_host_serial_port(dev)) { return 1; } break; default: break; } return 0; }