linux/drivers/net/wan/lapbether.c
Stephen Hemminger 0c4e85813d [NET]: Wrap netdevice hardware header creation.
Add inline for common usage of hardware header creation, and
fix bug in IPV6 mcast where the assumption about negative return is
an errno. Negative return from hard_header means not enough space
was available,(ie -N bytes).

Signed-off-by: Stephen Hemminger <shemminger@linux-foundation.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2007-10-10 16:52:50 -07:00

471 lines
10 KiB
C

/*
* "LAPB via ethernet" driver release 001
*
* This code REQUIRES 2.1.15 or higher/ NET3.038
*
* This module:
* This module 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 of the License, or (at your option) any later version.
*
* This is a "pseudo" network driver to allow LAPB over Ethernet.
*
* This driver can use any ethernet destination address, and can be
* limited to accept frames from one dedicated ethernet card only.
*
* History
* LAPBETH 001 Jonathan Naylor Cloned from bpqether.c
* 2000-10-29 Henner Eisen lapb_data_indication() return status.
* 2000-11-14 Henner Eisen dev_hold/put, NETDEV_GOING_DOWN support
*/
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/in.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/net.h>
#include <linux/inet.h>
#include <linux/netdevice.h>
#include <linux/if_arp.h>
#include <linux/skbuff.h>
#include <net/sock.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/notifier.h>
#include <linux/stat.h>
#include <linux/netfilter.h>
#include <linux/module.h>
#include <linux/lapb.h>
#include <linux/init.h>
#include <net/x25device.h>
static char bcast_addr[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
/* If this number is made larger, check that the temporary string buffer
* in lapbeth_new_device is large enough to store the probe device name.*/
#define MAXLAPBDEV 100
struct lapbethdev {
struct list_head node;
struct net_device *ethdev; /* link to ethernet device */
struct net_device *axdev; /* lapbeth device (lapb#) */
struct net_device_stats stats; /* some statistics */
};
static struct list_head lapbeth_devices = LIST_HEAD_INIT(lapbeth_devices);
/* ------------------------------------------------------------------------ */
/*
* Get the LAPB device for the ethernet device
*/
static struct lapbethdev *lapbeth_get_x25_dev(struct net_device *dev)
{
struct lapbethdev *lapbeth;
list_for_each_entry_rcu(lapbeth, &lapbeth_devices, node) {
if (lapbeth->ethdev == dev)
return lapbeth;
}
return NULL;
}
static __inline__ int dev_is_ethdev(struct net_device *dev)
{
return dev->type == ARPHRD_ETHER && strncmp(dev->name, "dummy", 5);
}
/* ------------------------------------------------------------------------ */
/*
* Receive a LAPB frame via an ethernet interface.
*/
static int lapbeth_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *ptype, struct net_device *orig_dev)
{
int len, err;
struct lapbethdev *lapbeth;
if (dev->nd_net != &init_net)
goto drop;
if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
return NET_RX_DROP;
if (!pskb_may_pull(skb, 2))
goto drop;
rcu_read_lock();
lapbeth = lapbeth_get_x25_dev(dev);
if (!lapbeth)
goto drop_unlock;
if (!netif_running(lapbeth->axdev))
goto drop_unlock;
lapbeth->stats.rx_packets++;
len = skb->data[0] + skb->data[1] * 256;
lapbeth->stats.rx_bytes += len;
skb_pull(skb, 2); /* Remove the length bytes */
skb_trim(skb, len); /* Set the length of the data */
if ((err = lapb_data_received(lapbeth->axdev, skb)) != LAPB_OK) {
printk(KERN_DEBUG "lapbether: lapb_data_received err - %d\n", err);
goto drop_unlock;
}
out:
rcu_read_unlock();
return 0;
drop_unlock:
kfree_skb(skb);
goto out;
drop:
kfree_skb(skb);
return 0;
}
static int lapbeth_data_indication(struct net_device *dev, struct sk_buff *skb)
{
unsigned char *ptr;
skb_push(skb, 1);
if (skb_cow(skb, 1))
return NET_RX_DROP;
ptr = skb->data;
*ptr = 0x00;
skb->protocol = x25_type_trans(skb, dev);
skb->dev->last_rx = jiffies;
return netif_rx(skb);
}
/*
* Send a LAPB frame via an ethernet interface
*/
static int lapbeth_xmit(struct sk_buff *skb, struct net_device *dev)
{
int err = -ENODEV;
/*
* Just to be *really* sure not to send anything if the interface
* is down, the ethernet device may have gone.
*/
if (!netif_running(dev)) {
goto drop;
}
switch (skb->data[0]) {
case 0x00:
err = 0;
break;
case 0x01:
if ((err = lapb_connect_request(dev)) != LAPB_OK)
printk(KERN_ERR "lapbeth: lapb_connect_request "
"error: %d\n", err);
goto drop_ok;
case 0x02:
if ((err = lapb_disconnect_request(dev)) != LAPB_OK)
printk(KERN_ERR "lapbeth: lapb_disconnect_request "
"err: %d\n", err);
/* Fall thru */
default:
goto drop_ok;
}
skb_pull(skb, 1);
if ((err = lapb_data_request(dev, skb)) != LAPB_OK) {
printk(KERN_ERR "lapbeth: lapb_data_request error - %d\n", err);
err = -ENOMEM;
goto drop;
}
err = 0;
out:
return err;
drop_ok:
err = 0;
drop:
kfree_skb(skb);
goto out;
}
static void lapbeth_data_transmit(struct net_device *ndev, struct sk_buff *skb)
{
struct lapbethdev *lapbeth = netdev_priv(ndev);
unsigned char *ptr;
struct net_device *dev;
int size = skb->len;
skb->protocol = htons(ETH_P_X25);
ptr = skb_push(skb, 2);
*ptr++ = size % 256;
*ptr++ = size / 256;
lapbeth->stats.tx_packets++;
lapbeth->stats.tx_bytes += size;
skb->dev = dev = lapbeth->ethdev;
dev_hard_header(skb, dev, ETH_P_DEC, bcast_addr, NULL, 0);
dev_queue_xmit(skb);
}
static void lapbeth_connected(struct net_device *dev, int reason)
{
unsigned char *ptr;
struct sk_buff *skb = dev_alloc_skb(1);
if (!skb) {
printk(KERN_ERR "lapbeth: out of memory\n");
return;
}
ptr = skb_put(skb, 1);
*ptr = 0x01;
skb->protocol = x25_type_trans(skb, dev);
skb->dev->last_rx = jiffies;
netif_rx(skb);
}
static void lapbeth_disconnected(struct net_device *dev, int reason)
{
unsigned char *ptr;
struct sk_buff *skb = dev_alloc_skb(1);
if (!skb) {
printk(KERN_ERR "lapbeth: out of memory\n");
return;
}
ptr = skb_put(skb, 1);
*ptr = 0x02;
skb->protocol = x25_type_trans(skb, dev);
skb->dev->last_rx = jiffies;
netif_rx(skb);
}
/*
* Statistics
*/
static struct net_device_stats *lapbeth_get_stats(struct net_device *dev)
{
struct lapbethdev *lapbeth = netdev_priv(dev);
return &lapbeth->stats;
}
/*
* Set AX.25 callsign
*/
static int lapbeth_set_mac_address(struct net_device *dev, void *addr)
{
struct sockaddr *sa = addr;
memcpy(dev->dev_addr, sa->sa_data, dev->addr_len);
return 0;
}
static struct lapb_register_struct lapbeth_callbacks = {
.connect_confirmation = lapbeth_connected,
.connect_indication = lapbeth_connected,
.disconnect_confirmation = lapbeth_disconnected,
.disconnect_indication = lapbeth_disconnected,
.data_indication = lapbeth_data_indication,
.data_transmit = lapbeth_data_transmit,
};
/*
* open/close a device
*/
static int lapbeth_open(struct net_device *dev)
{
int err;
if ((err = lapb_register(dev, &lapbeth_callbacks)) != LAPB_OK) {
printk(KERN_ERR "lapbeth: lapb_register error - %d\n", err);
return -ENODEV;
}
netif_start_queue(dev);
return 0;
}
static int lapbeth_close(struct net_device *dev)
{
int err;
netif_stop_queue(dev);
if ((err = lapb_unregister(dev)) != LAPB_OK)
printk(KERN_ERR "lapbeth: lapb_unregister error - %d\n", err);
return 0;
}
/* ------------------------------------------------------------------------ */
static void lapbeth_setup(struct net_device *dev)
{
dev->hard_start_xmit = lapbeth_xmit;
dev->open = lapbeth_open;
dev->stop = lapbeth_close;
dev->destructor = free_netdev;
dev->set_mac_address = lapbeth_set_mac_address;
dev->get_stats = lapbeth_get_stats;
dev->type = ARPHRD_X25;
dev->hard_header_len = 3;
dev->mtu = 1000;
dev->addr_len = 0;
}
/*
* Setup a new device.
*/
static int lapbeth_new_device(struct net_device *dev)
{
struct net_device *ndev;
struct lapbethdev *lapbeth;
int rc = -ENOMEM;
ASSERT_RTNL();
ndev = alloc_netdev(sizeof(*lapbeth), "lapb%d",
lapbeth_setup);
if (!ndev)
goto out;
lapbeth = netdev_priv(ndev);
lapbeth->axdev = ndev;
dev_hold(dev);
lapbeth->ethdev = dev;
rc = dev_alloc_name(ndev, ndev->name);
if (rc < 0)
goto fail;
rc = -EIO;
if (register_netdevice(ndev))
goto fail;
list_add_rcu(&lapbeth->node, &lapbeth_devices);
rc = 0;
out:
return rc;
fail:
dev_put(dev);
free_netdev(ndev);
kfree(lapbeth);
goto out;
}
/*
* Free a lapb network device.
*/
static void lapbeth_free_device(struct lapbethdev *lapbeth)
{
dev_put(lapbeth->ethdev);
list_del_rcu(&lapbeth->node);
unregister_netdevice(lapbeth->axdev);
}
/*
* Handle device status changes.
*
* Called from notifier with RTNL held.
*/
static int lapbeth_device_event(struct notifier_block *this,
unsigned long event, void *ptr)
{
struct lapbethdev *lapbeth;
struct net_device *dev = ptr;
if (dev->nd_net != &init_net)
return NOTIFY_DONE;
if (!dev_is_ethdev(dev))
return NOTIFY_DONE;
switch (event) {
case NETDEV_UP:
/* New ethernet device -> new LAPB interface */
if (lapbeth_get_x25_dev(dev) == NULL)
lapbeth_new_device(dev);
break;
case NETDEV_DOWN:
/* ethernet device closed -> close LAPB interface */
lapbeth = lapbeth_get_x25_dev(dev);
if (lapbeth)
dev_close(lapbeth->axdev);
break;
case NETDEV_UNREGISTER:
/* ethernet device disappears -> remove LAPB interface */
lapbeth = lapbeth_get_x25_dev(dev);
if (lapbeth)
lapbeth_free_device(lapbeth);
break;
}
return NOTIFY_DONE;
}
/* ------------------------------------------------------------------------ */
static struct packet_type lapbeth_packet_type = {
.type = __constant_htons(ETH_P_DEC),
.func = lapbeth_rcv,
};
static struct notifier_block lapbeth_dev_notifier = {
.notifier_call = lapbeth_device_event,
};
static char banner[] __initdata = KERN_INFO "LAPB Ethernet driver version 0.02\n";
static int __init lapbeth_init_driver(void)
{
dev_add_pack(&lapbeth_packet_type);
register_netdevice_notifier(&lapbeth_dev_notifier);
printk(banner);
return 0;
}
module_init(lapbeth_init_driver);
static void __exit lapbeth_cleanup_driver(void)
{
struct lapbethdev *lapbeth;
struct list_head *entry, *tmp;
dev_remove_pack(&lapbeth_packet_type);
unregister_netdevice_notifier(&lapbeth_dev_notifier);
rtnl_lock();
list_for_each_safe(entry, tmp, &lapbeth_devices) {
lapbeth = list_entry(entry, struct lapbethdev, node);
unregister_netdevice(lapbeth->axdev);
}
rtnl_unlock();
}
module_exit(lapbeth_cleanup_driver);
MODULE_AUTHOR("Jonathan Naylor <g4klx@g4klx.demon.co.uk>");
MODULE_DESCRIPTION("The unofficial LAPB over Ethernet driver");
MODULE_LICENSE("GPL");