diff --git a/kissserial.c b/kissserial.c new file mode 100644 index 0000000..aeab11b --- /dev/null +++ b/kissserial.c @@ -0,0 +1,506 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2011, 2013, 2014, 2016, 2017 John Langner, WB2OSZ +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + + +/*------------------------------------------------------------------ + * + * Module: kissserial.c + * + * Purpose: Act as a virtual KISS TNC for use by other packet radio applications. + * This file provides the service by good old fashioned serial port. + * Other files implement a pseudo terminal or TCP KISS interface. + * + * Description: This implements the KISS TNC protocol as described in: + * http://www.ka9q.net/papers/kiss.html + * + * Briefly, a frame is composed of + * + * * FEND (0xC0) + * * Contents - with special escape sequences so a 0xc0 + * byte in the data is not taken as end of frame. + * as part of the data. + * * FEND + * + * The first byte of the frame contains: + * + * * port number in upper nybble. + * * command in lower nybble. + * + * Commands from application recognized: + * + * _0 Data Frame AX.25 frame in raw format. + * + * _1 TXDELAY See explanation in xmit.c. + * + * _2 Persistence " " + * + * _3 SlotTime " " + * + * _4 TXtail " " + * Spec says it is obsolete but Xastir + * sends it and we respect it. + * + * _5 FullDuplex Ignored. + * + * _6 SetHardware TNC specific. + * + * FF Return Exit KISS mode. Ignored. + * + * + * Messages sent to client application: + * + * _0 Data Frame Received AX.25 frame in raw format. + * + * + * Platform differences: + * + * This file implements KISS over a serial port. + * It should behave pretty much the same for both Windows and Linux. + * + * When running a client application on Windows, two applications + * can be connected together using a a "Null-modem emulator" + * such as com0com from http://sourceforge.net/projects/com0com/ + * + * (When running a client application, on the same host, with Linux, + * a pseudo terminal can be used for old applications. More modern + * applications will generally have AGW and/or KISS over TCP.) + * + * + * version 1.5: Split out from kiss.c, simplified, consistent for Windows and Linux. + * Add polling option for use with Bluetooth. + * + *---------------------------------------------------------------*/ + +#include "direwolf.h" + +#include +#include +#include +#include +#include +#include + + +#include "ax25_pad.h" +#include "textcolor.h" +#include "serial_port.h" +#include "kissserial.h" +#include "kiss_frame.h" +#include "xmit.h" + + +#if __WIN32__ +typedef HANDLE MYFDTYPE; +#define MYFDERROR INVALID_HANDLE_VALUE +#else +typedef int MYFDTYPE; +#define MYFDERROR (-1) +#endif + + +/* + * Save Configuration for later use. + */ + +static struct misc_config_s *g_misc_config_p; + +/* + * Accumulated KISS frame and state of decoder. + */ + +static kiss_frame_t kf; + + +/* + * The serial port device handle. + */ + +static MYFDTYPE serialport_fd = MYFDERROR; + + + + +// TODO: define in one place, use everywhere. +#if __WIN32__ +#define THREAD_F unsigned __stdcall +#else +#define THREAD_F void * +#endif + +static THREAD_F kissserial_listen_thread (void *arg); + + +static int kissserial_debug = 0; /* Print information flowing from and to client. */ + +void kissserial_set_debug (int n) +{ + kissserial_debug = n; +} + + +/* In server.c. Should probably move to some misc. function file. */ + +void hex_dump (unsigned char *p, int len); + + + + +/*------------------------------------------------------------------- + * + * Name: kissserial_init + * + * Purpose: Set up a serial port acting as a virtual KISS TNC. + * + * Inputs: mc-> + * kiss_serial_port - Name of device for real or virtual serial port. + * kiss_serial_speed - Speed, bps, or 0 meaning leave it alone. + * kiss_serial_poll - When non-zero, poll each n seconds to see if + * device has appeared. + * + * Outputs: + * + * Description: (1) Open file descriptor for the device. + * (2) Start a new thread to listen for commands from client app + * so the main application doesn't block while we wait. + * + *--------------------------------------------------------------------*/ + + +void kissserial_init (struct misc_config_s *mc) +{ + +#if __WIN32__ + HANDLE kissserial_listen_th; +#else + pthread_t kissserial_listen_tid; + int e; +#endif + + g_misc_config_p = mc; + + memset (&kf, 0, sizeof(kf)); + + + if (strlen(g_misc_config_p->kiss_serial_port) > 0) { + + if (g_misc_config_p->kiss_serial_poll == 0) { + + // Normal case, try to open the serial port at start up time. + + serialport_fd = serial_port_open (g_misc_config_p->kiss_serial_port, g_misc_config_p->kiss_serial_speed); + + if (serialport_fd != MYFDERROR) { + text_color_set(DW_COLOR_INFO); + dw_printf ("Opened %s for serial port KISS.\n", g_misc_config_p->kiss_serial_port); + } + else { + // An error message was already displayed. + } + } + else { + + // Polling case. Defer until read and device not opened. + text_color_set(DW_COLOR_INFO); + dw_printf ("Will be checking periodically for %s\n", g_misc_config_p->kiss_serial_port); + } + + if (g_misc_config_p->kiss_serial_poll != 0 || serialport_fd != MYFDERROR) { +#if __WIN32__ + kissserial_listen_th = (HANDLE)_beginthreadex (NULL, 0, kissserial_listen_thread, NULL, 0, NULL); + if (kissserial_listen_th == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Could not create kiss serial thread\n"); + return; + } +#else + e = pthread_create (&kissserial_listen_tid, NULL, kissserial_listen_thread, NULL); + if (e != 0) { + text_color_set(DW_COLOR_ERROR); + perror("Could not create kiss serial thread."); + } +#endif + } + } + + +#if DEBUG + text_color_set (DW_COLOR_DEBUG); + + dw_printf ("end of kiss_init: serialport_fd = %d, polling = %d\n", serialport_fd, g_misc_config_p->kiss_serial_poll); +#endif +} + + + + +/*------------------------------------------------------------------- + * + * Name: kissserial_send_rec_packet + * + * Purpose: Send a received packet or text string to the client app. + * + * Inputs: chan - Channel number where packet was received. + * 0 = first, 1 = second if any. + * + * pp - Identifier for packet object. + * + * fbuf - Address of raw received frame buffer + * or a text string. + * + * flen - Length of raw received frame not including the FCS + * or -1 for a text string. + * + * client - Not used for serial port version. + * Here so that 3 related functions all have + * the same parameter list. + * + * Description: Send message to client. + * We really don't care if anyone is listening or not. + * I don't even know if we can find out. + * + *--------------------------------------------------------------------*/ + + +void kissserial_send_rec_packet (int chan, unsigned char *fbuf, int flen, int client) +{ + unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN + 2]; + int kiss_len; + int err; + + +/* + * Quietly discard if we don't have open connection. + */ + if (serialport_fd == MYFDERROR) { + return; + } + + if (flen < 0) { + flen = strlen((char*)fbuf); + if (kissserial_debug) { + kiss_debug_print (TO_CLIENT, "Fake command prompt", fbuf, flen); + } + strlcpy ((char *)kiss_buff, (char *)fbuf, sizeof(kiss_buff)); + kiss_len = strlen((char *)kiss_buff); + } + else { + + unsigned char stemp[AX25_MAX_PACKET_LEN + 1]; + + if (flen > (int)(sizeof(stemp)) - 1) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\nSerial Port KISS buffer too small. Truncated.\n\n"); + flen = (int)(sizeof(stemp)) - 1; + } + + stemp[0] = (chan << 4) + 0; + memcpy (stemp+1, fbuf, flen); + + if (kissserial_debug >= 2) { + /* AX.25 frame with the CRC removed. */ + text_color_set(DW_COLOR_DEBUG); + dw_printf ("\n"); + dw_printf ("Packet content before adding KISS framing and any escapes:\n"); + hex_dump (fbuf, flen); + } + + kiss_len = kiss_encapsulate (stemp, flen+1, kiss_buff); + + /* This has KISS framing and escapes for sending to client app. */ + + if (kissserial_debug) { + kiss_debug_print (TO_CLIENT, NULL, kiss_buff, kiss_len); + } + } + +/* + * This write can block on Windows if using the virtual null modem + * and nothing is connected to the other end. + * The solution is found in the com0com ReadMe file: + * + * Q. My application hangs during its startup when it sends anything to one paired + * COM port. The only way to unhang it is to start HyperTerminal, which is connected + * to the other paired COM port. I didn't have this problem with physical serial + * ports. + * A. Your application can hang because receive buffer overrun is disabled by + * default. You can fix the problem by enabling receive buffer overrun for the + * receiving port. Also, to prevent some flow control issues you need to enable + * baud rate emulation for the sending port. So, if your application use port CNCA0 + * and other paired port is CNCB0, then: + * + * 1. Launch the Setup Command Prompt shortcut. + * 2. Enter the change commands, for example: + * + * command> change CNCB0 EmuOverrun=yes + * command> change CNCA0 EmuBR=yes + */ + + err = serial_port_write (serialport_fd, (char*)kiss_buff, kiss_len); + + if (err != kiss_len) + { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\nError sending KISS message to client application thru serial port.\n\n"); + serial_port_close (serialport_fd); + serialport_fd = MYFDERROR; + } + +} /* kissserial_send_rec_packet */ + + + +/*------------------------------------------------------------------- + * + * Name: kissserial_get + * + * Purpose: Read one byte from the KISS client app. + * + * Global In: serialport_fd + * + * Returns: one byte (value 0 - 255) or terminate thread on error. + * + * Description: There is room for improvment here. Reading one byte + * at a time is inefficient. We could read a large block + * into a local buffer and return a byte from that most of the time. + * Is it worth the effort? I don't know. With GHz processors and + * the low data rate here it might not make a noticable difference. + * + *--------------------------------------------------------------------*/ + + +static int kissserial_get (void) +{ + int ch; // normally 0-255 but -1 for error. + + + if (g_misc_config_p->kiss_serial_poll == 0) { +/* + * Normal case, was opened at start up time. + */ + ch = serial_port_get1 (serialport_fd); + + if (ch < 0) { + + text_color_set(DW_COLOR_ERROR); + dw_printf ("\nSerial Port KISS read error. Closing connection.\n\n"); + serial_port_close (serialport_fd); + serialport_fd = MYFDERROR; +#if __WIN32__ + ExitThread (0); +#else + pthread_exit (NULL); +#endif + } + +#if DEBUGx + text_color_set(DW_COLOR_DEBUG); + dw_printf ("kissserial_get(%d) returns 0x%02x\n", fd, ch); +#endif + return (ch); + } + +/* + * Polling case. Wait until device is present and open. + */ + while (1) { + + if (serialport_fd != MYFDERROR) { + + // Open, try to read. + + ch = serial_port_get1 (serialport_fd); + + if (ch >= 0) { + return (ch); + } + + text_color_set(DW_COLOR_ERROR); + dw_printf ("\nSerial Port KISS read error. Closing connection.\n\n"); + serial_port_close (serialport_fd); + serialport_fd = MYFDERROR; + } + else { + + // Not open. Wait for it to appear and try opening. + + struct stat buf; + + SLEEP_SEC (g_misc_config_p->kiss_serial_poll); + + if (stat(g_misc_config_p->kiss_serial_port, &buf) == 0) { + + // It's there now. Try to open. + + serialport_fd = serial_port_open (g_misc_config_p->kiss_serial_port, g_misc_config_p->kiss_serial_speed); + + if (serialport_fd != MYFDERROR) { + text_color_set(DW_COLOR_INFO); + dw_printf ("\nOpened %s for serial port KISS.\n\n", g_misc_config_p->kiss_serial_port); + + memset (&kf, 0, sizeof(kf)); // Start with clean state. + } + else { + // An error message was already displayed. + } + } + } + } + +} /* end kissserial_get */ + + + +/*------------------------------------------------------------------- + * + * Name: kissserial_listen_thread + * + * Purpose: Read messages from serial port KISS client application. + * + * Global In: serialport_fd + * + * Description: Reads bytes from the serial port KISS client app and + * sends them to kiss_rec_byte for processing. + * kiss_rec_byte is a common function used by all 3 KISS + * interfaces: serial port, pseudo terminal, and TCP. + * + *--------------------------------------------------------------------*/ + + +static THREAD_F kissserial_listen_thread (void *arg) +{ + unsigned char ch; + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("kissserial_listen_thread ( %d )\n", fd); +#endif + + while (1) { + ch = kissserial_get(); + kiss_rec_byte (&kf, ch, kissserial_debug, -1, kissserial_send_rec_packet); + } + +#if __WIN32__ + return(0); +#else + return (THREAD_F) 0; /* Unreachable but avoids compiler warning. */ +#endif +} + +/* end kissserial.c */