
/*
 * phys.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 "msg.h"
#include "link.h"
#include "devices.h"

/*
 * The physical layer has four states: DOWN, OPENING, CLOSING, and UP.
 * Each device type must implement this set of standard methods:
 *
 *  init	Called once for each device to initialize it.
 *  open	Called in the DOWN state to initiate connection.
 *		Device should eventually call PhysUp() or PhysDown().
 *  close	Called in the OPENING or UP states.
 *		Device should eventually call PhysDown().
 *  update	Called when LCP reaches the UP state. Device should
 *		update its configuration based on LCP negotiated
 *		settings, if necessary.
 *  showstat	Display device statistics.
 *
 * The device should generate UP and DOWN events in response to OPEN
 * and CLOSE events. If the device goes down suddenly after being OPEN,
 * the close method will not be explicitly called to clean up.
 *
 * All device types must support MRU's of at least 1500.
 *
 * Each device is responsible for connecting the appropriate netgraph
 * node to the PPP node when the link comes up, and disconnecting it
 * when the link goes down (or is closed). The device should NOT send
 * any NGM_PPP_SET_CONFIG messsages to the ppp node.
 */

/*
 * DEFINITIONS
 */

  struct downmsg {
    const char	*reason;
    char	buf[256];
  };

/*
 * GLOBAL VARIABLES
 */

  const PhysType gPhysTypes[] = {
#define _WANT_DEVICE_TYPES
#include "devices.h"
    NULL,
  };

/*
 * INTERNAL FUNCTIONS
 */

  static void	PhysOpenTimeout(void *arg);
  static void	PhysMsg(int type, void *arg);

/*
 * PhysInit()
 *
 * Initialize physical layer state. Note that
 * the device type remains unspecified at this point.
 */

PhysInfo
PhysInit(void)
{
  PhysInfo	p;

  p = Malloc(MB_PHYS, sizeof(*p));
  p->state = PHYS_DOWN;
  p->msgs = MsgRegister(PhysMsg, PHYS_PRIO);
  return(p);
}

/*
 * PhysOpen()
 */

void
PhysOpen(void)
{
  MsgSend(lnk->phys->msgs, MSG_OPEN, NULL);
}

/*
 * PhysClose()
 */

void
PhysClose(void)
{
  MsgSend(lnk->phys->msgs, MSG_CLOSE, NULL);
}

/*
 * PhysUp()
 */

void
PhysUp(void)
{
  MsgSend(lnk->phys->msgs, MSG_UP, NULL);
}

/*
 * PhysDown()
 */

void
PhysDown(const char *reason, const char *details, ...)
{
  struct downmsg	*dm = Malloc(MB_UTIL, sizeof(*dm));
  va_list		args;

  dm->reason = reason;
  if (details) {
    va_start(args, details);
    vsnprintf(dm->buf, sizeof(dm->buf), details, args);
    va_end(args);
  }
  MsgSend(lnk->phys->msgs, MSG_DOWN, dm);
}

/*
 * PhysUpdate()
 */

void
PhysUpdate(void)
{
  const PhysInfo	p = lnk->phys;

  if (p->type->update != NULL)
    (*p->type->update)(p);
}

/*
 * PhysGetOriginate()
 *
 * This returns one of LINK_ORIGINATE_{UNKNOWN, LOCAL, REMOTE}
 */

int
PhysGetOriginate(void)
{
  PhysInfo	const p = lnk->phys;
  PhysType	const pt = p->type;

  return((pt && pt->originate) ? (*pt->originate)(p) : LINK_ORIGINATE_UNKNOWN);
}

/*
 * PhysSetDeviceType()
 */

void
PhysSetDeviceType(char *typename)
{
  PhysInfo	const p = lnk->phys;
  PhysType	pt;
  int		k;

  /* Make sure device type not already set */
  if (p->type) {
    Log(LG_ERR, ("[%s] device type already set to %s",
      lnk->name, p->type->name));
    return;
  }

  /* Locate type */
  for (k = 0; (pt = gPhysTypes[k]); k++) {
    if (!strcmp(pt->name, typename))
      break;
  }
  if (pt == NULL) {
    Log(LG_ERR, ("[%s] device type \"%s\" unknown", lnk->name, typename));
    return;
  }
  p->type = pt;

  /* Initialize type specific stuff */
  if ((p->type->init)(p) < 0) {
    Log(LG_ERR, ("[%s] type \"%s\" initialization failed",
      lnk->name, p->type->name));
    p->type = NULL;
    return;
  }
}

/*
 * PhysMsg()
 */

static void
PhysMsg(int type, void *arg)
{
  PhysInfo	const p = lnk->phys;
  time_t	const now = time(NULL);

  Log(LG_PHYS, ("[%s] device: %s event in state %s",
    lnk->name, MsgName(type), PhysState(p)));
  if (!p->type) {
    Log(LG_ERR, ("[%s] this link has no type set", lnk->name));
    goto done;
  }
  switch (type) {
    case MSG_OPEN:
      p->want_open = TRUE;
      if (now - p->lastClose < p->type->minReopenDelay) {
	if (TimerRemain(&p->openTimer) < 0) {
	  int	delay = p->type->minReopenDelay - (now - p->lastClose);

	  if ((random() ^ getpid() ^ time(NULL)) & 1)
		delay++;
	  else if (delay > 1)
		delay--;
	  Log(LG_PHYS, ("[%s] pausing %d seconds before open",
	    lnk->name, delay));
	  TimerStop(&p->openTimer);
	  TimerInit(&p->openTimer, "PhysOpen",
	    delay * SECONDS, PhysOpenTimeout, NULL);
	  TimerStart(&p->openTimer);
	}
	break;
      }
      switch (p->state) {
	case PHYS_DOWN:
	  if (TimerRemain(&p->openTimer) >= 0)
	    goto done;
	  (*p->type->open)(p);
	  p->state = PHYS_OPENING;
	  break;
	case PHYS_CLOSING:
	case PHYS_OPENING:
	case PHYS_UP:
	  break;
      }
      break;
    case MSG_CLOSE:
      p->want_open = FALSE;
      TimerStop(&p->openTimer);
      switch (p->state) {
	case PHYS_DOWN:
	case PHYS_CLOSING:
	  break;
	case PHYS_OPENING:
	case PHYS_UP:
	  (*p->type->close)(p);
	  p->state = PHYS_CLOSING;
	  break;
      }
      break;
    case MSG_DOWN:
      {
	struct downmsg	*const dm = (struct downmsg *) arg;

	p->lastClose = now;
	switch (p->state) {
	  case PHYS_CLOSING:
	    RecordLinkUpDown(-1);
	    /* fall through */
	  case PHYS_DOWN:
	    if (p->want_open)
	      PhysOpen();
	    break;
	  case PHYS_OPENING:
	    if (*dm->buf) {
	      SetStatus(ADLG_WAN_CONNECT_FAILURE, STR_COPY, dm->buf);
	      RecordLinkUpDownReason(lnk, 0, dm->reason, dm->buf);
	    } else {
	      SetStatus(ADLG_WAN_CONNECT_FAILURE, STR_CON_FAILED0);
	      RecordLinkUpDownReason(lnk, 0, dm->reason, NULL);
	    }
	    RecordLinkUpDown(0);
#if 0
	    SetStatus(ADLG_WAN_WAIT_FOR_DEMAND, STR_COPY, dm->buf);
#endif
	    RecordLinkUpDownReason(lnk, 1, STR_REDIAL, NULL);
	    break;
	  case PHYS_UP:
	    if (dm->reason)
	      RecordLinkUpDownReason(lnk, 0, dm->reason, dm->buf);
	    RecordLinkUpDown(-1);
	    SetStatus(ADLG_WAN_WAIT_FOR_DEMAND, STR_COPY, dm->buf);
	    RecordLinkUpDownReason(lnk, 1, STR_REDIAL, NULL);
	    break;
	}
	p->state = PHYS_DOWN;
	LinkDown(lnk);
	Freee(dm);
      }
      break;
    case MSG_UP:
      switch (p->state)
      {
	case PHYS_DOWN:
	case PHYS_CLOSING:
	  Log(LG_ERR, ("[%s] weird event in this state", lnk->name));
	  break;
	case PHYS_OPENING:
/*
	  Log(LG_PHYS, ("[%s] connection successful", lnk->name));
*/
	  RecordLinkUpDown(1);
	  LinkUp(lnk);
	  break;
	case PHYS_UP:
	  break;
      }
      p->state = PHYS_UP;
      break;
  }
done:
  Log(LG_PHYS, ("[%s] device is now in state %s",
    lnk->name, PhysState(p)));
}

/*
 * PhysOpenTimeout()
 */

static void
PhysOpenTimeout(void *arg)
{
  PhysInfo	const p = lnk->phys;

  TimerStop(&p->openTimer);
  assert(p->want_open);
  PhysOpen();
}

/*
 * PhysStat()
 */

void
PhysStat(int ac, char *av[], void *arg)
{
  PhysInfo	const p = lnk->phys;

  printf("\tType  : %s\n", p->type->name);
  printf("\tState : %s\n", PhysState(p));
  if (p->type->showstat)
    (*p->type->showstat)(p);
}

/*
 * PhysState()
 */

const char *
PhysState(PhysInfo p)
{
  switch (p->state) {
    case PHYS_DOWN:	return("DOWN");
    case PHYS_CLOSING:	return("CLOSING");
    case PHYS_OPENING:	return("OPENING");
    case PHYS_UP:	return("UP");
  }
  return("???");
}

