From 8c26b44ce66f674e2bdd1e3f6cf8ac2cf5eb1794 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 28 Aug 2006 14:29:02 +0000 Subject: [PATCH] Serial back end for Unix. Due to hardware limitations (no Linux box I own has both an X display and a working serial port) I have been unable to give this the full testing it deserves; I've managed to demonstrate the basic functionality of Unix Plink talking to a serial port, but I haven't been able to test the GTK front end. I have no reason to think it will fail, but I'll be more comfortable once somebody has actually tested it. [originally from svn r6822] --- Recipe | 14 +- unix/gtkdlg.c | 89 ++++++--- unix/gtkwin.c | 4 +- unix/unix.h | 5 + unix/uxcfg.c | 7 + unix/uxplink.c | 8 +- unix/uxser.c | 510 +++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 605 insertions(+), 32 deletions(-) create mode 100644 unix/uxser.c diff --git a/Recipe b/Recipe index 31a6b5d7..d2fe8d6b 100644 --- a/Recipe +++ b/Recipe @@ -223,7 +223,7 @@ GUITERM = TERMINAL window windlg winctrls sizetip winucs winprint + winutils wincfg sercfg # Same thing on Unix. -UXTERM = TERMINAL uxcfg uxucs uxprint timing +UXTERM = TERMINAL uxcfg sercfg uxucs uxprint timing GTKTERM = UXTERM gtkwin gtkcfg gtkdlg gtkcols gtkpanel xkeysym OSXTERM = UXTERM osxwin osxdlg osxctrls @@ -266,6 +266,9 @@ BE_NONE = be_none nocproxy # More backend sets, with the additional Windows serial-port module. W_BE_ALL = be_all_s winser cproxy W_BE_NOSSH = be_nos_s winser nocproxy +# And with the Unix serial-port module. +U_BE_ALL = be_all_s uxser cproxy +U_BE_NOSSH = be_nos_s uxser nocproxy # ------------------------------------------------------------ # Definitions of actual programs. The program name, followed by a @@ -293,12 +296,13 @@ puttygen : [G] winpgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version pterm : [X] GTKTERM uxmisc misc ldisc settings uxpty uxsel BE_NONE uxstore + uxsignal CHARSET cmdline uxpterm version time -putty : [X] GTKTERM uxmisc misc ldisc settings uxsel BE_ALL uxstore +putty : [X] GTKTERM uxmisc misc ldisc settings uxsel U_BE_ALL uxstore + uxsignal CHARSET uxputty NONSSH UXSSH UXMISC ux_x11 -puttytel : [X] GTKTERM uxmisc misc ldisc settings uxsel BE_NOSSH +puttytel : [X] GTKTERM uxmisc misc ldisc settings uxsel U_BE_NOSSH + uxstore uxsignal CHARSET uxputty NONSSH UXMISC -plink : [U] uxplink uxcons NONSSH UXSSH BE_ALL logging UXMISC uxsignal ux_x11 +plink : [U] uxplink uxcons NONSSH UXSSH U_BE_ALL logging UXMISC uxsignal + + ux_x11 puttygen : [U] cmdgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version + sshrand uxnoise sshsha misc sshrsa sshdss uxcons uxstore uxmisc @@ -318,5 +322,5 @@ PuTTYgen : [M] macpgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version + sshrand macnoise sshsha macstore misc sshrsa sshdss macmisc sshpubk + sshaes sshsh512 import macpgen.rsrc macpgkey macabout -PuTTY : [MX] osxmain OSXTERM OSXMISC CHARSET BE_ALL NONSSH UXSSH +PuTTY : [MX] osxmain OSXTERM OSXMISC CHARSET U_BE_ALL NONSSH UXSSH + ux_x11 uxpty uxsignal testback putty.icns info.plist diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index 19546d9c..b2725e44 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -55,6 +55,7 @@ struct uctrl { GtkWidget *menu; /* for optionmenu (==droplist) */ GtkWidget *optmenu; /* also for optionmenu */ GtkWidget *text; /* for text */ + GtkWidget *label; /* for dlg_label_change */ GtkAdjustment *adj; /* for the scrollbar in a list box */ guint textsig; }; @@ -90,6 +91,7 @@ static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event, gpointer data); static void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw, int chr, int action, void *ptr); +static void shortcut_highlight(GtkWidget *label, int chr); static int listitem_single_key(GtkWidget *item, GdkEventKey *event, gpointer data); static int listitem_multi_key(GtkWidget *item, GdkEventKey *event, @@ -594,13 +596,42 @@ void dlg_text_set(union control *ctrl, void *dlg, char const *text) void dlg_label_change(union control *ctrl, void *dlg, char const *text) { - /* - * This function is currently only used by the config box to - * switch the labels on the host and port boxes between serial - * and network modes. Since Unix does not (yet) have a serial - * back end, this function can safely do nothing for the - * moment. - */ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + + switch (uc->ctrl->generic.type) { + case CTRL_BUTTON: + gtk_label_set_text(GTK_LABEL(uc->toplevel), text); + shortcut_highlight(uc->toplevel, ctrl->button.shortcut); + break; + case CTRL_CHECKBOX: + gtk_label_set_text(GTK_LABEL(uc->toplevel), text); + shortcut_highlight(uc->toplevel, ctrl->checkbox.shortcut); + break; + case CTRL_RADIO: + gtk_label_set_text(GTK_LABEL(uc->label), text); + shortcut_highlight(uc->label, ctrl->radio.shortcut); + break; + case CTRL_EDITBOX: + gtk_label_set_text(GTK_LABEL(uc->label), text); + shortcut_highlight(uc->label, ctrl->editbox.shortcut); + break; + case CTRL_FILESELECT: + gtk_label_set_text(GTK_LABEL(uc->label), text); + shortcut_highlight(uc->label, ctrl->fileselect.shortcut); + break; + case CTRL_FONTSELECT: + gtk_label_set_text(GTK_LABEL(uc->label), text); + shortcut_highlight(uc->label, ctrl->fontselect.shortcut); + break; + case CTRL_LISTBOX: + gtk_label_set_text(GTK_LABEL(uc->label), text); + shortcut_highlight(uc->label, ctrl->listbox.shortcut); + break; + default: + assert(!"This shouldn't happen"); + break; + } } void dlg_filesel_set(union control *ctrl, void *dlg, Filename fn) @@ -1336,6 +1367,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, uc->buttons = NULL; uc->entry = uc->list = uc->menu = NULL; uc->button = uc->optmenu = uc->text = NULL; + uc->label = NULL; switch (ctrl->generic.type) { case CTRL_BUTTON: @@ -1381,6 +1413,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, gtk_widget_show(label); shortcut_add(scs, label, ctrl->radio.shortcut, SHORTCUT_UCTRL, uc); + uc->label = label; } percentages = g_new(gint, ctrl->radio.ncolumns); for (i = 0; i < ctrl->radio.ncolumns; i++) { @@ -1485,6 +1518,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, gtk_widget_show(w); w = container; + uc->label = label; } gtk_signal_connect(GTK_OBJECT(uc->entry), "focus_out_event", GTK_SIGNAL_FUNC(editbox_lostfocus), dp); @@ -1513,6 +1547,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, ctrl->fileselect.shortcut : ctrl->fontselect.shortcut), SHORTCUT_UCTRL, uc); + uc->label = ww; } uc->entry = ww = gtk_entry_new(); @@ -1650,6 +1685,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, shortcut_add(scs, label, ctrl->listbox.shortcut, SHORTCUT_UCTRL, uc); w = container; + uc->label = label; } break; case CTRL_TEXT: @@ -1716,6 +1752,8 @@ static void treeitem_sel(GtkItem *item, gpointer data) panels_switch_to(sp->panels, sp->panel); + dlg_refresh(NULL, sp->dp); + sp->dp->shortcuts = &sp->shortcuts; sp->dp->currtreeitem = sp->treeitem; } @@ -1935,13 +1973,31 @@ int tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) return FALSE; } -void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw, - int chr, int action, void *ptr) +static void shortcut_highlight(GtkWidget *labelw, int chr) { GtkLabel *label = GTK_LABEL(labelw); gchar *currstr, *pattern; int i; + gtk_label_get(label, &currstr); + for (i = 0; currstr[i]; i++) + if (tolower((unsigned char)currstr[i]) == chr) { + GtkRequisition req; + + pattern = dupprintf("%*s_", i, ""); + + gtk_widget_size_request(GTK_WIDGET(label), &req); + gtk_label_set_pattern(label, pattern); + gtk_widget_set_usize(GTK_WIDGET(label), -1, req.height); + + sfree(pattern); + break; + } +} + +void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw, + int chr, int action, void *ptr) +{ if (chr == NO_SHORTCUT) return; @@ -1959,20 +2015,7 @@ void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw, scs->sc[chr].uc = (struct uctrl *)ptr; } - gtk_label_get(label, &currstr); - for (i = 0; currstr[i]; i++) - if (tolower((unsigned char)currstr[i]) == chr) { - GtkRequisition req; - - pattern = dupprintf("%*s_", i, ""); - - gtk_widget_size_request(GTK_WIDGET(label), &req); - gtk_label_set_pattern(label, pattern); - gtk_widget_set_usize(GTK_WIDGET(label), -1, req.height); - - sfree(pattern); - break; - } + shortcut_highlight(labelw, chr); } int get_listitemheight(void) diff --git a/unix/gtkwin.c b/unix/gtkwin.c index 76f78f69..920d7fe0 100644 --- a/unix/gtkwin.c +++ b/unix/gtkwin.c @@ -155,6 +155,8 @@ Filename platform_default_filename(const char *name) char *platform_default_s(const char *name) { + if (!strcmp(name, "SerialLine")) + return dupstr("/dev/ttyS0"); return NULL; } @@ -3444,7 +3446,7 @@ int pt_main(int argc, char **argv) cmdline_run_saved(&inst->cfg); - if (!*inst->cfg.host && !cfgbox(&inst->cfg)) + if (!cfg_launchable(&inst->cfg) && !cfgbox(&inst->cfg)) exit(0); /* config box hit Cancel */ } diff --git a/unix/unix.h b/unix/unix.h index c0b56c74..ff665b9b 100644 --- a/unix/unix.h +++ b/unix/unix.h @@ -149,4 +149,9 @@ void *sk_getxdmdata(void *sock, int *lenp); if (max < fd + 1) max = fd + 1; \ } while (0) +/* + * Exports from winser.c. + */ +extern Backend serial_backend; + #endif diff --git a/unix/uxcfg.c b/unix/uxcfg.c index 067fc7ed..de8e8865 100644 --- a/unix/uxcfg.c +++ b/unix/uxcfg.c @@ -69,4 +69,11 @@ void unix_setup_config_box(struct controlbox *b, int midsession) } } + /* + * Serial back end is available on Unix. However, we have to + * mask out a couple of the configuration options: mark and + * space parity are not conveniently supported, and neither is + * DSR/DTR flow control. + */ + ser_setup_config_box(b, midsession, 0x07, 0x07); } diff --git a/unix/uxplink.c b/unix/uxplink.c index 1daafe4b..38129f96 100644 --- a/unix/uxplink.c +++ b/unix/uxplink.c @@ -82,6 +82,8 @@ char *platform_default_s(const char *name) return dupstr(getenv("TERM")); if (!strcmp(name, "UserName")) return get_username(); + if (!strcmp(name, "SerialLine")) + return dupstr("/dev/ttyS0"); return NULL; } @@ -627,7 +629,7 @@ int main(int argc, char **argv) errors = 1; } } else if (*p) { - if (!*cfg.host) { + if (!cfg_launchable(&cfg)) { char *q = p; /* @@ -701,7 +703,7 @@ int main(int argc, char **argv) { Config cfg2; do_defaults(host, &cfg2); - if (loaded_session || cfg2.host[0] == '\0') { + if (loaded_session || !cfg_launchable(&cfg2)) { /* No settings for this host; use defaults */ /* (or session was already loaded with -load) */ strncpy(cfg.host, host, sizeof(cfg.host) - 1); @@ -755,7 +757,7 @@ int main(int argc, char **argv) if (errors) return 1; - if (!*cfg.host) { + if (!cfg_launchable(&cfg)) { usage(); } diff --git a/unix/uxser.c b/unix/uxser.c new file mode 100644 index 00000000..85e96189 --- /dev/null +++ b/unix/uxser.c @@ -0,0 +1,510 @@ +/* + * Serial back end (Unix-specific). + */ + +/* + * TODO: + * + * - send break. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "putty.h" +#include "tree234.h" + +#define SERIAL_MAX_BACKLOG 4096 + +typedef struct serial_backend_data { + void *frontend; + int fd; + int finished; + int inbufsize; + bufchain output_data; +} *Serial; + +/* + * We store our serial backends in a tree sorted by fd, so that + * when we get an uxsel notification we know which backend instance + * is the owner of the serial port that caused it. + */ +static int serial_compare_by_fd(void *av, void *bv) +{ + Serial a = (Serial)av; + Serial b = (Serial)bv; + + if (a->fd < b->fd) + return -1; + else if (a->fd > b->fd) + return +1; + return 0; +} + +static int serial_find_by_fd(void *av, void *bv) +{ + int a = *(int *)av; + Serial b = (Serial)bv; + + if (a < b->fd) + return -1; + else if (a > b->fd) + return +1; + return 0; +} + +static tree234 *serial_by_fd = NULL; + +static int serial_select_result(int fd, int event); +static void serial_uxsel_setup(Serial serial); +static void serial_try_write(Serial serial); + +static const char *serial_configure(Serial serial, Config *cfg) +{ + struct termios options; + int bflag, bval; + const char *str; + char *msg; + + if (serial->fd < 0) + return "Unable to reconfigure already-closed serial connection"; + + tcgetattr(serial->fd, &options); + + /* + * Find the appropriate baud rate flag. + */ +#define SETBAUD(x) (bflag = B ## x, bval = x) +#define CHECKBAUD(x) do { if (cfg->serspeed >= x) SETBAUD(x); } while (0) + SETBAUD(50); +#ifdef B75 + CHECKBAUD(75); +#endif +#ifdef B110 + CHECKBAUD(110); +#endif +#ifdef B134 + CHECKBAUD(134); +#endif +#ifdef B150 + CHECKBAUD(150); +#endif +#ifdef B200 + CHECKBAUD(200); +#endif +#ifdef B300 + CHECKBAUD(300); +#endif +#ifdef B600 + CHECKBAUD(600); +#endif +#ifdef B1200 + CHECKBAUD(1200); +#endif +#ifdef B1800 + CHECKBAUD(1800); +#endif +#ifdef B2400 + CHECKBAUD(2400); +#endif +#ifdef B4800 + CHECKBAUD(4800); +#endif +#ifdef B9600 + CHECKBAUD(9600); +#endif +#ifdef B19200 + CHECKBAUD(19200); +#endif +#ifdef B38400 + CHECKBAUD(38400); +#endif +#ifdef B57600 + CHECKBAUD(57600); +#endif +#ifdef B76800 + CHECKBAUD(76800); +#endif +#ifdef B115200 + CHECKBAUD(115200); +#endif +#ifdef B230400 + CHECKBAUD(230400); +#endif +#undef CHECKBAUD +#undef SETBAUD + cfsetispeed(&options, bflag); + cfsetospeed(&options, bflag); + msg = dupprintf("Configuring baud rate %d", bval); + logevent(serial->frontend, msg); + sfree(msg); + + options.c_cflag &= ~CSIZE; + switch (cfg->serdatabits) { + case 5: options.c_cflag |= CS5; break; + case 6: options.c_cflag |= CS6; break; + case 7: options.c_cflag |= CS7; break; + case 8: options.c_cflag |= CS8; break; + default: return "Invalid number of data bits (need 5, 6, 7 or 8)"; + } + msg = dupprintf("Configuring %d data bits", cfg->serdatabits); + logevent(serial->frontend, msg); + sfree(msg); + + if (cfg->serstopbits >= 4) { + options.c_cflag |= CSTOPB; + } else { + options.c_cflag &= ~CSTOPB; + } + msg = dupprintf("Configuring %d stop bits", + (options.c_cflag & CSTOPB ? 2 : 1)); + logevent(serial->frontend, msg); + sfree(msg); + + options.c_cflag &= ~(IXON|IXOFF); + if (cfg->serflow == SER_FLOW_XONXOFF) { + options.c_cflag |= IXON | IXOFF; + str = "XON/XOFF"; + } else if (cfg->serflow == SER_FLOW_RTSCTS) { +#ifdef CRTSCTS + options.c_cflag |= CRTSCTS; +#endif +#ifdef CNEW_RTSCTS + options.c_cflag |= CNEW_RTSCTS; +#endif + str = "RTS/CTS"; + } else + str = "no"; + msg = dupprintf("Configuring %s flow control", str); + logevent(serial->frontend, msg); + sfree(msg); + + /* Parity */ + if (cfg->serparity == SER_PAR_ODD) { + options.c_cflag |= PARENB; + options.c_cflag |= PARODD; + str = "odd"; + } else if (cfg->serparity == SER_PAR_EVEN) { + options.c_cflag |= PARENB; + options.c_cflag &= ~PARODD; + str = "even"; + } else { + options.c_cflag &= ~PARENB; + str = "no"; + } + msg = dupprintf("Configuring %s parity", str); + logevent(serial->frontend, msg); + sfree(msg); + + options.c_cflag |= CLOCAL | CREAD; + options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + options.c_oflag &= ~OPOST; + options.c_cc[VMIN] = 1; + options.c_cc[VTIME] = 0; + + if (tcsetattr(serial->fd, TCSANOW, &options) < 0) + return "Unable to configure serial port"; + + return NULL; +} + +/* + * Called to set up the serial connection. + * + * Returns an error message, or NULL on success. + * + * Also places the canonical host name into `realhost'. It must be + * freed by the caller. + */ +static const char *serial_init(void *frontend_handle, void **backend_handle, + Config *cfg, + char *host, int port, char **realhost, int nodelay, + int keepalive) +{ + Serial serial; + const char *err; + + serial = snew(struct serial_backend_data); + *backend_handle = serial; + + serial->frontend = frontend_handle; + serial->finished = FALSE; + serial->inbufsize = 0; + bufchain_init(&serial->output_data); + + { + char *msg = dupprintf("Opening serial device %s", cfg->serline); + logevent(serial->frontend, msg); + } + + serial->fd = open(cfg->serline, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK); + if (serial->fd < 0) + return "Unable to open serial port"; + + err = serial_configure(serial, cfg); + if (err) + return err; + + *realhost = dupstr(cfg->serline); + + if (!serial_by_fd) + serial_by_fd = newtree234(serial_compare_by_fd); + add234(serial_by_fd, serial); + + serial_uxsel_setup(serial); + + return NULL; +} + +static void serial_close(Serial serial) +{ + if (serial->fd >= 0) { + close(serial->fd); + serial->fd = -1; + } +} + +static void serial_free(void *handle) +{ + Serial serial = (Serial) handle; + + serial_close(serial); + + bufchain_clear(&serial->output_data); + + sfree(serial); +} + +static void serial_reconfig(void *handle, Config *cfg) +{ + Serial serial = (Serial) handle; + const char *err; + + err = serial_configure(serial, cfg); + + /* + * FIXME: what should we do if err returns something? + */ +} + +static int serial_select_result(int fd, int event) +{ + Serial serial; + char buf[4096]; + int ret; + int finished = FALSE; + + serial = find234(serial_by_fd, &fd, serial_find_by_fd); + + if (!serial) + return 1; /* spurious event; keep going */ + + if (event == 1) { + ret = read(serial->fd, buf, sizeof(buf)); + + if (ret == 0) { + /* + * Shouldn't happen on a real serial port, but I'm open + * to the idea that there might be two-way devices we + * can treat _like_ serial ports which can return EOF. + */ + finished = TRUE; + } else if (ret < 0) { + perror("read serial port"); + exit(1); + } else if (ret > 0) { + serial->inbufsize = from_backend(serial->frontend, 0, buf, ret); + serial_uxsel_setup(serial); /* might acquire backlog and freeze */ + } + } else if (event == 2) { + /* + * Attempt to send data down the pty. + */ + serial_try_write(serial); + } + + if (finished) { + serial_close(serial); + + serial->finished = TRUE; + + notify_remote_exit(serial->frontend); + } + + return !finished; +} + +static void serial_uxsel_setup(Serial serial) +{ + int rwx = 0; + + if (serial->inbufsize <= SERIAL_MAX_BACKLOG) + rwx |= 1; + if (bufchain_size(&serial->output_data)) + rwx |= 2; /* might also want to write to it */ + uxsel_set(serial->fd, rwx, serial_select_result); +} + +static void serial_try_write(Serial serial) +{ + void *data; + int len, ret; + + assert(serial->fd >= 0); + + while (bufchain_size(&serial->output_data) > 0) { + bufchain_prefix(&serial->output_data, &data, &len); + ret = write(serial->fd, data, len); + + if (ret < 0 && (errno == EWOULDBLOCK)) { + /* + * We've sent all we can for the moment. + */ + break; + } + if (ret < 0) { + perror("write serial port"); + exit(1); + } + bufchain_consume(&serial->output_data, ret); + } + + serial_uxsel_setup(serial); +} + +/* + * Called to send data down the serial connection. + */ +static int serial_send(void *handle, char *buf, int len) +{ + Serial serial = (Serial) handle; + + if (serial->fd < 0) + return 0; + + bufchain_add(&serial->output_data, buf, len); + serial_try_write(serial); + + return bufchain_size(&serial->output_data); +} + +/* + * Called to query the current sendability status. + */ +static int serial_sendbuffer(void *handle) +{ + Serial serial = (Serial) handle; + return bufchain_size(&serial->output_data); +} + +/* + * Called to set the size of the window + */ +static void serial_size(void *handle, int width, int height) +{ + /* Do nothing! */ + return; +} + +/* + * Send serial special codes. + */ +static void serial_special(void *handle, Telnet_Special code) +{ + /* + * FIXME: serial break? XON? XOFF? + */ + return; +} + +/* + * Return a list of the special codes that make sense in this + * protocol. + */ +static const struct telnet_special *serial_get_specials(void *handle) +{ + /* + * FIXME: serial break? XON? XOFF? + */ + return NULL; +} + +static int serial_connected(void *handle) +{ + return 1; /* always connected */ +} + +static int serial_sendok(void *handle) +{ + return 1; +} + +static void serial_unthrottle(void *handle, int backlog) +{ + Serial serial = (Serial) handle; + serial->inbufsize = backlog; + serial_uxsel_setup(serial); +} + +static int serial_ldisc(void *handle, int option) +{ + /* + * Local editing and local echo are off by default. + */ + return 0; +} + +static void serial_provide_ldisc(void *handle, void *ldisc) +{ + /* This is a stub. */ +} + +static void serial_provide_logctx(void *handle, void *logctx) +{ + /* This is a stub. */ +} + +static int serial_exitcode(void *handle) +{ + Serial serial = (Serial) handle; + if (serial->fd >= 0) + return -1; /* still connected */ + else + /* Exit codes are a meaningless concept with serial ports */ + return INT_MAX; +} + +/* + * cfg_info for Serial does nothing at all. + */ +static int serial_cfg_info(void *handle) +{ + return 0; +} + +Backend serial_backend = { + serial_init, + serial_free, + serial_reconfig, + serial_send, + serial_sendbuffer, + serial_size, + serial_special, + serial_get_specials, + serial_connected, + serial_exitcode, + serial_sendok, + serial_ldisc, + serial_provide_ldisc, + serial_provide_logctx, + serial_unthrottle, + serial_cfg_info, + 1 +};