| From: |
| Dan Eble <dane@aiinet.com> |
| To: |
| linux-net <linux-net@vger.kernel.org> |
| Subject: |
| [PATCH] Generic PPP for Generic HDLC [1/3] |
| Date: |
| Tue, 10 Jun 2003 11:38:03 -0400 (EDT) |
This is part 1 of a 3-message set that adds generic PPP support to the
generic HDLC layer. These changes were made in PPC Linux 2.4.21-pre4.
These 3 parts DO NOT include changes to the PPP daemon to enable it to
work with WAN HDLC devices. I shall post those changes to the
linux-ppp email list, under the subject "[PATCH] WAN HDLC Support".
I welcome comments and suggestions (polite or not). Eventually, this
will be used in the real world ;) and I would like it to work
properly.
______________________________________________________________________
Part 1 is a new drivers/net/wan/hdlc_ppp.c. I have included it
verbatim because I think it is easier to read than a patch (since
almost all of it is different from the previous version).
Part 2 patches the following:
* In include/linux/hdlc.h, the PPP state variables have changed
in accordance with the switch from syncppp.c to ppp_generic.
* In drivers/net/wan/Makefile, syncppp.c is no longer required
when CONFIG_HDLC_PPP is defined.
* In include/linux/hdlc/ioctl.h, there is now a structure
(ppp_proto) for retrieving PPP protocol information via
the SIOCWANDEV(IF_GET_PROTO) ioctl.
* In include/linux/if.h, ppp_proto is part of the union used in
SIOCWANDEV.
Part 3 patches the "sethdlc" program to use the new ppp_proto
structure.
--
Dan Eble <dane@aiinet.com> _____ .
| _ |/|
Applied Innovation Inc. | |_| | |
http://www.aiinet.com/ |__/|_|_|
/*
* 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>
/**************************************************************************
* Prototypes
*************************************************************************/
static int hdlc_ppp_register(hdlc_device *hdlc);
static void hdlc_ppp_unregister(hdlc_device *hdlc);
static int hdlc_ppp_dont_change_mtu(struct net_device *dev, int new_mtu);
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;
int err;
/**
* Save the old change_mtu and use a new one that prevents the
* MTU from being changed while the PPP channel is registered.
* Then, call the old function to set an MTU adequate for PPP.
*/
hdlc->state.ppp.old_change_mtu = dev->change_mtu;
dev->change_mtu = hdlc_ppp_dont_change_mtu;
old_mtu = dev->mtu;
err = hdlc->state.ppp.old_change_mtu(dev, PPP_HDRLEN + PPP_MTU);
if (err)
{
printk(KERN_NOTICE
"%s: Changing MTU to %d for PPP failed (%d).\n"
"%s: Using current MTU, %d.\n",
dev->name, PPP_HDRLEN + PPP_MTU, err,
dev->name, dev->mtu);
}
/* reset the PPP state */
memset(&hdlc->state.ppp, sizeof(hdlc->state.ppp), 0);
chan->private = hdlc;
chan->ops = &hdlc_genppp_ops;
chan->mtu = dev->mtu - PPP_HDRLEN;
chan->hdrlen = 2; /* address & control bytes */
/* 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_RAWHDLC;
dev->hard_header_len = 0;
dev->flags = IFF_POINTOPOINT | IFF_NOARP;
dev->addr_len = 0;
hdlc->state.ppp.settings.channel =
ppp_channel_index(hdlc_to_chan(hdlc));
goto Success;
}
/* restore old MTU */
hdlc->state.ppp.old_change_mtu(dev, old_mtu);
dev->change_mtu = hdlc->state.ppp.old_change_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 net_device *const dev = hdlc_to_dev(hdlc);
struct ppp_channel *const chan = hdlc_to_chan(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);
dev->change_mtu = hdlc->state.ppp.old_change_mtu;
hdlc->state.ppp.settings.channel = -1;
}
/**
* The channel should initialize the `mtu' and `hdrlen' fields before
* calling ppp_register_channel() and not change them until after
* ppp_unregister_channel() returns.
*/
static int hdlc_ppp_dont_change_mtu(struct net_device *dev, int new_mtu)
{
return -EBUSY;
}
/**
* 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_HDRLEN, (int *) arg))
break;
err = 0;
break;
case PPPIOCSMRU:
if (get_user(val, (int *) arg))
break;
if (val > dev->mtu - PPP_HDRLEN)
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;
}
-
To unsubscribe from this list: send the line "unsubscribe linux-net" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html