/*	$OpenBSD: llc_output.c,v 1.7 2004/01/03 14:08:53 espie Exp $	*/
/*	$NetBSD: llc_output.c,v 1.3 1996/02/13 22:04:47 christos Exp $	*/

/* 
 * Copyright (C) Dirk Husemann, Computer Science Department IV, 
 * 		 University of Erlangen-Nuremberg, Germany, 1990, 1991, 1992
 * Copyright (c) 1992, 1993
 *	The Regents of the University of California.  All rights reserved.
 * 
 * This code is derived from software contributed to Berkeley by
 * Dirk Husemann and the Computer Science Department (IV) of
 * the University of Erlangen-Nuremberg, Germany.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	@(#)llc_output.c	8.1 (Berkeley) 6/10/93
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/domain.h>
#include <sys/socket.h>
#include <sys/protosw.h>
#include <sys/errno.h>
#include <sys/time.h>
#include <sys/kernel.h>

#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_llc.h>
#include <net/route.h>

#include <netccitt/dll.h>
#include <netccitt/llc_var.h>

#include <sys/stdarg.h>

/*
 * llc_output() --- called by an upper layer (network layer) entity whenever
 *                  there is an INFO frame to be transmitted. We enqueue the
 *                  info frame and call llc_start() to do the actual sending.
 */

int
llc_output(struct mbuf *m, ...)
{
	struct llc_linkcb *linkp;
	int    i = splimp();
	va_list ap;

	va_start(ap, m);
	linkp = va_arg(ap, struct llc_linkcb *);
	va_end(ap);

	LLC_ENQUEUE(linkp, m);
	llc_start(linkp);
	splx(i);
	return 0;
}


/*
 * llc_start() --- We try to subsequently dequeue all the frames available and
 *                 send them out.
 */
void
llc_start(linkp)
	struct llc_linkcb *linkp;
{
	struct mbuf *m;

	while ((LLC_STATEEQ(linkp,NORMAL) || LLC_STATEEQ(linkp,BUSY) ||
		LLC_STATEEQ(linkp,REJECT)) &&
	       (linkp->llcl_slotsfree > 0) &&
	       (LLC_GETFLAG(linkp,REMOTE_BUSY) == 0)) {
		LLC_DEQUEUE(linkp,m);
		if (m == NULL)
			break;
		LLC_SETFRAME(linkp, m);
		(void) llc_statehandler(linkp, NULL, NL_DATA_REQUEST, 0, 0);
	}
}


/*
 * llc_send() --- Handles single frames. If dealing with INFO frames we need to
 *                prepend the LLC header, otherwise we just allocate an mbuf.
 *                In both cases the actual send is done by llc_rawsend().
 */
void
llc_send(linkp, frame_kind, cmdrsp, pollfinal)
	struct llc_linkcb *linkp;
	int frame_kind;
	int cmdrsp;
	int pollfinal;
{
	struct mbuf *m = (struct mbuf *) 0;
	struct llc *frame;

	if (frame_kind == LLCFT_INFO)
		m = linkp->llcl_output_buffers[llc_seq2slot(linkp,
							    linkp->llcl_vs)];
	LLC_GETHDR(frame, m);

	/* pass it on to llc_rawsend() */
	llc_rawsend(linkp, m, frame, frame_kind, linkp->llcl_vs, cmdrsp, pollfinal);

	if (frame_kind == LLCFT_INFO)
		LLC_INC(linkp->llcl_vs);
}

/*
 * llc_resend() --- llc_resend() retransmits all unacknowledged INFO frames.
 */
void
llc_resend(linkp, cmdrsp, pollfinal)
	struct llc_linkcb *linkp;
	int cmdrsp;
	int pollfinal;
{
	struct llc *frame;
	struct mbuf *m;
	int   slot;

	if (linkp->llcl_slotsfree < linkp->llcl_window)
		/* assert lock between nr_received & V(S) */
		if (linkp->llcl_nr_received != linkp->llcl_vs)
			panic("llc: V(S) != N(R) received");

	for (slot = llc_seq2slot(linkp, linkp->llcl_vs);
	     slot != linkp->llcl_freeslot;
	     LLC_INC(linkp->llcl_vs),
	     slot = llc_seq2slot(linkp, linkp->llcl_vs)) {
		m = linkp->llcl_output_buffers[slot];
		LLC_GETHDR(frame, m);
		llc_rawsend(linkp, m, frame, LLCFT_INFO, linkp->llcl_vs,
			    cmdrsp, pollfinal);
		pollfinal = 0;
	}
}

/*
 * llc_rawsend() --- constructs an LLC frame and sends it out via the
 *                   associated interface of the link control block.
 *
 * We need to make sure that outgoing frames have the correct length,
 * in particular the 4 byte ones (RR, RNR, REJ) as LLC_GETHDR() will
 * set the mbuf len to 3 as default len for non INFO frames ...
 *
 * Frame kind             Length (w/o MAC header, {D,S}SAP incl.)
 * --------------------------------------------------------------
 * DISC, SABME, UA, DM    3 bytes  ({D,S}SAP + CONTROL)
 * RR, RNR, REJ           4 bytes  ({D,S}SAP + CONTROL0 + CONTROL1)
 * XID                    6 bytes  ({D,S}SAP + CONTROL0 + FI,CLASS,WINDOW)
 * FRMR                   7 bytes  ({D,S}SAP + CONTROL0 + REJ CONTROL,V(S),V(R),CAUSE)
 * INFO                   4 -- MTU
 * UI, TEST               3 -- MTU
 *
 */
#define LLC_SETLEN(m, l) (m)->m_pkthdr.len = (m)->m_len = (l)

void
llc_rawsend(linkp, m, frame, frame_kind, vs, cmdrsp, pollfinal)
	struct llc_linkcb *linkp;
	struct mbuf *m;
	struct llc *frame;
	int frame_kind;
	int vs;
	int cmdrsp;
	int pollfinal;
{
	short  adjust = LLC_UFRAMELEN;
	struct ifnet   *ifp;

	switch (frame_kind) {
		/* supervisory and information frames */
	case LLCFT_INFO:
		frame->llc_control = LLC_INFO;
		LLCSBITS(frame->llc_control, i_ns, vs);
		LLCSBITS(frame->llc_control_ext, i_nr, linkp->llcl_vr);
		adjust = LLC_ISFRAMELEN;
		break;
	case LLCFT_RR:
		frame->llc_control = LLC_RR;
		LLC_SETLEN(m, LLC_ISFRAMELEN);
		LLCSBITS(frame->llc_control_ext, s_nr, linkp->llcl_vr);
		adjust = LLC_ISFRAMELEN;
		break;
	case LLCFT_RNR:
		frame->llc_control = LLC_RNR;
		LLC_SETLEN(m, LLC_ISFRAMELEN);
		LLCSBITS(frame->llc_control_ext, s_nr, linkp->llcl_vr);
		adjust = LLC_ISFRAMELEN;
		break;
	case LLCFT_REJ:
		frame->llc_control = LLC_REJ;
		LLC_SETLEN(m, LLC_ISFRAMELEN);
		LLCSBITS(frame->llc_control_ext, s_nr, linkp->llcl_vr);
		adjust = LLC_ISFRAMELEN;
		break;
		/* unnumbered frames */
	case LLCFT_DM:
		frame->llc_control = LLC_DM;
		break;
	case LLCFT_SABME:
		frame->llc_control = LLC_SABME;
		break;
	case LLCFT_DISC:
		frame->llc_control = LLC_DISC;
		break;
	case LLCFT_UA:
		frame->llc_control = LLC_UA;
		break;
	case LLCFT_UI:
		frame->llc_control = LLC_UI;
		break;
	case LLCFT_FRMR:
		frame->llc_control = LLC_FRMR;
		/* get more space --- FRMR frame are longer then usual */
		LLC_SETLEN(m, LLC_FRMRLEN);
		bcopy((caddr_t) & linkp->llcl_frmrinfo,
		      (caddr_t) & frame->llc_frmrinfo,
		      sizeof(struct frmrinfo));
		break;
	default:
		/*
		 * We don't send {XID, TEST} frames
		 */
		if (m)
			m_freem(m);
		return;
	}

	/*
	 * Fill in DSAP/SSAP
	 */
	frame->llc_dsap = frame->llc_ssap = LLSAPADDR(&linkp->llcl_addr);
	frame->llc_ssap |= cmdrsp;

	/*
	 * Check for delayed action pending. ISO 8802-2, 7.9.2 (5)
	 * and ISO 8802-2, 7.9.2.3 (32), (34), (36) pertain to this
	 * piece of code --- hopefully we got it right here (i.e.
	 * in the spirit of (32), (34), and (36) ...
	 */
	switch (frame_kind) {
	case LLCFT_RR:
	case LLCFT_RNR:
	case LLCFT_REJ:
	case LLCFT_INFO:
		switch (LLC_GETFLAG(linkp,DACTION)) {
		case LLC_DACKCMD:
		case LLC_DACKRSP:
			LLC_STOPTIMER(linkp,DACTION);
			break;
		case LLC_DACKCMDPOLL:
			if (cmdrsp == LLC_CMD) {
				pollfinal = 1;
				LLC_STOPTIMER(linkp,DACTION);
			}
			break;
		case LLC_DACKRSPFINAL:
			if (cmdrsp == LLC_RSP) {
				pollfinal = 1;
				LLC_STOPTIMER(linkp,DACTION);
			}
			break;
		}
		break;
	}

	if (adjust == LLC_UFRAMELEN)
		LLCSBITS(frame->llc_control,u_pf,pollfinal);
	else
		LLCSBITS(frame->llc_control_ext,s_pf,pollfinal);

	/*
	 * Get interface to send frame onto
	 */
	ifp = linkp->llcl_if;
	if (frame_kind == LLCFT_INFO) {
		/*
		 * send out a copy of the frame, retain the original
		 */
		(*ifp->if_output) (ifp, m_copy(m, 0, (int) M_COPYALL),
				   rt_key(linkp->llcl_nlrt),
				   linkp->llcl_nlrt);
		/*
		 * Account for the LLC header and let it ``disappear''
		 * as the raw info frame payload is what we hold in
		 * the output_buffers of the link.
		 */
		m_adj(m, LLC_ISFRAMELEN);
	} else
		(*ifp->if_output) (ifp, m,
				   rt_key(linkp->llcl_nlrt),
				   linkp->llcl_nlrt);
}
