/*
 *
 * Copyright 1999 Digi International (www.digi.com)
 *     James Puzzo <jamesp at digi dot com>
 *
 * 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, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE.  See the GNU General Public License for more details.
 *
 */

/*
 *
 *  Filename:
 *
 *     dgrp_common.c
 *
 *  Description:
 *
 *     Definitions of global variables and functions which are either
 *     shared by the tty, mon, and net drivers; or which cross them
 *     functionally (like the poller).
 *
 *  Author:
 *
 *     James A. Puzzo
 *
 */

#include <linux/errno.h>
#include <linux/tty.h>
#include <linux/sched.h>
#include <linux/cred.h>

#include "dgrp_common.h"

/**
 * dgrp_carrier -- check for carrier change state and act
 * @ch: struct ch_struct *
 */
void dgrp_carrier(struct ch_struct *ch)
{
	struct nd_struct *nd;

	int virt_carrier = 0;
	int phys_carrier = 0;

	/* fix case when the tty has already closed. */

	if (!ch)
		return;
	nd  = ch->ch_nd;
	if (!nd)
		return;

	/*
	 *  If we are currently waiting to determine the status of the port,
	 *  we don't yet know the state of the modem lines.  As a result,
	 *  we ignore state changes when we are waiting for the modem lines
	 *  to be established.  We know, as a result of code in dgrp_net_ops,
	 *  that we will be called again immediately following the reception
	 *  of the status message with the true modem status flags in it.
	 */
	if (ch->ch_expect & RR_STATUS)
		return;

	/*
	 * If CH_HANGUP is set, we gotta keep trying to get all the processes
	 * that have the port open to close the port.
	 * So lets just keep sending a hangup every time we get here.
	 */
	if ((ch->ch_flag & CH_HANGUP) &&
	    (ch->ch_tun.un_open_count > 0))
		tty_hangup(ch->ch_tun.un_tty);

	/*
	 *  Compute the effective state of both the physical and virtual
	 *  senses of carrier.
	 */

	if (ch->ch_s_mlast & DM_CD)
		phys_carrier = 1;

	if ((ch->ch_s_mlast & DM_CD) ||
	    (ch->ch_digi.digi_flags & DIGI_FORCEDCD) ||
	    (ch->ch_flag & CH_CLOCAL))
		virt_carrier = 1;

	/*
	 *  Test for a VIRTUAL carrier transition to HIGH.
	 *
	 *  The CH_HANGUP condition is intended to prevent any action
	 *  except for close.  As a result, we ignore positive carrier
	 *  transitions during CH_HANGUP.
	 */
	if (((ch->ch_flag & CH_HANGUP)  == 0) &&
	    ((ch->ch_flag & CH_VIRT_CD) == 0) &&
	    (virt_carrier == 1)) {
		/*
		 * When carrier rises, wake any threads waiting
		 * for carrier in the open routine.
		 */
		nd->nd_tx_work = 1;

		if (waitqueue_active(&ch->ch_flag_wait))
			wake_up_interruptible(&ch->ch_flag_wait);
	}

	/*
	 *  Test for a PHYSICAL transition to low, so long as we aren't
	 *  currently ignoring physical transitions (which is what "virtual
	 *  carrier" indicates).
	 *
	 *  The transition of the virtual carrier to low really doesn't
	 *  matter... it really only means "ignore carrier state", not
	 *  "make pretend that carrier is there".
	 */
	if ((virt_carrier == 0) &&
	    ((ch->ch_flag & CH_PHYS_CD) != 0) &&
	    (phys_carrier == 0)) {
		/*
		 * When carrier drops:
		 *
		 *   Do a Hard Hangup if that is called for.
		 *
		 *   Drop carrier on all open units.
		 *
		 *   Flush queues, waking up any task waiting in the
		 *   line discipline.
		 *
		 *   Send a hangup to the control terminal.
		 *
		 *   Enable all select calls.
		 */

		nd->nd_tx_work = 1;

		ch->ch_flag &= ~(CH_LOW | CH_EMPTY | CH_DRAIN | CH_INPUT);

		if (waitqueue_active(&ch->ch_flag_wait))
			wake_up_interruptible(&ch->ch_flag_wait);

		if (ch->ch_tun.un_open_count > 0)
			tty_hangup(ch->ch_tun.un_tty);

		if (ch->ch_pun.un_open_count > 0)
			tty_hangup(ch->ch_pun.un_tty);
	}

	/*
	 *  Make sure that our cached values reflect the current reality.
	 */
	if (virt_carrier == 1)
		ch->ch_flag |= CH_VIRT_CD;
	else
		ch->ch_flag &= ~CH_VIRT_CD;

	if (phys_carrier == 1)
		ch->ch_flag |= CH_PHYS_CD;
	else
		ch->ch_flag &= ~CH_PHYS_CD;

}