blob: d33d685356892e8441336ca5ea30ed324dbfbed5 [file] [log] [blame]
/* line discipline loading daemon
* open a serial device and attach a line discipline on it
*
* Usage:
* ldattach GIGASET_M101 /dev/ttyS0
*
* =====================================================================
* 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.
* =====================================================================
*/
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <termios.h>
#include <unistd.h>
#include "c.h"
#include "all-io.h"
#include "nls.h"
#include "strutils.h"
#include "closestream.h"
#include <signal.h>
#include <sys/socket.h>
#include <linux/if.h>
#include <linux/tty.h> /* for N_GSM0710 */
#ifdef LINUX_GSMMUX_H
# include <linux/gsmmux.h> /* Add by guowenxue */
#else
struct gsm_config
{
unsigned int adaption;
unsigned int encapsulation;
unsigned int initiator;
unsigned int t1;
unsigned int t2;
unsigned int t3;
unsigned int n2;
unsigned int mru;
unsigned int mtu;
unsigned int k;
unsigned int i;
unsigned int unused[8]; /* Padding for expansion without
breaking stuff */
};
# define GSMIOC_GETCONF _IOR('G', 0, struct gsm_config)
# define GSMIOC_SETCONF _IOW('G', 1, struct gsm_config)
#endif
#ifndef N_GIGASET_M101
# define N_GIGASET_M101 16
#endif
#ifndef N_PPS
# define N_PPS 18
#endif
#ifndef N_GSM0710
# define N_GSM0710 21
#endif
#define MAXINTROPARMLEN 32
/* attach a line discipline ioctl */
#ifndef TIOCSETD
# define TIOCSETD 0x5423
#endif
static int debug = 0;
struct ld_table {
const char *name;
int value;
};
/* currently supported line disciplines, plus some aliases */
static const struct ld_table ld_discs[] = {
{ "TTY", N_TTY },
{ "SLIP", N_SLIP },
{ "MOUSE", N_MOUSE },
{ "PPP", N_PPP },
{ "STRIP", N_STRIP },
{ "AX25", N_AX25 },
{ "X25", N_X25 },
{ "6PACK", N_6PACK },
{ "R3964", N_R3964 },
{ "IRDA", N_IRDA },
{ "HDLC", N_HDLC },
{ "SYNC_PPP", N_SYNC_PPP },
{ "SYNCPPP", N_SYNC_PPP },
{ "HCI", N_HCI },
{ "GIGASET_M101", N_GIGASET_M101 },
{ "M101", N_GIGASET_M101 },
{ "GIGASET", N_GIGASET_M101 },
{ "PPS", N_PPS },
{ "GSM0710", N_GSM0710},
{ NULL, 0 }
};
/* known c_iflag names */
static const struct ld_table ld_iflags[] =
{
{ "IGNBRK", IGNBRK },
{ "BRKINT", BRKINT },
{ "IGNPAR", IGNPAR },
{ "PARMRK", PARMRK },
{ "INPCK", INPCK },
{ "ISTRIP", ISTRIP },
{ "INLCR", INLCR },
{ "IGNCR", IGNCR },
{ "ICRNL", ICRNL },
{ "IUCLC", IUCLC },
{ "IXON", IXON },
{ "IXANY", IXANY },
{ "IXOFF", IXOFF },
{ "IMAXBEL", IMAXBEL },
{ "IUTF8", IUTF8 },
{ NULL, 0 }
};
static void dbg(char *fmt, ...)
{
va_list args;
if (debug == 0)
return;
fflush(NULL);
va_start(args, fmt);
#ifdef HAVE_VWARNX
vwarnx(fmt, args);
#else
fprintf(stderr, "%s: ", program_invocation_short_name);
vfprintf(stderr, fmt, args);
fprintf(stderr, "\n");
#endif
va_end(args);
fflush(NULL);
return;
}
static int lookup_table(const struct ld_table *tab, const char *str)
{
const struct ld_table *t;
for (t = tab; t && t->name; t++)
if (!strcasecmp(t->name, str))
return t->value;
return -1;
}
static void print_table(FILE * out, const struct ld_table *tab)
{
const struct ld_table *t;
int i;
for (t = tab, i = 1; t && t->name; t++, i++) {
fprintf(out, " %-12s", t->name);
if (!(i % 5))
fputc('\n', out);
}
}
static int parse_iflag(char *str, int *set_iflag, int *clr_iflag)
{
int iflag;
char *s;
for (s = strtok(str, ","); s != NULL; s = strtok(NULL, ",")) {
if (*s == '-')
s++;
if ((iflag = lookup_table(ld_iflags, s)) < 0)
iflag = strtos32_or_err(s, _("invalid iflag"));
if (s > str && *(s - 1) == '-')
*clr_iflag |= iflag;
else
*set_iflag |= iflag;
}
dbg("iflag (set/clear): %d/%d", *set_iflag, *clr_iflag);
return 0;
}
static void __attribute__((__noreturn__)) usage(void)
{
FILE *out = stdout;
fputs(USAGE_HEADER, out);
fprintf(out, _(" %s [options] <ldisc> <device>\n"), program_invocation_short_name);
fputs(USAGE_SEPARATOR, out);
fputs(_("Attach a line discipline to a serial line.\n"), out);
fputs(USAGE_OPTIONS, out);
fputs(_(" -d, --debug print verbose messages to stderr\n"), out);
fputs(_(" -s, --speed <value> set serial line speed\n"), out);
fputs(_(" -c, --intro-command <string> intro sent before ldattach\n"), out);
fputs(_(" -p, --pause <seconds> pause between intro and ldattach\n"), out);
fputs(_(" -7, --sevenbits set character size to 7 bits\n"), out);
fputs(_(" -8, --eightbits set character size to 8 bits\n"), out);
fputs(_(" -n, --noparity set parity to none\n"), out);
fputs(_(" -e, --evenparity set parity to even\n"), out);
fputs(_(" -o, --oddparity set parity to odd\n"), out);
fputs(_(" -1, --onestopbit set stop bits to one\n"), out);
fputs(_(" -2, --twostopbits set stop bits to two\n"), out);
fputs(_(" -i, --iflag [-]<iflag> set input mode flag\n"), out);
fputs(USAGE_SEPARATOR, out);
printf(USAGE_HELP_OPTIONS(25));
fputs(_("\nKnown <ldisc> names:\n"), out);
print_table(out, ld_discs);
fputs(USAGE_SEPARATOR, out);
fputs(_("\nKnown <iflag> names:\n"), out);
print_table(out, ld_iflags);
printf(USAGE_MAN_TAIL("ldattach(8)"));
exit(EXIT_SUCCESS);
}
static int my_cfsetspeed(struct termios *ts, int speed)
{
/* Standard speeds
* -- cfsetspeed() is able to translate number to Bxxx constants
*/
if (cfsetspeed(ts, speed) == 0)
return 0;
/* Nonstandard speeds
* -- we have to bypass glibc and set the speed manually (because glibc
* checks for speed and supports Bxxx bit rates only)...
*/
#ifdef _HAVE_STRUCT_TERMIOS_C_ISPEED
# define BOTHER 0010000 /* non standard rate */
dbg("using non-standard speeds");
ts->c_ospeed = ts->c_ispeed = speed;
ts->c_cflag &= ~CBAUD;
ts->c_cflag |= BOTHER;
return 0;
#else
return -1;
#endif
}
static void handler(int s)
{
dbg("got SIG %i -> exiting", s);
exit(EXIT_SUCCESS);
}
static void gsm0710_set_conf(int tty_fd)
{
struct gsm_config c;
/* Add by guowenxue */
/* get n_gsm configuration */
ioctl(tty_fd, GSMIOC_GETCONF, &c);
/* we are initiator and need encoding 0 (basic) */
c.initiator = 1;
c.encapsulation = 0;
/* our modem defaults to a maximum size of 127 bytes */
c.mru = 127;
c.mtu = 127;
/* set the new configuration */
ioctl(tty_fd, GSMIOC_SETCONF, &c);
/* Add by guowenxue end*/
}
int main(int argc, char **argv)
{
int tty_fd;
struct termios ts;
int speed = 0, bits = '-', parity = '-', stop = '-';
int set_iflag = 0, clr_iflag = 0;
int ldisc;
int optc;
char *dev;
int intropause = 1;
char *introparm = NULL;
static const struct option opttbl[] = {
{"speed", required_argument, NULL, 's'},
{"sevenbits", no_argument, NULL, '7'},
{"eightbits", no_argument, NULL, '8'},
{"noparity", no_argument, NULL, 'n'},
{"evenparity", no_argument, NULL, 'e'},
{"oddparity", no_argument, NULL, 'o'},
{"onestopbit", no_argument, NULL, '1'},
{"twostopbits", no_argument, NULL, '2'},
{"iflag", required_argument, NULL, 'i'},
{"help", no_argument, NULL, 'h'},
{"version", no_argument, NULL, 'V'},
{"debug", no_argument, NULL, 'd'},
{"intro-command", no_argument, NULL, 'c'},
{"pause", no_argument, NULL, 'p'},
{NULL, 0, NULL, 0}
};
signal(SIGKILL, handler);
signal(SIGINT, handler);
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
atexit(close_stdout);
/* parse options */
if (argc == 0)
errx(EXIT_FAILURE, _("bad usage"));
while ((optc =
getopt_long(argc, argv, "dhV78neo12s:i:c:p:", opttbl,
NULL)) >= 0) {
switch (optc) {
case 'd':
debug = 1;
break;
case '1':
case '2':
stop = optc;
break;
case '7':
case '8':
bits = optc;
break;
case 'n':
case 'e':
case 'o':
parity = optc;
break;
case 's':
speed = strtos32_or_err(optarg, _("invalid speed argument"));
break;
case 'p':
intropause = strtou32_or_err(optarg, _("invalid pause argument"));
if (intropause > 10)
errx(EXIT_FAILURE, "invalid pause: %s", optarg);
break;
case 'c':
introparm = optarg;
break;
case 'i':
parse_iflag(optarg, &set_iflag, &clr_iflag);
break;
case 'V':
printf(UTIL_LINUX_VERSION);
return EXIT_SUCCESS;
case 'h':
usage();
default:
errtryhelp(EXIT_FAILURE);
}
}
if (argc - optind != 2) {
warnx(_("not enough arguments"));
errtryhelp(EXIT_FAILURE);
}
/* parse line discipline specification */
ldisc = lookup_table(ld_discs, argv[optind]);
if (ldisc < 0)
ldisc = strtos32_or_err(argv[optind], _("invalid line discipline argument"));
/* ldisc specific option settings */
if (ldisc == N_GIGASET_M101) {
/* device specific defaults for line speed and data format */
if (speed == 0)
speed = 115200;
if (bits == '-')
bits = '8';
if (parity == '-')
parity = 'n';
if (stop == '-')
stop = '1';
}
/* open device */
dev = argv[optind + 1];
if ((tty_fd = open(dev, O_RDWR | O_NOCTTY)) < 0)
err(EXIT_FAILURE, _("cannot open %s"), dev);
if (!isatty(tty_fd))
errx(EXIT_FAILURE, _("%s is not a serial line"), dev);
dbg("opened %s", dev);
/* set line speed and format */
if (tcgetattr(tty_fd, &ts) < 0)
err(EXIT_FAILURE,
_("cannot get terminal attributes for %s"), dev);
cfmakeraw(&ts);
if (speed && my_cfsetspeed(&ts, speed) < 0)
errx(EXIT_FAILURE, _("speed %d unsupported"), speed);
switch (stop) {
case '1':
ts.c_cflag &= ~CSTOPB;
break;
case '2':
ts.c_cflag |= CSTOPB;
break;
case '-':
break;
default:
abort();
}
switch (bits) {
case '7':
ts.c_cflag = (ts.c_cflag & ~CSIZE) | CS7;
break;
case '8':
ts.c_cflag = (ts.c_cflag & ~CSIZE) | CS8;
break;
case '-':
break;
default:
abort();
}
switch (parity) {
case 'n':
ts.c_cflag &= ~(PARENB | PARODD);
break;
case 'e':
ts.c_cflag |= PARENB;
ts.c_cflag &= ~PARODD;
break;
case 'o':
ts.c_cflag |= (PARENB | PARODD);
break;
case '-':
break;
default:
abort();
}
ts.c_cflag |= CREAD; /* just to be on the safe side */
ts.c_iflag |= set_iflag;
ts.c_iflag &= ~clr_iflag;
if (tcsetattr(tty_fd, TCSAFLUSH, &ts) < 0)
err(EXIT_FAILURE,
_("cannot set terminal attributes for %s"), dev);
dbg("set to raw %d %c%c%c: cflag=0x%x",
speed, bits, parity, stop, ts.c_cflag);
if (introparm && *introparm)
{
dbg("intro command is '%s'", introparm);
if (write_all(tty_fd, introparm, strlen(introparm)) != 0)
err(EXIT_FAILURE,
_("cannot write intro command to %s"), dev);
if (intropause) {
dbg("waiting for %d seconds", intropause);
sleep(intropause);
}
}
/* Attach the line discipline. */
if (ioctl(tty_fd, TIOCSETD, &ldisc) < 0)
err(EXIT_FAILURE, _("cannot set line discipline"));
dbg("line discipline set to %d", ldisc);
/* ldisc specific post-attach actions */
if (ldisc == N_GSM0710)
gsm0710_set_conf(tty_fd);
/* Go into background if not in debug mode. */
if (!debug && daemon(0, 0) < 0)
err(EXIT_FAILURE, _("cannot daemonize"));
/* Sleep to keep the line discipline active. */
pause();
exit(EXIT_SUCCESS);
}