/*
 * Generic HDLC support routines for Linux
 * Point-to-point protocol support
 *
 * Copyright (C) 1999 - 2003 Krzysztof Halasa <khc@pm.waw.pl>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License
 * as published by the Free Software Foundation.
 */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/errno.h>
#include <linux/if_arp.h>
#include <linux/init.h>
#include <linux/skbuff.h>
#include <linux/pkt_sched.h>
#include <linux/inetdevice.h>
#include <linux/lapb.h>
#include <linux/rtnetlink.h>
#include <linux/hdlc.h>
#include <linux/ppp_defs.h>
#include <linux/if_ppp.h>
#include <linux/ppp_channel.h>

/**************************************************************************
 * Constants and Structures
 *************************************************************************/

/* The PPP header includes the HDLC address & control bytes,
 * so do not count them in MTU adjustments.
 */
#define PPP_OVERHEAD	(PPP_HDRLEN - 2)

/**************************************************************************
 * Prototypes
 *************************************************************************/

static int hdlc_ppp_register(hdlc_device *hdlc);
static void hdlc_ppp_unregister(hdlc_device *hdlc);
static void hdlc_ppp_netif_rx(struct sk_buff *skb);
static int hdlc_genppp_start_xmit(struct ppp_channel *, struct sk_buff *);
static int hdlc_genppp_ioctl(struct ppp_channel*, unsigned int, unsigned long);

/**************************************************************************
 * Variables
 *************************************************************************/

static struct ppp_channel_ops hdlc_genppp_ops =
{
	.start_xmit =	hdlc_genppp_start_xmit,
	.ioctl =	hdlc_genppp_ioctl
};

/**************************************************************************
 * Inline Functions
 *************************************************************************/

/** Get the PPP channel owned by an HDLC device. */
static __inline__ struct ppp_channel* hdlc_to_chan(hdlc_device *hdlc)
{
	return (struct ppp_channel*)&hdlc->state.ppp.chan;
}

/** Get an HDLC device from its PPP channel. */
static __inline__ hdlc_device* chan_to_hdlc(struct ppp_channel *chan)
{
	return (hdlc_device*)chan->private;
}

/** Check UP, RUNNING, and carrier all at once. */
static __inline__ int netif_good_to_go(struct net_device *dev)
{
	return (dev->flags & IFF_UP) &&
		netif_running(dev) &&
		netif_carrier_ok(dev);
}

/**************************************************************************
 * Functions
 *************************************************************************/

/**
 * Initialize and register a PPP channel with the generic PPP layer.
 *
 * hdlc_ppp_register() is called from process context while the
 * interface is down.
 */
static int hdlc_ppp_register(hdlc_device *hdlc)
{
	struct net_device *const dev = hdlc_to_dev(hdlc);
	struct ppp_channel *const chan = hdlc_to_chan(hdlc);
	int old_mtu, new_mtu;
	int err;

	/*
	 * Save the old MTU and use one more adequate for PPP.
	 */
	old_mtu = dev->mtu;

	/* Try an MTU for bridging Ethernet frames. */
	new_mtu = PPP_OVERHEAD +BCP_802_3_HDRLEN + ETH_FRAME_LEN + ETH_FCS_LEN;
	if (dev->mtu < new_mtu)
		dev->change_mtu(dev, new_mtu);

	/* If that MTU didn't work, try it without room for the Ethernet FCS,
	 * which is optional for BCP encapsulation. */
	new_mtu -= ETH_FCS_LEN;
	if (dev->mtu < new_mtu)
		dev->change_mtu(dev, new_mtu);

	/* If *that* didn't work, try the default PPP MTU. */
	new_mtu = PPP_OVERHEAD + PPP_MTU;
	if (dev->mtu < new_mtu)
		dev->change_mtu(dev, new_mtu);

	/* MTU should not be changed while PPP mode is in effect. */
	hdlc->mtu_locked = 1;

	/* reset the PPP state */
	memset(&hdlc->state.ppp, 0, sizeof(hdlc->state.ppp));
	chan->private = hdlc;
	chan->ops = &hdlc_genppp_ops;

	/* The channel MTU is the underlying HDLC MTU reduced by the
	 * overhead PPP requires.
	 */
	chan->mtu = dev->mtu - PPP_OVERHEAD;

	/* chan->hdrlen is the headroom the generic PPP layer will
	 * reserve on buffers it sends to this driver.  We need enough
	 * for the HDLC address and control bytes. */
	chan->hdrlen = 2;

 	/* The ppp_channel object must exist from the time that
	 * ppp_register_channel() is called until after the call to
	 * ppp_unregister_channel() returns.
	 */
	err = ppp_register_channel(chan);
	if (!err)
	{
		hdlc->open = NULL;
		hdlc->stop = NULL;
		hdlc->proto_detach = hdlc_ppp_unregister;
		hdlc->netif_rx = hdlc_ppp_netif_rx;
		hdlc->type_trans = NULL;	/* force use of netif_rx() */
		hdlc->proto = IF_PROTO_PPP;

		dev->hard_start_xmit = hdlc->xmit;
		dev->hard_header = NULL;
		dev->type = ARPHRD_PPP;
		dev->hard_header_len = 2;
		dev->addr_len = 1;
		dev->broadcast[0] = PPP_ALLSTATIONS;
		dev->dev_addr[0] = 0;		/* not important for PPP */

		hdlc->state.ppp.settings.channel =
			ppp_channel_index(hdlc_to_chan(hdlc));

		goto Success;
	}

	/* restore old MTU */
	hdlc->mtu_locked = 0;
	if (dev->mtu != old_mtu)
		dev->change_mtu(dev, old_mtu);

 Success:
	return err;
}



/**
 * Close the channel to the generic PPP layer.
 *
 * hdlc_ppp_unregister() is called from process context while the
 * interface is down.
 */
static void hdlc_ppp_unregister(hdlc_device *hdlc)
{
	struct ppp_channel *const chan = hdlc_to_chan(hdlc);
	struct net_device *const dev = hdlc_to_dev(hdlc);

	/* No thread may be in a call to any of ppp_input(),
	 * ppp_input_error(), ppp_output_wakeup(), ppp_channel_index()
	 * or ppp_unit_number() for a channel at the time that
	 * ppp_unregister_channel() is called for that channel.
	 */
	/* By the time a call to ppp_unregister_channel() returns, no
	 * thread will be executing in a call from the generic layer
	 * to that channel's start_xmit() or ioctl() function, and the
	 * generic layer will not call either of those functions
	 * subsequently.
	 */
	ppp_unregister_channel(chan);

	hdlc->mtu_locked = 0;
	hdlc->state.ppp.settings.channel = -1;
	dev->hard_header_len = 16;
}



/**
 * Receive a buffer from the hardware, strip the PPP header, and pass
 * the rest to the generic PPP layer.
 */
static void hdlc_ppp_netif_rx(struct sk_buff *skb)
{
	struct ppp_channel *const chan = hdlc_to_chan(dev_to_hdlc(skb->dev));
	unsigned char *p;

	/* strip address/control field if present */
	p = skb->data;
	if (p[0] == PPP_ALLSTATIONS && p[1] == PPP_UI) {
		/* chop off address/control */
		if (skb->len < 3)
			goto err;
		p = skb_pull(skb, 2);
	}

	/* decompress protocol field if compressed */
	if (p[0] & 1) {
		/* protocol is compressed */
		skb_push(skb, 1)[0] = 0;
	} else if (skb->len < 2)
		goto err;

	/* pass to generic layer */
	ppp_input(chan, skb);
	return;

 err:
	kfree_skb(skb);
	ppp_input_error(chan, 0);
}



/**
 * Send a packet (or multilink fragment) on this channel.
 * Returns 1 if it was accepted, 0 to queue it for later.
 *
 * The generic layer will not call the start_xmit() function for a
 * channel while any thread is already executing in that function for
 * that channel.
 *
 * The generic layer may call the channel start_xmit() function at
 * softirq/BH level but will not call it at interrupt level.  Thus the
 * start_xmit() function may not block.
 */
static int hdlc_genppp_start_xmit(struct ppp_channel *chan,
				  struct sk_buff *skb)
{
	hdlc_device *const hdlc = chan_to_hdlc(chan);
	struct net_device *const dev = hdlc_to_dev(hdlc);
	int proto;
	unsigned char *data;
	int islcp;

	if (!netif_good_to_go(dev)) {
		/** @todo Instead, return 0 to make generic layer
		 * queue the packet.  That will require calling
		 * ppp_output_wakeup() at an appropriate time. */
		kfree_skb(skb);
		++hdlc->stats.tx_dropped;
		return 1;
	}

	data  = skb->data;
	proto = (data[0] << 8) + data[1];

	/* LCP packets with codes between 1 (configure-request)
	 * and 7 (code-reject) must be sent as though no options
	 * have been negotiated.
	 */
	islcp = proto == PPP_LCP && 1 <= data[2] && data[2] <= 7;

	/* compress protocol field if option enabled */
	if (data[0] == 0 && (hdlc->state.ppp.flags & SC_COMP_PROT) && !islcp)
		skb_pull(skb,1);

	/* prepend address/control fields if necessary */
	if ((hdlc->state.ppp.flags & SC_COMP_AC) == 0 || islcp) {
		if (skb_headroom(skb) < 2) {
			struct sk_buff *npkt = dev_alloc_skb(skb->len + 2);
			if (npkt == NULL) {
				kfree_skb(skb);
				++hdlc->stats.tx_dropped;
				return 1;
			}
			skb_reserve(npkt,2);
			memcpy(skb_put(npkt,skb->len), skb->data, skb->len);
			kfree_skb(skb);
			skb = npkt;
		}
		skb_push(skb,2);
		skb->data[0] = PPP_ALLSTATIONS;
		skb->data[1] = PPP_UI;
	}

	skb->dev = dev;
	skb->nh.raw = skb->data;

	dev_queue_xmit(skb);
	return 1;
}



/**
 * Handle an ioctl call that has come in via /dev/ppp.
 *
 * The generic layer will only call the channel ioctl() function in
 * process context.
 *
 * The generic layer will not call the ioctl() function for a channel
 * while any thread is already executing in that function for that
 * channel.
 */
static int hdlc_genppp_ioctl(struct ppp_channel *chan,
			     unsigned int cmd, unsigned long arg)
{
	hdlc_device *const hdlc = chan_to_hdlc(chan);
	struct net_device *const dev = hdlc_to_dev(hdlc);
	int val;
	int err;

	err = -EFAULT;
	switch (cmd) {
	case PPPIOCGMRU:
		if (put_user(dev->mtu - PPP_OVERHEAD, (int *) arg))
			break;
		err = 0;
		break;

	case PPPIOCSMRU:
		if (get_user(val, (int *) arg))
			break;

		if (val > dev->mtu - PPP_OVERHEAD)
			err = -EINVAL;
		else
			err = 0;
		break;

	case PPPIOCSFLAGS:
		if (get_user(val, (int *) arg))
			break;
		val &= SC_MASK;	/* keep the bits that are allowed to be set */
		hdlc->state.ppp.flags &= ~SC_MASK;
		hdlc->state.ppp.flags |= val;
		err = 0;
		break;

	default:
		err = -ENOTTY;
	}
	return err;
}



int hdlc_ppp_ioctl(hdlc_device *hdlc, struct ifreq *ifr)
{
	struct net_device *dev = hdlc_to_dev(hdlc);
	int err;

	switch (ifr->ifr_settings.type) {
	case IF_GET_PROTO:
		ifr->ifr_settings.type = IF_PROTO_PPP;
		if (ifr->ifr_settings.size < sizeof(ppp_proto)) {
			ifr->ifr_settings.size = sizeof(ppp_proto);
			return -ENOBUFS;
		}
		if (copy_to_user(ifr->ifr_settings.ifs_ifsu.ppp,
				 &hdlc->state.ppp.settings, sizeof(ppp_proto)))
			return -EFAULT;
		return 0;

	case IF_PROTO_PPP:
		if(!capable(CAP_NET_ADMIN))
			return -EPERM;

		if(dev->flags & IFF_UP)
			return -EBUSY;

		/* no settable parameters */

		err = hdlc->attach(hdlc, ENCODING_NRZ,PARITY_CRC16_PR1_CCITT);
		if (!err) {
			hdlc_proto_detach(hdlc);
			err = hdlc_ppp_register(hdlc);
		}

		return err;
	}

	return -EINVAL;
}
