File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / mpd / src / modem.c
Revision 1.1: download - view: text, annotated - select for diffs - revision graph
Tue Feb 21 23:32:47 2012 UTC (12 years, 10 months ago) by misho
CVS tags: MAIN, HEAD
Initial revision


/*
 * modem.c
 *
 * Written by Archie Cobbs <archie@freebsd.org>
 * Copyright (c) 1995-1999 Whistle Communications, Inc. All rights reserved.
 * See ``COPYRIGHT.whistle''
 */

#include "ppp.h"
#include <termios.h>
#include "chat.h"
#include "phys.h"
#include "modem.h"
#include "ngfunc.h"
#include "lcp.h"
#include "event.h"
#include "util.h"
#include "log.h"

#include <netgraph/ng_message.h>
#include <netgraph/ng_socket.h>
#include <netgraph/ng_async.h>
#include <netgraph/ng_tty.h>
#include <netgraph.h>

/*
 * DEFINITIONS
 */

#ifndef NETGRAPHDISC
  #define NETGRAPHDISC			7	/* XXX */
#endif

  #define MODEM_MTU			1600
  #define MODEM_MRU			1600

  #define MODEM_MIN_CLOSE_TIME		3
  #define MODEM_CHECK_INTERVAL		1
  #define MODEM_DEFAULT_SPEED		115200
  #define MODEM_ERR_REPORT_INTERVAL	60

  #define MODEM_IDLE_RESULT_ANSWER	"answer"
  #define MODEM_IDLE_RESULT_RINGBACK	"ringback"

  /* Special chat script variables we set/use */
  #define CHAT_VAR_LOGIN		"$Login"
  #define CHAT_VAR_PASSWORD		"$Password"
  #define CHAT_VAR_DEVICE		"$modemDevice"
  #define CHAT_VAR_IDLE_RESULT		"$IdleResult"
  #define CHAT_VAR_CONNECT_SPEED	"$ConnectionSpeed"
  #define CHAT_VAR_CALLING		"$CallingID"
  #define CHAT_VAR_CALLED		"$CalledID"

  /* Nominal link parameters */
  #define MODEM_DEFAULT_BANDWIDTH	28800	/* ~33.6 modem */
  #define MODEM_DEFAULT_LATENCY		10000	/* 10ms */

  /* Modem device state */
  struct modeminfo {
    int			fd;			/* Device file desc, or -1 */
    int			csock;			/* netgraph control socket */
    int			speed;			/* Port speed */
    u_int		watch;			/* Signals to watch */
    char		device[20];		/* Serial device name */
    char		ttynode[NG_NODESIZ];	/* TTY node name */
    char		connScript[CHAT_MAX_LABEL];	/* Connect script */
    char		idleScript[CHAT_MAX_LABEL];	/* Idle script */
    struct pppTimer	checkTimer;		/* Timer to check pins */
    struct pppTimer	reportTimer;		/* Timer to report errs */
    struct pppTimer	startTimer;		/* Timer for ModemStart() */
    struct optinfo	options;		/* Binary options */
    struct ng_async_cfg	acfg;			/* ng_async node config */
    ChatInfo		chat;			/* Chat script state */
    time_t		lastClosed;		/* Last time device closed */
    u_char		opened:1;		/* We have been opened */
    u_char		originated:1;		/* We originated current call */
    u_char		answering:1;		/* $IdleResult was "answer" */
  };
  typedef struct modeminfo	*ModemInfo;

  /* Set menu options */
  enum {
    SET_DEVICE,
    SET_SPEED,
    SET_CSCRIPT,
    SET_ISCRIPT,
    SET_SCRIPT_VAR,
    SET_WATCH
  };

/*
 * INTERNAL FUNCTIONS
 */

  static int		ModemInit(Link l);
  static void		ModemOpen(Link l);
  static void		ModemClose(Link l);
  static void		ModemUpdate(Link l);
  static int		ModemSetAccm(Link l, u_int32_t xmit, u_int32_t recv);
  static void		ModemStat(Context ctx);
  static int		ModemOriginated(Link l);
  static int		ModemIsSync(Link l);
  static int		ModemSelfAddr(Link l, void *buf, size_t buf_len);
  static int		ModemIface(Link l, void *buf, size_t buf_len);
  static int		ModemCallingNum(Link l, void *buf, size_t buf_len);
  static int		ModemCalledNum(Link l, void *buf, size_t buf_len);

  static void		ModemStart(void *arg);
  static void		ModemDoClose(Link l, int opened);

  /* Chat callbacks */
  static int		ModemChatSetBaudrate(void *arg, int baud);
  static void		ModemChatConnectResult(void *arg,
				int rslt, const char *msg);
  static void		ModemChatIdleResult(void *arg, int rslt,
				const char *msg);

  static int		ModemSetCommand(Context ctx, int ac, char *av[], void *arg);
  static int		ModemInstallNodes(Link l);
  static int		ModemGetNgStats(Link l, struct ng_async_stat *sp);

  static void		ModemCheck(void *arg);
  static void		ModemErrorCheck(void *arg);

/*
 * GLOBAL VARIABLES
 */

  const struct phystype gModemPhysType = {
    .name		= "modem",
    .descr		= "Serial port modem",
    .mtu		= MODEM_MTU,
    .mru		= MODEM_MRU,
    .tmpl		= 0,
    .init		= ModemInit,
    .open		= ModemOpen,
    .close		= ModemClose,
    .update		= ModemUpdate,
    .showstat		= ModemStat,
    .originate		= ModemOriginated,
    .issync		= ModemIsSync,
    .setaccm 		= ModemSetAccm,
    .selfaddr		= ModemSelfAddr,
    .peeraddr		= ModemSelfAddr,
    .peeriface		= ModemIface,
    .callingnum		= ModemCallingNum,
    .callednum		= ModemCalledNum,
  };

  const struct cmdtab ModemSetCmds[] = {
    { "device {name}",			"Set modem device",
      ModemSetCommand, NULL, 2, (void *) SET_DEVICE },
    { "speed {port-speed}",		"Set modem speed",
      ModemSetCommand, NULL, 2, (void *) SET_SPEED },
    { "script [{label}]",		"Set connect script",
      ModemSetCommand, NULL, 2, (void *) SET_CSCRIPT },
    { "idle-script [{label}]",		"Set idle script",
      ModemSetCommand, NULL, 2, (void *) SET_ISCRIPT },
    { "var ${var} {string}",		"Set script variable",
      ModemSetCommand, NULL, 2, (void *) SET_SCRIPT_VAR },
    { "watch [+|-cd] [+|-dsr]", 	"Set signals to monitor",
      ModemSetCommand, NULL, 2, (void *) SET_WATCH },
    { NULL },
  };

/*
 * INTERNAL VARIABLES
 */

  static int	gSpeedList[] = {
    50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 
    38400, 7200, 14400, 28800, 57600, 76800, 115200, 230400, 460800, 614400,
    921600, 1228800, 1843200, 2000000, 3000000, -1
  };

/*
 * ModemInit()
 *
 * Allocate and initialize device private info
 */

static int
ModemInit(Link l)
{
    char	defSpeed[32];
    ModemInfo	m;

    m = (ModemInfo) (l->info = Malloc(MB_PHYS, sizeof(*m)));
    m->watch = TIOCM_CAR;
    m->chat = ChatInit(l, ModemChatSetBaudrate);
    m->fd = -1;
    m->opened = FALSE;

    /* Set nominal link speed and bandwith for a modem connection */
    l->latency = MODEM_DEFAULT_LATENCY;
    l->bandwidth = MODEM_DEFAULT_BANDWIDTH;

    /* Set default speed */
    m->speed = MODEM_DEFAULT_SPEED;
    snprintf(defSpeed, sizeof(defSpeed), "%d", m->speed);
    ChatPresetVar(m->chat, CHAT_VAR_BAUDRATE, defSpeed);
    return(0);
}

/*
 * ModemOpen()
 */

static void
ModemOpen(Link l)
{
    ModemInfo	const m = (ModemInfo) l->info;

    assert(!m->opened);
    m->opened = TRUE;
    if (m->fd >= 0) {			/* Device is already open.. */
	if (m->answering) {			/* We just answered a call */
	    m->originated = FALSE;
	    m->answering = FALSE;
	    ModemChatConnectResult(l, TRUE, NULL);
	} else {
	    Log(LG_PHYS2, ("[%s] MODEM: Stop idle script then dial back",
	      l->name));
	    ModemDoClose(l, TRUE);		/* Stop idle script then dial back */
	}
    } else
	ModemStart(l);			/* Open device and try to dial */
}

/*
 * ModemStart()
 */

static void
ModemStart(void *arg)
{
    Link		const l = (Link) arg;
    ModemInfo		const m = (ModemInfo) l->info;
    const time_t	now = time(NULL);
    char		password[AUTH_MAX_PASSWORD];
    FILE		*scriptfp;

    /* If we're idle, and there's no idle script, there's nothing to do */
    assert(!m->answering);
    TimerStop(&m->startTimer);
    if (!m->opened &&
      (!*m->idleScript || !Enabled(&l->conf.options, LINK_CONF_INCOMING) ||
       gShutdownInProgress))
	return;

    /* Avoid brief hang from kernel enforcing minimum DTR hold time */
    if (now - m->lastClosed < MODEM_MIN_CLOSE_TIME) {
	TimerInit(&m->startTimer, "ModemStart",
	  (MODEM_MIN_CLOSE_TIME - (now - m->lastClosed)) * SECONDS, ModemStart, l);
	TimerStart(&m->startTimer);
	return;
    }

    /* Open and configure serial port */
    if ((m->fd = OpenSerialDevice(l->name, m->device, m->speed)) < 0) {
	Log(LG_ERR, ("[%s] MODEM: Fail to open serial port %s on speed %d",
	  l->name, m->device, m->speed));
	goto fail;
    }
    /* If connecting, but no connect script, then skip chat altogether */
    if (m->opened && !*m->connScript) {
	Log(LG_PHYS2, ("[%s] MODEM: No connect script present", l->name));
	ModemChatConnectResult(l, TRUE, NULL);
	return;
    }

    /* Open chat script file */
    if ((scriptfp = OpenConfFile(SCRIPT_FILE, NULL)) == NULL) {
	Log(LG_ERR, ("[%s] MODEM: can't open chat script file", l->name));
	ExclusiveCloseDevice(l->name, m->fd, m->device);
	m->fd = -1;
fail:
	m->opened = FALSE;
	m->lastClosed = time(NULL);
	l->state = PHYS_STATE_DOWN;
	PhysDown(l, STR_ERROR, STR_DEV_NOT_READY);
	return;
    }

    /* Preset some special chat variables */
    ChatPresetVar(m->chat, CHAT_VAR_DEVICE, m->device);
    ChatPresetVar(m->chat, CHAT_VAR_LOGIN, l->lcp.auth.conf.authname);
    if (l->lcp.auth.conf.password[0] != 0) {
	ChatPresetVar(m->chat, CHAT_VAR_PASSWORD, l->lcp.auth.conf.password);
    } else if (AuthGetData(l->lcp.auth.conf.authname,
	password, sizeof(password), NULL, NULL) >= 0) {
	    ChatPresetVar(m->chat, CHAT_VAR_PASSWORD, password);
    }

    /* Run connect or idle script as appropriate */
    if (!m->opened) {
	ChatPresetVar(m->chat, CHAT_VAR_IDLE_RESULT, "<unknown>");
	ChatStart(m->chat, m->fd, scriptfp, m->idleScript, ModemChatIdleResult);
    } else {
	m->originated = TRUE;
	l->state = PHYS_STATE_CONNECTING;
	ChatStart(m->chat, m->fd, scriptfp, m->connScript, ModemChatConnectResult);
    }
}

/*
 * ModemClose()
 */

static void
ModemClose(Link l)
{
    ModemInfo	const m = (ModemInfo) l->info;

    if (!m->opened)
	return;
    ModemDoClose(l, FALSE);
    l->state = PHYS_STATE_DOWN;
    PhysDown(l, STR_MANUALLY, NULL);
}

static void
ModemUpdate(Link l)
{
    ModemInfo	const m = (ModemInfo) l->info;

    if (m->opened || TimerRemain(&m->startTimer) >= 0)
	return;		/* nothing needs to be done right now */
    if (m->fd >= 0 &&
      (!*m->idleScript || !Enabled(&l->conf.options, LINK_CONF_INCOMING)))
	ModemDoClose(l, FALSE);
    else if (m->fd < 0 &&
      (*m->idleScript && Enabled(&l->conf.options, LINK_CONF_INCOMING)))
	ModemStart(l);
}

/*
 * ModemDoClose()
 */

static void
ModemDoClose(Link l, int opened)
{
    ModemInfo	const m = (ModemInfo) l->info;
    const char	ch = ' ';

    /* Shutdown everything */
    assert(m->fd >= 0);
    ChatAbort(m->chat);
    TimerStop(&m->checkTimer);
    TimerStop(&m->startTimer);
    TimerStop(&m->reportTimer);
    if (*m->ttynode != '\0') {
	char	path[NG_PATHSIZ];

	snprintf(path, sizeof(path), "%s:%s", m->ttynode, NG_TTY_HOOK);
	NgFuncShutdownNode(m->csock, l->name, path);
	snprintf(path, sizeof(path), "%s:", m->ttynode);
	NgFuncShutdownNode(m->csock, l->name, path);
	*m->ttynode = '\0';
    }
    (void) write(m->fd, &ch, 1);	/* USR kludge to prevent dial lockup */
    if (m->csock > 0) {
	close(m->csock);
	m->csock = -1;
    }
    ExclusiveCloseDevice(l->name, m->fd, m->device);
    m->lastClosed = time(NULL);
    m->answering = FALSE;
    m->fd = -1;
    m->opened = opened;
    ModemStart(l);
}

/*
 * ModemSetAccm()
 */

static int
ModemSetAccm(Link l, u_int32_t xmit, u_int32_t recv)
{
    ModemInfo	const m = (ModemInfo) l->info;
    char	path[NG_PATHSIZ];

    /* Update async config */
    m->acfg.accm = xmit|recv;
    snprintf(path, sizeof(path), "%s:%s", m->ttynode, NG_TTY_HOOK);
    if (NgSendMsg(m->csock, path, NGM_ASYNC_COOKIE,
      NGM_ASYNC_CMD_SET_CONFIG, &m->acfg, sizeof(m->acfg)) < 0) {
	Perror("[%s] MODEM: can't update config for %s", l->name, path);
	return (-1);
    }
    return (0);
}

/*
 * ModemChatConnectResult()
 *
 * Connect chat script returns here when finished.
 */

static void
ModemChatConnectResult(void *arg, int result, const char *msg)
{
    Link	const l = (Link) arg;
    ModemInfo	const m = (ModemInfo) l->info;
    char	*cspeed;
    int		bw;

    /* Was the connect script successful? */
    Log(LG_PHYS, ("[%s] MODEM: chat script %s",
	l->name, result ? "succeeded" : "failed"));
    if (!result) {
failed:
	ModemDoClose(l, FALSE);
	l->state = PHYS_STATE_DOWN;
	PhysDown(l, STR_ERROR, msg);
	return;
    }

    /* Set modem's reported connection speed (if any) as the link bandwidth */
    if ((cspeed = ChatGetVar(m->chat, CHAT_VAR_CONNECT_SPEED)) != NULL) {
	if ((bw = (int) strtoul(cspeed, NULL, 10)) > 0)
	    l->bandwidth = bw;
	Freee(cspeed);
    }

    /* Do async <-> sync conversion via netgraph node */
    if (ModemInstallNodes(l) < 0) {
	msg = STR_DEV_NOT_READY;
	goto failed;
    }

    /* Start pin check and report timers */
    TimerInit(&m->checkTimer, "ModemCheck",
	MODEM_CHECK_INTERVAL * SECONDS, ModemCheck, l);
    TimerStart(&m->checkTimer);
    TimerStop(&m->reportTimer);
    TimerInit(&m->reportTimer, "ModemReport",
	MODEM_ERR_REPORT_INTERVAL * SECONDS, ModemErrorCheck, l);
    TimerStart(&m->reportTimer);

    l->state = PHYS_STATE_UP;
    PhysUp(l);
}

/*
 * ModemChatIdleResult()
 *
 * Idle chat script returns here when finished. If the script returned
 * successfully, then one of two things happened: either we answered
 * an incoming call, or else we got a ring and want to do ringback.
 * We tell the difference by checking $IdleResult.
 */

static void
ModemChatIdleResult(void *arg, int result, const char *msg)
{
    Link	const l = (Link) arg;
    ModemInfo	const m = (ModemInfo) l->info;
    char	*idleResult;

    /* If script failed, then do nothing */
    if (!result) {
	ModemDoClose(l, FALSE);
	return;
    }

    /* See what script wants us to do now by checking variable $IdleResult */
    if ((idleResult = ChatGetVar(m->chat, CHAT_VAR_IDLE_RESULT)) == NULL) {
	Log(LG_ERR, ("[%s] MODEM: idle script succeeded, but %s not defined",
	    l->name, CHAT_VAR_IDLE_RESULT));
	ModemDoClose(l, FALSE);
	return;
    }

    /* Do whatever */
    Log(LG_PHYS, ("[%s] MODEM: idle script succeeded, action=%s",
	l->name, idleResult));

    if (gShutdownInProgress) {
	Log(LG_PHYS, ("Shutdown sequence in progress, ignoring"));
	ModemDoClose(l, FALSE);
    } else if (strcasecmp(idleResult, MODEM_IDLE_RESULT_ANSWER) == 0) {
	Log(LG_PHYS, ("[%s] MODEM: opening link in %s mode", l->name, "answer"));
	RecordLinkUpDownReason(NULL, l, 1, STR_INCOMING_CALL, msg ? "%s" : NULL, msg);
	m->answering = TRUE;
	l->state = PHYS_STATE_READY;
	PhysIncoming(l);
    } else if (strcasecmp(idleResult, MODEM_IDLE_RESULT_RINGBACK) == 0) {
	Log(LG_PHYS, ("[%s] MODEM: opening link in %s mode", l->name, "ringback"));
	RecordLinkUpDownReason(NULL, l, 1, STR_RINGBACK, msg ? "%s" : NULL, msg);
	m->answering = FALSE;
	PhysIncoming(l);
    } else {
	Log(LG_ERR, ("[%s] MODEM: idle script succeeded, but action \"%s\" unknown",
	  l->name, idleResult));
	ModemDoClose(l, FALSE);
    }
    Freee(idleResult);
}

/*
 * ModemInstallNodes()
 */

static int
ModemInstallNodes(Link l)
{
    ModemInfo 		m = (ModemInfo) l->info;
    struct ngm_mkpeer	ngm;
    struct ngm_connect	cn;
    char       		path[NG_PATHSIZ];
    int			hotchar = PPP_FLAG;
#if NGM_TTY_COOKIE < 1226109660
    struct nodeinfo	ngtty;
    int			ldisc = NETGRAPHDISC;
#else
    struct ngm_rmhook	rm;
    union {
	u_char buf[sizeof(struct ng_mesg) + sizeof(struct nodeinfo)];
	struct ng_mesg reply;
    } repbuf;
    struct ng_mesg *const reply = &repbuf.reply;
    struct nodeinfo *ninfo = (struct nodeinfo *)&reply->data;
    int	tty[2];
#endif

    /* Get a temporary netgraph socket node */
    if (NgMkSockNode(NULL, &m->csock, NULL) < 0) {
	Perror("[%s] MODEM: NgMkSockNode failed", l->name);
	return(-1);
    }

#if NGM_TTY_COOKIE < 1226109660
    /* Install ng_tty line discipline */
    if (ioctl(m->fd, TIOCSETD, &ldisc) < 0) {

	/* Installation of the tty node type should be automatic, but isn't yet.
	   The 'mkpeer' below will fail, because you can only create a ng_tty
           node via TIOCSETD; however, this will force a load of the node type. */
	if (errno == ENODEV) {
	    (void)NgSendAsciiMsg(m->csock, ".:",
		"mkpeer { type=\"%s\" ourhook=\"dummy\" peerhook=\"%s\" }",
		NG_TTY_NODE_TYPE, NG_TTY_HOOK);
	}
	if (ioctl(m->fd, TIOCSETD, &ldisc) < 0) {
	    Perror("[%s] ioctl(TIOCSETD, %d)", l->name, ldisc);
	    close(m->csock);
	    return(-1);
	}
    }

    /* Get the name of the ng_tty node */
    if (ioctl(m->fd, NGIOCGINFO, &ngtty) < 0) {
	Perror("[%s] MODEM: ioctl(NGIOCGINFO)", l->name);
	close(m->csock);
	return(-1);
    }
    strlcpy(m->ttynode, ngtty.name, sizeof(m->ttynode));
#else
    /* Attach a TTY node */
    snprintf(ngm.type, sizeof(ngm.type), "%s", NG_TTY_NODE_TYPE);
    snprintf(ngm.ourhook, sizeof(ngm.ourhook), "%s", NG_TTY_HOOK);
    snprintf(ngm.peerhook, sizeof(ngm.peerhook), "%s", NG_TTY_HOOK);
    if (NgSendMsg(m->csock, ".", NGM_GENERIC_COOKIE,
	    NGM_MKPEER, &ngm, sizeof(ngm)) < 0) {
	Perror("[%s] MODEM: can't connect %s node on %s", l->name,
	    NG_TTY_NODE_TYPE, ".");
	close(m->csock);
	return(-1);
    }
    snprintf(path, sizeof(path), ".:%s", NG_TTY_HOOK);
    if (NgSendMsg(m->csock, path,
	    NGM_GENERIC_COOKIE, NGM_NODEINFO, NULL, 0) != -1) {
	if (NgRecvMsg(m->csock, reply, sizeof(repbuf), NULL) < 0) {
	    Perror("[%s] MODEM: can't locate %s node on %s (%d)", l->name,
	    NG_TTY_NODE_TYPE, path, errno);
	    close(m->csock);
	    return(-1);
	}
    }
    snprintf(m->ttynode, sizeof(m->ttynode), "[%x]", ninfo->id);
    /* Attach to the TTY */
    tty[0] = gPid;
    tty[1] = m->fd;
    if (NgSendMsg(m->csock, path, NGM_TTY_COOKIE,
          NGM_TTY_SET_TTY, &tty, sizeof(tty)) < 0) {
	Perror("[%s] MODEM: can't hook tty to fd %d", l->name, m->fd);
	close(m->csock);
	return(-1);
    }
    /* Disconnect temporary hook. */
    snprintf(rm.ourhook, sizeof(rm.ourhook), "%s", NG_TTY_HOOK);
    if (NgSendMsg(m->csock, ".",
	    NGM_GENERIC_COOKIE, NGM_RMHOOK, &rm, sizeof(rm)) < 0) {
	Perror("[%s] MODEM: can't remove hook %s", l->name, NG_TTY_HOOK);
	close(m->csock);
	return(-1);
    }
#endif

    /* Set the ``hot char'' on the TTY node */
    snprintf(path, sizeof(path), "%s:", m->ttynode);
    if (NgSendMsg(m->csock, path, NGM_TTY_COOKIE,
      NGM_TTY_SET_HOTCHAR, &hotchar, sizeof(hotchar)) < 0) {
	Perror("[%s] MODEM: can't set hotchar", l->name);
	close(m->csock);
	return(-1);
    }

    /* Attach an async converter node */
    strcpy(ngm.type, NG_ASYNC_NODE_TYPE);
    strcpy(ngm.ourhook, NG_TTY_HOOK);
    strcpy(ngm.peerhook, NG_ASYNC_HOOK_ASYNC);
    if (NgSendMsg(m->csock, path, NGM_GENERIC_COOKIE,
      NGM_MKPEER, &ngm, sizeof(ngm)) < 0) {
	Perror("[%s] MODEM: can't connect %s node", l->name, NG_ASYNC_NODE_TYPE);
	close(m->csock);
	return(-1);
    }

    /* Configure the async converter node */
    snprintf(path, sizeof(path), "%s:%s", m->ttynode, NG_TTY_HOOK);
    memset(&m->acfg, 0, sizeof(m->acfg));
    m->acfg.enabled = TRUE;
    m->acfg.accm = ~0;
    m->acfg.amru = MODEM_MRU;
    m->acfg.smru = MODEM_MTU;
    if (NgSendMsg(m->csock, path, NGM_ASYNC_COOKIE,
      NGM_ASYNC_CMD_SET_CONFIG, &m->acfg, sizeof(m->acfg)) < 0) {
	Perror("[%s] MODEM: can't config %s", l->name, path);
	close(m->csock);
	return(-1);
    }

    /* Attach async node to PPP node */
    if (!PhysGetUpperHook(l, cn.path, cn.peerhook)) {
	Log(LG_PHYS, ("[%s] MODEM: can't get upper hook", l->name));
	close(m->csock);
	return (-1);
    }
    snprintf(cn.ourhook, sizeof(cn.ourhook), NG_ASYNC_HOOK_SYNC);
    if (NgSendMsg(m->csock, path, NGM_GENERIC_COOKIE, NGM_CONNECT, 
        &cn, sizeof(cn)) < 0) {
	Perror("[%s] MODEM: can't connect \"%s\"->\"%s\" and \"%s\"->\"%s\"",
	    l->name, path, cn.ourhook, cn.path, cn.peerhook);
	close(m->csock);
	return (-1);
    }

    return(0);
}

/*
 * ModemChatSetBaudrate()
 *
 * This callback changes the actual baudrate of the serial port.
 * Should only be called once the device is already open.
 * Returns -1 on failure.
 */

static int
ModemChatSetBaudrate(void *arg, int baud)
{
    Link		const l = (Link) arg;
    ModemInfo		const m = (ModemInfo) l->info;
    struct termios	attr;

    /* Change baud rate */
    if (tcgetattr(m->fd, &attr) < 0) {
	Perror("[%s] MODEM: can't tcgetattr \"%s\"", l->name, m->device);
	return(-1);
    }
    if (cfsetspeed(&attr, (speed_t) baud) < 0) {
	Perror("[%s] MODEM: can't set speed %d", l->name, baud);
	return(-1);
    }
    if (tcsetattr(m->fd, TCSANOW, &attr) < 0) {
	Perror("[%s] MODEM: can't tcsetattr \"%s\"", l->name, m->device);
	return(-1);
    }
    return(0);
}

/*
 * ModemGetVar()
 */

char *
ModemGetVar(Link l, const char *name)
{
    ModemInfo	const m = (ModemInfo) l->info;

    return ChatGetVar(m->chat, name);
}

/*
 * ModemCheck()
 */

static void
ModemCheck(void *arg)
{
    Link	const l = (Link)arg;
    ModemInfo	const m = (ModemInfo) l->info;
    int		state;

    if (ioctl(m->fd, TIOCMGET, &state) < 0) {
	Perror("[%s] MODEM: can't ioctl(TIOCMGET) %s", l->name, m->device);
	l->state = PHYS_STATE_DOWN;
	ModemDoClose(l, FALSE);
	PhysDown(l, STR_ERROR, strerror(errno));
	return;
    }
    if ((m->watch & TIOCM_CAR) && !(state & TIOCM_CAR)) {
	Log(LG_PHYS, ("[%s] MODEM: carrier detect (CD) signal lost", l->name));
	l->state = PHYS_STATE_DOWN;
	ModemDoClose(l, FALSE);
	PhysDown(l, STR_DROPPED, STR_LOST_CD);
	return;
    }
    if ((m->watch & TIOCM_DSR) && !(state & TIOCM_DSR)) {
	Log(LG_PHYS, ("[%s] MODEM: data-set ready (DSR) signal lost", l->name));
	l->state = PHYS_STATE_DOWN;
	ModemDoClose(l, FALSE);
	PhysDown(l, STR_DROPPED, STR_LOST_DSR);
	return;
    }
    TimerStart(&m->checkTimer);
}

/*
 * ModemErrorCheck()
 *
 * Called every second to record errors to the log
 */

static void
ModemErrorCheck(void *arg)
{
    Link			const l = (Link) arg;
    ModemInfo			const m = (ModemInfo) l->info;
    char			path[NG_PATHSIZ];
    struct ng_async_stat	stats;

    /* Check for errors */
    snprintf(path, sizeof(path), "%s:%s", m->ttynode, NG_TTY_HOOK);
    if (ModemGetNgStats(l, &stats) >= 0
      && (stats.asyncBadCheckSums
      || stats.asyncRunts || stats.asyncOverflows)) {
	Log(LG_PHYS, ("[%s] NEW FRAME ERRS: FCS %u RUNT %u OVFL %u", l->name,
          stats.asyncBadCheckSums, stats.asyncRunts, stats.asyncOverflows));
	(void) NgSendMsg(m->csock, path,
	    NGM_ASYNC_COOKIE, NGM_ASYNC_CMD_CLR_STATS, NULL, 0);
    }

    /* Restart timer */
    TimerStop(&m->reportTimer);
    TimerStart(&m->reportTimer);
}

/*
 * ModemGetNgStats()
 */

static int
ModemGetNgStats(Link l, struct ng_async_stat *sp)
{
    ModemInfo		const m = (ModemInfo) l->info;
    char		path[NG_PATHSIZ];
    union {
	u_char		buf[sizeof(struct ng_mesg) + sizeof(*sp)];
	struct ng_mesg	resp;
    } u;

    /* Get stats */
    snprintf(path, sizeof(path), "%s:%s", m->ttynode, NG_TTY_HOOK);
    if (NgFuncSendQuery(path, NGM_ASYNC_COOKIE, NGM_ASYNC_CMD_GET_STATS,
      NULL, 0, &u.resp, sizeof(u), NULL) < 0) {
	Perror("[%s] MODEM: can't get stats", l->name);
	return(-1);
    }

    memcpy(sp, u.resp.data, sizeof(*sp));
    return(0);
}

/*
 * ModemSetCommand()
 */

static int
ModemSetCommand(Context ctx, int ac, char *av[], void *arg)
{
    Link	const l = ctx->lnk;
    ModemInfo	const m = (ModemInfo) l->info;

    switch ((intptr_t)arg) {
	case SET_DEVICE:
	    if (ac == 1)
		strlcpy(m->device, av[0], sizeof(m->device));
	    break;
	case SET_SPEED:
	    {
		int	k, baud;

		if (ac != 1)
		    return(-1);
		baud = atoi(*av);
		for (k = 0; gSpeedList[k] != -1 && baud != gSpeedList[k]; k++);
		if (gSpeedList[k] == -1)
		    Error("invalid speed \'%s\'", *av);
		else {
		    char	buf[32];

		    m->speed = baud;
		    snprintf(buf, sizeof(buf), "%d", m->speed);
		    ChatPresetVar(m->chat, CHAT_VAR_BAUDRATE, buf);
		}
	    }
	    break;
	case SET_CSCRIPT:
	    if (ac != 1)
		return(-1);
	    *m->connScript = 0;
	    strlcpy(m->connScript, av[0], sizeof(m->connScript));
	    break;
	case SET_ISCRIPT:
	    if (ac != 1)
		return(-1);
	    *m->idleScript = 0;
	    strlcpy(m->idleScript, av[0], sizeof(m->idleScript));
	    if (m->opened || TimerRemain(&m->startTimer) >= 0)
		break;		/* nothing needs to be done right now */
	    if (m->fd >= 0 && !*m->idleScript)
		ModemDoClose(l, FALSE);
	    else if (m->fd < 0 && *m->idleScript)
		ModemStart(l);
	    break;
	case SET_SCRIPT_VAR:
	    if (ac != 2)
		return(-1);
	    ChatPresetVar(m->chat, av[0], av[1]);
	    break;
	case SET_WATCH:
	    {
		int	bit, add;

		while (ac--) {
		    switch (**av) {
			case '+':
			    (*av)++;
			default:
			    add = TRUE;
			    break;
			case '-':
			    add = FALSE;
			    (*av)++;
			    break;
		    }
		    if (!strcasecmp(*av, "cd"))
			bit = TIOCM_CAR;
		    else if (!strcasecmp(*av, "dsr"))
			bit = TIOCM_DSR;
		    else {
			Printf("[%s] modem signal \"%s\" is unknown\r\n", l->name, *av);
			bit = 0;
		    }
		    if (add)
			m->watch |= bit;
		    else
			m->watch &= ~bit;
		    av++;
		}
	    }
	    break;
	default:
	    assert(0);
    }
    return(0);
}

/*
 * ModemOriginated()
 */

static int
ModemOriginated(Link l)
{
    ModemInfo	const m = (ModemInfo) l->info;

    return(m->originated ? LINK_ORIGINATE_LOCAL : LINK_ORIGINATE_REMOTE);
}

/*
 * ModemIsSync()
 */

static int
ModemIsSync(Link l)
{
    return (0);
}

static int
ModemSelfAddr(Link l, void *buf, size_t buf_len)
{
    ModemInfo	const m = (ModemInfo) l->info;

    strlcpy(buf, m->ttynode, buf_len);
    return(0);
}

static int
ModemIface(Link l, void *buf, size_t buf_len)
{
    ModemInfo	const m = (ModemInfo) l->info;

    strlcpy(buf, m->device, buf_len);
    return(0);
}

static int
ModemCallingNum(Link l, void *buf, size_t buf_len)
{
    ModemInfo	const m = (ModemInfo) l->info;
    char	*tmp;

    if ((tmp = ChatGetVar(m->chat, CHAT_VAR_CALLING)) == NULL) {
	((char *)buf)[0] = 0;
	return (-1);
    }
    strlcpy((char*)buf, tmp, buf_len);
    Freee(tmp);
    return(0);
}

static int
ModemCalledNum(Link l, void *buf, size_t buf_len)
{
    ModemInfo	const m = (ModemInfo) l->info;
    char	*tmp;

    if ((tmp = ChatGetVar(m->chat, CHAT_VAR_CALLED)) == NULL) {
	((char *)buf)[0] = 0;
	return (-1);
    }
    strlcpy((char*)buf, tmp, buf_len);
    Freee(tmp);
    return(0);
}

/*
 * ModemStat()
 */

void
ModemStat(Context ctx)
{
    ModemInfo			const m = (ModemInfo) ctx->lnk->info;
    struct ng_async_stat	stats;
    char			*tmp;

    Printf("Modem info:\r\n");
    Printf("\tDevice       : %s\r\n", m->device);
    Printf("\tPort speed   : %d baud\r\n", m->speed);
    Printf("\tConn. script : \"%s\"\r\n", m->connScript);
    Printf("\tIdle script  : \"%s\"\r\n", m->idleScript);
    Printf("\tPins to watch: %s%s\r\n",
	(m->watch & TIOCM_CAR) ? "CD " : "",
	(m->watch & TIOCM_DSR) ? "DSR" : "");

    Printf("Modem status:\r\n");
    if (ctx->lnk->state != PHYS_STATE_DOWN) {
	Printf("\tOpened       : %s\r\n", (m->opened?"YES":"NO"));
	Printf("\tIncoming     : %s\r\n", (m->originated?"NO":"YES"));

	/* Set modem's reported connection speed (if any) as the link bandwidth */
	if ((tmp = ChatGetVar(m->chat, CHAT_VAR_CONNECT_SPEED)) != NULL) {
	    Printf("\tConnect speed: %s baud\r\n", tmp);
	    Freee(tmp);
	}

	if ((tmp = ChatGetVar(m->chat, CHAT_VAR_CALLING)) != NULL) {
	    Printf("\tCalling      : %s\r\n", tmp);
	    Freee(tmp);
	}
	if ((tmp = ChatGetVar(m->chat, CHAT_VAR_CALLED)) != NULL) {
	    Printf("\tCalled       : %s\r\n", tmp);
	    Freee(tmp);
	}

	if (ctx->lnk->state == PHYS_STATE_UP && 
		ModemGetNgStats(ctx->lnk, &stats) >= 0) {
	    Printf("Async stats:\r\n");
	    Printf("\t       syncOctets: %8u\r\n", stats.syncOctets);
	    Printf("\t       syncFrames: %8u\r\n", stats.syncFrames);
	    Printf("\t    syncOverflows: %8u\r\n", stats.syncOverflows);
	    Printf("\t      asyncOctets: %8u\r\n", stats.asyncOctets);
	    Printf("\t      asyncFrames: %8u\r\n", stats.asyncFrames);
	    Printf("\t       asyncRunts: %8u\r\n", stats.asyncRunts);
	    Printf("\t   asyncOverflows: %8u\r\n", stats.asyncOverflows);
	    Printf("\tasyncBadCheckSums: %8u\r\n", stats.asyncBadCheckSums);
	}
    }
}

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>