diff options
-rw-r--r-- | CHANGELOG | 1 | ||||
-rw-r--r-- | TODO | 12 | ||||
-rw-r--r-- | sercp.c | 310 | ||||
-rw-r--r-- | sercp.rc | 10 |
4 files changed, 271 insertions, 62 deletions
@@ -1,3 +1,4 @@ +2024-01-11 v0.4.0 use new ACK messages instead hw-flow control 2020-05-01 v0.3.5 use gnu99 instead of gnu11, add O_NDELAY to serial port initialization 2020-04-08 v0.3.4 removed hw flow control for mac and bsd 2020-04-06 v0.3.3 fixed progressbar for files smaller than 256 bytes @@ -1,3 +1,13 @@ +2024-02-05 + + add -o switch to use old "protocol" wihout ack messages + - fix buffer overflow when receiving fileinfo @115200Bd and other computer send it @38400Bd + +2024-01-11 + + check Ack messages properly + +2023-07-29 + + add support for ack of messages + 2020-04-05 + for short files < 256 bytes does not work the progressbar - underflow counter - listing of enumerated serial ports - linux, win, mac @@ -25,11 +35,11 @@ VÝHODY * funguje :-) * nejrychlejší způsob jak dostat do ZX malý soubor * nepotřebuje PC - přenos mezi dvěma Spectry ++ nastavuje datum a čas souboru - esxdosu má api utime() NEVÝHODY * nemá LFN (esxdos) - ale sercp je na to připraven :-) -* nenastavuje datum a čas souboru - esxdosu chybí api * nejde obnovit přenos * zatím není na ZX psáno portabilně - na jiné diskové systémy @@ -18,6 +18,10 @@ /* * IFDEFS inspired here: * https://iq.opengenus.org/detect-operating-system-in-c/ + * + * vmin, vtime insipired here: + * // http://unixwiz.net/techtips/termios-vmin-vtime.html + * */ #ifdef _WIN32 @@ -64,12 +68,12 @@ #define FALSE 0 #define true 1 #define TRUE 1 -const char* _version = "v0.3.5"; +const char* _version = "v0.8.0"; // SERIAL FILE *tapout_fd = NULL; int is_outfile = 0; int baud_rate = 0; -int wait_ms = 800; +int wait_ms = 800; char *path; char sercp_file[FILENAME_MAX]; unsigned char buff[32768]; @@ -84,6 +88,8 @@ long pos = 0; int scp = 0; int is_scp_read = 0; int is_continue = 1; +int is_flow_control_hw = 0; // 1 - set flow control to NONE +int is_old_protocol = 0; // 1 - set old protocol without AKC messages // prototypes @@ -101,7 +107,7 @@ char SERIALDEVICE[128] = { static HANDLE serial_fd; #else int serial_fd; - struct sigaction saterm; /* definition of signal action */ + // struct sigaction saterm; /* definition of signal action */ struct pollfd spolfd_serial[1]; // pole descriptoru pro poll #endif @@ -127,20 +133,29 @@ typedef struct { FILEINFO fileinfo; void usage(void) { - printf ("sercp %s (c)2018-2020 Pavel Vymetalek <pavel@vym.cz>\n", _version); + printf ("sercp %s (c)2018-2024 Pavel Vymetalek <pavel@vym.cz>\n", _version); printf ("serial copy for transfering file to/from ZX Spectrum 128 AY's RS232\n"); - printf ("Uses 1109bytes of fileinfo - blocks sums, filename, etc.\n"); + // printf ("Use 1109-bytes of fileinfo - blocks sums, filename, etc.\n"); + printf ("Confirmation of communication using ack messages - no HW flow control\n"); + printf ("Still compatible with old .sercp - with -l switch\n"); printf ("More info at https://vym.cz/sercp/\n"); - printf ("Usage:\nsercp [-v] [-h] -d /dev/serial [-b baud_rate] [-w time] [-r] <filename>\n"); +#ifdef _WIN32 + printf ("Usage:\nsercp.exe -d com1 [-t] [-r] <filename>\n"); +#else + printf ("Usage:\nsercp -d /dev/serial [-t] [-r] <filename>\n"); +#endif printf ("\t-v, --version\tShow version info\n"); printf ("\t-h, --help\tShow this text\n"); #ifdef _WIN32 printf ("\t-d, --device\tSerial com port\n"); #else - printf ("\t-d, --device\tSerial communication device\n"); + printf ("\t-d, --device\tSerial communication device. Default /dev/ttyUSB0\n"); #endif - printf ("\t-b, --baud\tSet the communication speed. Default 38400Bd\n"); - printf ("\t-w, --wait\tWaiting in milliseconds between transmitted blocks.\n\t\t\tDefault is -w 800 ms\n"); + printf ("\t-b, --baud\tSet the communication baud rate. Default 38400Bd\n"); + printf ("\t-t, --turbo\tSet the baud rate 115200Bd\n"); + printf ("\t-l, --hwflow\tSet the flow control to CTS/RTS. Default is NONE.\n\t\t\tThis is useful for old .sercp with HW flow control\n"); + printf ("\t-o, --oldprot\tUse the old protocol without AKC messages.\n\t\t\tThis is useful for old .sercp with missing HW flow control\n"); + printf ("\t-w, --wait\tWaiting in milliseconds between transmitted blocks.\n\t\t\tIn case of improperly functioning hw flow control\n"); printf ("\t-r, --read\tRead file from serial port\n"); } @@ -155,14 +170,17 @@ void TestArgs (int argc, char *argv[]) {"device", required_argument, NULL, 'd'}, {"baud", required_argument, NULL, 'b'}, {"wait", required_argument, NULL, 'w'}, + {"hwflow", no_argument, NULL, 'l'}, + {"oldprot", no_argument, NULL, 'o'}, {"read", no_argument, NULL, 'r'}, + {"turbo", no_argument, NULL, 't'}, {"version", no_argument, NULL, 'v'}, {"help", no_argument, NULL, 'h'}, {0, 0, 0, 0} }; - c = getopt_long (argc, argv, "d:b:w:rvh", long_options, &option_index); + c = getopt_long (argc, argv, "d:b:w:lortvh", long_options, &option_index); if (c == -1) { - // konec parametru + // end of arguments break; } switch (c) { @@ -172,17 +190,25 @@ void TestArgs (int argc, char *argv[]) #else sprintf(SERIALDEVICE, "%s", optarg); #endif -// printf ("Serial port: %s\n", SERIALDEVICE); break; case 'b': baud_rate = atoi(optarg); break; + case 't': + baud_rate = 115200; + break; case 'w': wait_ms = atoi(optarg); break; case 'r': is_scp_read = 1; break; + case 'l': + is_flow_control_hw = 1; + break; + case 'o': + is_old_protocol = 1; + break; case 'v': printf ("%s\n", _version); exit(1); @@ -204,15 +230,27 @@ void TestArgs (int argc, char *argv[]) //***************************************************************************** +// TODO read bytes from serial port for 200ms void FlushSerialPort() { -if (serial_fd) { - sleep_ms(20); + ssize_t len = 0; + if (serial_fd) { + sleep_ms(20); #ifdef _WIN32 + unsigned long ulNumBytes = 0; PurgeComm(serial_fd, PURGE_RXABORT| PURGE_TXABORT | PURGE_RXCLEAR | PURGE_TXCLEAR); + do { + sleep_ms(100); + ReadFile(serial_fd, p_buff, 100, &ulNumBytes, NULL); + len = (size_t) ulNumBytes; + } while (len > 1); #else tcflush(serial_fd, TCIOFLUSH); - #endif + do { + sleep_ms(100); + len = read (serial_fd, p_buff, 100); + } while (len > 1); +#endif } } @@ -233,22 +271,6 @@ void CloseSerialPort() { //***************************************************************************** // -// uSleep win32 implementation - -void uSleep(int waitTime) { - #ifdef _WIN32 - __int64 time1 = 0, time2 = 0, freq = 0; - - QueryPerformanceCounter((LARGE_INTEGER *) &time1); - QueryPerformanceFrequency((LARGE_INTEGER *)&freq); - - do { - QueryPerformanceCounter((LARGE_INTEGER *) &time2); - } while ((time2 - time1) < waitTime); - #else - usleep(waitTime); - #endif -} void sleep_ms(int milliseconds) { #ifdef WIN32 @@ -290,7 +312,7 @@ void DoProgress(size_t pos, size_t max, unsigned char csum_ok) { char progress_char; p = pos; m = max; - percent = 100 / m * p; + percent = (100 / m * p) + 0.5; ipercent = percent / 100 * imax; if (is_binary) progress_char = '#'; else progress_char = '='; @@ -392,6 +414,132 @@ uint32_t GetOverallLen(FILEINFO *p_fi) { return overall_len; } +void sendAckFileinfo(void) { +uint8_t fi_ack_buffer[] = {'A','c','k',0, 0x49, 0x0F}; + if (is_old_protocol) return; +#ifdef _WIN32 + unsigned long ulNumBytes; + WriteFile(serial_fd, (void*)fi_ack_buffer, sizeof(fi_ack_buffer), &ulNumBytes, NULL); +#else + write (serial_fd, (void*)fi_ack_buffer, sizeof(fi_ack_buffer)); + if (tcdrain(serial_fd) == -1) { + perror("tcdrain err1: "); + return; + } +#endif +} + +void sendAckBlock(uint8_t block_num) { + uint8_t fi_ack_buffer[] = {'A','C','K',0, 0x49, 0xCF}; + int x; + uint8_t s_xor = 0; + uint8_t s_sum = 0; + if (is_old_protocol) return; + + fi_ack_buffer[3] = block_num; + // printf("\n Ack sent: "); + for (x=0; x<(sizeof(fi_ack_buffer)-2);x++) { + s_xor ^= fi_ack_buffer[x]; + s_sum += fi_ack_buffer[x]; + fi_ack_buffer[4] = s_xor; + fi_ack_buffer[5] = s_sum; + // printf("%2X ",fi_ack_buffer[x]); + } + // printf("%2X ",fi_ack_buffer[4]); + // printf("%2X \n",fi_ack_buffer[5]); +#ifdef _WIN32 + unsigned long ulNumBytes; + WriteFile(serial_fd, (void*)fi_ack_buffer, sizeof(fi_ack_buffer), &ulNumBytes, NULL); +#else + write (serial_fd, (void*)fi_ack_buffer, sizeof(fi_ack_buffer)); + if (tcdrain(serial_fd) == -1) { + perror("tcdrain err1: "); + return; + } +#endif +} + +uint8_t buf_ack[8]; + +uint8_t WaitReadAck(void) { + size_t len = 0; + #ifdef _WIN32 + unsigned long ulNumBytes =0; + while (1) { + sleep_ms(10); + ReadFile(serial_fd, &buf_ack[len], sizeof(buf_ack)-len, &ulNumBytes, NULL); + len += (size_t) ulNumBytes; + if (len == 6) break; + } +#else + // int x; + int result; + memset(buf_ack, 0, 8); + while (1) { + result = poll (spolfd_serial, 1, 300); // 200ms timeout + if (result == 0) { + // nothing is comming + continue; + } else if (result == -1) { + FlushSerialPort(); + CloseSerialPort(); + exit(EXIT_FAILURE); + } else if (result) { + len += read (serial_fd, &buf_ack[len], sizeof(buf_ack)-len); + if (len == 6) { + break; + } + } + } +#endif + return (len); +} +// receive fileinfo ACK +int CheckAckSum() { + int x; + uint8_t xorsum = 0; + uint8_t sumsum = 0; + for (x = 0; x < 4; x++) { + sumsum += buf_ack[x]; + xorsum ^= buf_ack[x]; + } + if ((xorsum == buf_ack[4]) && (sumsum == buf_ack[5])) { + return 0; + } else { + return 1; + } +} + +void RecvAckFileinfo(void) { + if (is_old_protocol) return; + if (WaitReadAck() == 6) { + if (CheckAckSum()) { + printf("\nErr Ack fileinfo\n"); + for (int x=0; x<6; x++) { + printf("%2X ",buf_ack[x]); + } + printf ("\n"); + exit(-1); + }; + }; + sleep_ms(100); // 100ms to wait speccy +} + +void RecvAckBlock(uint8_t block_number) { + if (is_old_protocol) return; + if (WaitReadAck() == 6) { + if (CheckAckSum() || (buf_ack[3] != block_number)) { + printf("\nErr ACK block\n"); + for (int x=0; x<6; x++) { + printf("%2X ",buf_ack[x]); + } + printf ("\n"); + exit(-1); + } + } + sleep_ms(100); // 100ms to wait speccy +} + /************************************************************************/ void sercpRecv(void) { int recv_phase = 0; // 0 - fileinfo, 1 - 16kiB block, 2 - last block @@ -427,8 +575,8 @@ void sercpRecv(void) { // receive fileinfo #ifdef _WIN32 sleep_ms(10); - ReadFile(serial_fd, p_buff, sizeof(fileinfo), &ulNumBytes, NULL); - len = (uint16_t) ulNumBytes; + ReadFile(serial_fd, p_buff, sizeof(fileinfo)-length, &ulNumBytes, NULL); + len = (size_t) ulNumBytes; #else sleep_ms(10); len = read (serial_fd, p_buff, sizeof(fileinfo)-length); @@ -445,6 +593,8 @@ void sercpRecv(void) { p_buff = buff; // initialize buffer pointer length = 0; expected_len = p_fileinfo->fi_blocks[block_index].block_len; + sleep_ms(100); + sendAckFileinfo(); tapout_fd = fopen ((char*)p_fileinfo->fi_name, "wb"); if (tapout_fd == NULL) { #ifdef _WIN32 @@ -480,7 +630,7 @@ void sercpRecv(void) { // receive of data block - max. length 16kiB #ifdef _WIN32 ReadFile(serial_fd, p_buff, expected_len, &ulNumBytes, NULL); - len = (uint16_t)ulNumBytes; + len = (size_t)ulNumBytes; #else len = read (serial_fd, p_buff, expected_len); #endif @@ -495,6 +645,9 @@ void sercpRecv(void) { if (CheckSumBlock(p_fileinfo, block_index, buff) == 0) { // block is ok - write to file if (tapout_fd) fwrite(buff, length, 1, tapout_fd); + // TODO + sleep_ms(100); + sendAckBlock(block_index); } length = 0; p_buff = buff; @@ -547,6 +700,7 @@ void sercpSend(void) { uint32_t len_sent; uint16_t send_size; uint32_t overall_sent; + uint8_t blck_num = 0; #ifdef _WIN32 unsigned long ulNumBytes; #endif @@ -617,8 +771,11 @@ void sercpSend(void) { } #endif printf("Fileinfo sent with filename: %s\n", p_bname); - sleep_ms(wait_ms); - + if (!is_flow_control_hw && !is_old_protocol) { + RecvAckFileinfo(); + } else { + sleep_ms(wait_ms); + } rewind(tap_fd); file_len = (uint32_t) st.st_size; overall_sent = 0; @@ -651,13 +808,20 @@ void sercpSend(void) { DoProgress(overall_sent, st.st_size, PROGRESS_PERCENT); } file_len -= len; - if (file_len > 0) { - sleep_ms(wait_ms); + if (!is_flow_control_hw && !is_old_protocol) { + RecvAckBlock(blck_num); + } else { + if (file_len > 0) { + sleep_ms(wait_ms); + } } + blck_num++; } printf("\nFile sent...\n"); - // FIXME na __APPLE__ tady pomaha pockat - // sleep_ms(5000); +#ifdef __APPLE__ + // FIXME on __APPLE__ this waiting helps + sleep_ms(wait_ms); +#endif fclose(tap_fd); } @@ -682,8 +846,13 @@ int OpenUart() { sDCB.fAbortOnError = TRUE; sDCB.fOutxDsrFlow = FALSE; sDCB.fDtrControl = DTR_CONTROL_DISABLE; - sDCB.fRtsControl = RTS_CONTROL_HANDSHAKE; - sDCB.fOutxCtsFlow = TRUE; + if (is_flow_control_hw) { + sDCB.fRtsControl = RTS_CONTROL_HANDSHAKE; + sDCB.fOutxCtsFlow = TRUE; + } else { + sDCB.fRtsControl = RTS_CONTROL_DISABLE; + sDCB.fOutxCtsFlow = FALSE; + } if (SetCommState(serial_fd, &sDCB) == 0) { return (-1); } @@ -700,16 +869,34 @@ int OpenUart() { if (SetCommTimeouts(serial_fd, &sCommTimeouts) == 0) { return (-1); } - FlushSerialPort(); return (0); #else struct termios oldtio, newtio; /* open the device to be non-blocking (read will return immediatly) */ - serial_fd = open(SERIALDEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK | O_NDELAY); + serial_fd = open(SERIALDEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK); // | O_NDELAY if (serial_fd < 0) { perror(SERIALDEVICE); return(-1); } +#ifdef __APPLE__ + // Note that open() follows POSIX semantics: multiple open() calls to the same file will succeed + // unless the TIOCEXCL ioctl is issued. This will prevent additional opens except by root-owned + // processes. + // See tty(4) <x-man-page//4/tty> and ioctl(2) <x-man-page//2/ioctl> for details. + if (ioctl(serial_fd, TIOCEXCL) == -1) { + printf("Error setting TIOCEXCL on %s - %s(%d).\n", SERIALDEVICE, strerror(errno), errno); + return (-1); + } + + // Now that the device is open, clear the O_NONBLOCK flag so subsequent I/O will block. + // See fcntl(2) <x-man-page//2/fcntl> for details. + if (fcntl(serial_fd, F_SETFL, 0) == -1) { + printf("Error clearing O_NONBLOCK %s - %s(%d).\n", SERIALDEVICE, strerror(errno), errno); + return (-1); + } + + +#endif tcgetattr(serial_fd, &oldtio); /* save current port settings */ bzero(&newtio, sizeof (newtio)); cfmakeraw(&newtio); @@ -743,29 +930,40 @@ int OpenUart() { newtio.c_cflag = B38400; break; } - cfsetspeed(&newtio, baud_rate); + cfsetospeed(&newtio, baud_rate); + cfsetispeed(&newtio, baud_rate); // set ispeed same as ospeed (see POSIX) #if defined (__APPLE__) || defined (BSD) -// newtio.c_cflag |= CS8 | CLOCAL | CREAD | CCTS_OFLOW | CRTS_IFLOW; - newtio.c_cflag |= CS8 | CLOCAL | CREAD; + if (is_flow_control_none) { + printf("Sorry, flow control is not tested on this platform\n"); + newtio.c_cflag |= CS8 | CLOCAL | CREAD | HUPCL; + } else { + newtio.c_cflag |= CS8 | CLOCAL | CREAD | CCTS_OFLOW | CRTS_IFLOW | HUPCL; + } #else - newtio.c_cflag |= CS8 | CLOCAL | CREAD | CRTSCTS; + if (is_flow_control_hw) { + // standard CTS/RTS flow control + newtio.c_cflag |= CS8 | CLOCAL | CREAD | CRTSCTS; + } else { + // usefull for eLeMeNt ZX and machines or serial port without CTS/RTS wires + newtio.c_cflag |= CS8 | CLOCAL | CREAD; + } #endif newtio.c_iflag &= ~(IXON | IXOFF | IXANY); newtio.c_oflag &= ~(OPOST); - printf ("Serial device: %s, communication speed is: %d Bd\n", SERIALDEVICE, baud_rate); + newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // raw input + printf ("Serial device: %s, communication speed is: %d Bd, flow control: %s\n", SERIALDEVICE, baud_rate, (is_flow_control_hw)?"CTS/RTS":"None"); + // http://unixwiz.net/techtips/termios-vmin-vtime.html - newtio.c_cc[VMIN] = 0; - newtio.c_cc[VTIME] = 0; + newtio.c_cc[VMIN] = 1; // minimum number to read + newtio.c_cc[VTIME] = 1; // time to wait tcsetattr(serial_fd, TCSANOW, &newtio); sleep_ms(20); - tcflush(serial_fd, TCIOFLUSH); return 0; #endif } - /************************************************************************/ /************************************************************************/ int main(int argc, char** argv, char** env) @@ -773,7 +971,7 @@ int main(int argc, char** argv, char** env) width = GetTerminalWidth(); inp_indx = 0; if (argc < 2) { - printf("You must specify the Serial device and file\n"); + // printf("You must specify the Serial device and file\n"); usage(); exit(1); } @@ -783,7 +981,7 @@ int main(int argc, char** argv, char** env) printf ("Can't open serial port\n"); exit (EXIT_FAILURE); } - + FlushSerialPort(); // make the input fifo empty #ifndef _WIN32 spolfd_serial[0].fd = serial_fd; // watching descriptor spolfd_serial[0].events = POLLIN; // watch data on input @@ -1,8 +1,8 @@ -// RC file, codepage utf-8 !!!!! +// RC file, codepage utf-8! #include <windows.h> // include for version info constants 1 VERSIONINFO -FILEVERSION 0,3,5,0 -PRODUCTVERSION 0,3,5,0 +FILEVERSION 0,8,0,0 +PRODUCTVERSION 0,8,0,0 FILETYPE VFT_APP { BLOCK "StringFileInfo" @@ -10,14 +10,14 @@ FILETYPE VFT_APP BLOCK "040904E4" { VALUE "CompanyName", "vym.cz" - VALUE "FileVersion", "0.3.5" + VALUE "FileVersion", "0.8.0" VALUE "FileDescription", "sercp - serial copy for ZX Spectrum" VALUE "InternalName", "sercp" VALUE "LegalCopyright", "GNU GPL v3 or above" VALUE "LegalTrademarks", "Pavel Vymetálek" VALUE "OriginalFilename", "sercp" VALUE "ProductName", "sercp" - VALUE "ProductVersion", "0.3.5" + VALUE "ProductVersion", "0.8.0" VALUE "Build environment", "Linux: Mingw64, x86_64" } } |