linux/drivers/net/wan/dlci.c
Thomas Gleixner 2874c5fd28 treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 152
Based on 1 normalized pattern(s):

  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 of the license or at
  your option any later version

extracted by the scancode license scanner the SPDX license identifier

  GPL-2.0-or-later

has been chosen to replace the boilerplate/reference in 3029 file(s).

Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Allison Randal <allison@lohutok.net>
Cc: linux-spdx@vger.kernel.org
Link: https://lkml.kernel.org/r/20190527070032.746973796@linutronix.de
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2019-05-30 11:26:32 -07:00

543 lines
11 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* DLCI Implementation of Frame Relay protocol for Linux, according to
* RFC 1490. This generic device provides en/decapsulation for an
* underlying hardware driver. Routes & IPs are assigned to these
* interfaces. Requires 'dlcicfg' program to create usable
* interfaces, the initial one, 'dlci' is for IOCTL use only.
*
* Version: @(#)dlci.c 0.35 4 Jan 1997
*
* Author: Mike McLagan <mike.mclagan@linux.org>
*
* Changes:
*
* 0.15 Mike Mclagan Packet freeing, bug in kmalloc call
* DLCI_RET handling
* 0.20 Mike McLagan More conservative on which packets
* are returned for retry and which are
* are dropped. If DLCI_RET_DROP is
* returned from the FRAD, the packet is
* sent back to Linux for re-transmission
* 0.25 Mike McLagan Converted to use SIOC IOCTL calls
* 0.30 Jim Freeman Fixed to allow IPX traffic
* 0.35 Michael Elizabeth Fixed incorrect memcpy_fromfs
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/interrupt.h>
#include <linux/ptrace.h>
#include <linux/ioport.h>
#include <linux/in.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/if_arp.h>
#include <linux/if_frad.h>
#include <linux/bitops.h>
#include <net/sock.h>
#include <asm/io.h>
#include <asm/dma.h>
#include <linux/uaccess.h>
static const char version[] = "DLCI driver v0.35, 4 Jan 1997, mike.mclagan@linux.org";
static LIST_HEAD(dlci_devs);
static void dlci_setup(struct net_device *);
/*
* these encapsulate the RFC 1490 requirements as well as
* deal with packet transmission and reception, working with
* the upper network layers
*/
static int dlci_header(struct sk_buff *skb, struct net_device *dev,
unsigned short type, const void *daddr,
const void *saddr, unsigned len)
{
struct frhdr hdr;
unsigned int hlen;
char *dest;
hdr.control = FRAD_I_UI;
switch (type)
{
case ETH_P_IP:
hdr.IP_NLPID = FRAD_P_IP;
hlen = sizeof(hdr.control) + sizeof(hdr.IP_NLPID);
break;
/* feel free to add other types, if necessary */
default:
hdr.pad = FRAD_P_PADDING;
hdr.NLPID = FRAD_P_SNAP;
memset(hdr.OUI, 0, sizeof(hdr.OUI));
hdr.PID = htons(type);
hlen = sizeof(hdr);
break;
}
dest = skb_push(skb, hlen);
if (!dest)
return 0;
memcpy(dest, &hdr, hlen);
return hlen;
}
static void dlci_receive(struct sk_buff *skb, struct net_device *dev)
{
struct frhdr *hdr;
int process, header;
if (!pskb_may_pull(skb, sizeof(*hdr))) {
netdev_notice(dev, "invalid data no header\n");
dev->stats.rx_errors++;
kfree_skb(skb);
return;
}
hdr = (struct frhdr *) skb->data;
process = 0;
header = 0;
skb->dev = dev;
if (hdr->control != FRAD_I_UI)
{
netdev_notice(dev, "Invalid header flag 0x%02X\n",
hdr->control);
dev->stats.rx_errors++;
}
else
switch (hdr->IP_NLPID)
{
case FRAD_P_PADDING:
if (hdr->NLPID != FRAD_P_SNAP)
{
netdev_notice(dev, "Unsupported NLPID 0x%02X\n",
hdr->NLPID);
dev->stats.rx_errors++;
break;
}
if (hdr->OUI[0] + hdr->OUI[1] + hdr->OUI[2] != 0)
{
netdev_notice(dev, "Unsupported organizationally unique identifier 0x%02X-%02X-%02X\n",
hdr->OUI[0],
hdr->OUI[1],
hdr->OUI[2]);
dev->stats.rx_errors++;
break;
}
/* at this point, it's an EtherType frame */
header = sizeof(struct frhdr);
/* Already in network order ! */
skb->protocol = hdr->PID;
process = 1;
break;
case FRAD_P_IP:
header = sizeof(hdr->control) + sizeof(hdr->IP_NLPID);
skb->protocol = htons(ETH_P_IP);
process = 1;
break;
case FRAD_P_SNAP:
case FRAD_P_Q933:
case FRAD_P_CLNP:
netdev_notice(dev, "Unsupported NLPID 0x%02X\n",
hdr->pad);
dev->stats.rx_errors++;
break;
default:
netdev_notice(dev, "Invalid pad byte 0x%02X\n",
hdr->pad);
dev->stats.rx_errors++;
break;
}
if (process)
{
/* we've set up the protocol, so discard the header */
skb_reset_mac_header(skb);
skb_pull(skb, header);
dev->stats.rx_bytes += skb->len;
netif_rx(skb);
dev->stats.rx_packets++;
}
else
dev_kfree_skb(skb);
}
static netdev_tx_t dlci_transmit(struct sk_buff *skb, struct net_device *dev)
{
struct dlci_local *dlp = netdev_priv(dev);
if (skb) {
struct netdev_queue *txq = skb_get_tx_queue(dev, skb);
netdev_start_xmit(skb, dlp->slave, txq, false);
}
return NETDEV_TX_OK;
}
static int dlci_config(struct net_device *dev, struct dlci_conf __user *conf, int get)
{
struct dlci_conf config;
struct dlci_local *dlp;
struct frad_local *flp;
int err;
dlp = netdev_priv(dev);
flp = netdev_priv(dlp->slave);
if (!get)
{
if (copy_from_user(&config, conf, sizeof(struct dlci_conf)))
return -EFAULT;
if (config.flags & ~DLCI_VALID_FLAGS)
return -EINVAL;
memcpy(&dlp->config, &config, sizeof(struct dlci_conf));
dlp->configured = 1;
}
err = (*flp->dlci_conf)(dlp->slave, dev, get);
if (err)
return err;
if (get)
{
if (copy_to_user(conf, &dlp->config, sizeof(struct dlci_conf)))
return -EFAULT;
}
return 0;
}
static int dlci_dev_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
struct dlci_local *dlp;
if (!capable(CAP_NET_ADMIN))
return -EPERM;
dlp = netdev_priv(dev);
switch (cmd)
{
case DLCI_GET_SLAVE:
if (!*(short *)(dev->dev_addr))
return -EINVAL;
strncpy(ifr->ifr_slave, dlp->slave->name, sizeof(ifr->ifr_slave));
break;
case DLCI_GET_CONF:
case DLCI_SET_CONF:
if (!*(short *)(dev->dev_addr))
return -EINVAL;
return dlci_config(dev, ifr->ifr_data, cmd == DLCI_GET_CONF);
default:
return -EOPNOTSUPP;
}
return 0;
}
static int dlci_change_mtu(struct net_device *dev, int new_mtu)
{
struct dlci_local *dlp = netdev_priv(dev);
return dev_set_mtu(dlp->slave, new_mtu);
}
static int dlci_open(struct net_device *dev)
{
struct dlci_local *dlp;
struct frad_local *flp;
int err;
dlp = netdev_priv(dev);
if (!*(short *)(dev->dev_addr))
return -EINVAL;
if (!netif_running(dlp->slave))
return -ENOTCONN;
flp = netdev_priv(dlp->slave);
err = (*flp->activate)(dlp->slave, dev);
if (err)
return err;
netif_start_queue(dev);
return 0;
}
static int dlci_close(struct net_device *dev)
{
struct dlci_local *dlp;
struct frad_local *flp;
int err;
netif_stop_queue(dev);
dlp = netdev_priv(dev);
flp = netdev_priv(dlp->slave);
err = (*flp->deactivate)(dlp->slave, dev);
return 0;
}
static int dlci_add(struct dlci_add *dlci)
{
struct net_device *master, *slave;
struct dlci_local *dlp;
struct frad_local *flp;
int err = -EINVAL;
/* validate slave device */
slave = dev_get_by_name(&init_net, dlci->devname);
if (!slave)
return -ENODEV;
if (slave->type != ARPHRD_FRAD || netdev_priv(slave) == NULL)
goto err1;
/* create device name */
master = alloc_netdev(sizeof(struct dlci_local), "dlci%d",
NET_NAME_UNKNOWN, dlci_setup);
if (!master) {
err = -ENOMEM;
goto err1;
}
/* make sure same slave not already registered */
rtnl_lock();
list_for_each_entry(dlp, &dlci_devs, list) {
if (dlp->slave == slave) {
err = -EBUSY;
goto err2;
}
}
*(short *)(master->dev_addr) = dlci->dlci;
dlp = netdev_priv(master);
dlp->slave = slave;
dlp->master = master;
flp = netdev_priv(slave);
err = (*flp->assoc)(slave, master);
if (err < 0)
goto err2;
err = register_netdevice(master);
if (err < 0)
goto err2;
strcpy(dlci->devname, master->name);
list_add(&dlp->list, &dlci_devs);
rtnl_unlock();
return 0;
err2:
rtnl_unlock();
free_netdev(master);
err1:
dev_put(slave);
return err;
}
static int dlci_del(struct dlci_add *dlci)
{
struct dlci_local *dlp;
struct frad_local *flp;
struct net_device *master, *slave;
int err;
bool found = false;
rtnl_lock();
/* validate slave device */
master = __dev_get_by_name(&init_net, dlci->devname);
if (!master) {
err = -ENODEV;
goto out;
}
list_for_each_entry(dlp, &dlci_devs, list) {
if (dlp->master == master) {
found = true;
break;
}
}
if (!found) {
err = -ENODEV;
goto out;
}
if (netif_running(master)) {
err = -EBUSY;
goto out;
}
dlp = netdev_priv(master);
slave = dlp->slave;
flp = netdev_priv(slave);
err = (*flp->deassoc)(slave, master);
if (!err) {
list_del(&dlp->list);
unregister_netdevice(master);
dev_put(slave);
}
out:
rtnl_unlock();
return err;
}
static int dlci_ioctl(unsigned int cmd, void __user *arg)
{
struct dlci_add add;
int err;
if (!capable(CAP_NET_ADMIN))
return -EPERM;
if (copy_from_user(&add, arg, sizeof(struct dlci_add)))
return -EFAULT;
switch (cmd)
{
case SIOCADDDLCI:
err = dlci_add(&add);
if (!err)
if (copy_to_user(arg, &add, sizeof(struct dlci_add)))
return -EFAULT;
break;
case SIOCDELDLCI:
err = dlci_del(&add);
break;
default:
err = -EINVAL;
}
return err;
}
static const struct header_ops dlci_header_ops = {
.create = dlci_header,
};
static const struct net_device_ops dlci_netdev_ops = {
.ndo_open = dlci_open,
.ndo_stop = dlci_close,
.ndo_do_ioctl = dlci_dev_ioctl,
.ndo_start_xmit = dlci_transmit,
.ndo_change_mtu = dlci_change_mtu,
};
static void dlci_setup(struct net_device *dev)
{
struct dlci_local *dlp = netdev_priv(dev);
dev->flags = 0;
dev->header_ops = &dlci_header_ops;
dev->netdev_ops = &dlci_netdev_ops;
dev->needs_free_netdev = true;
dlp->receive = dlci_receive;
dev->type = ARPHRD_DLCI;
dev->hard_header_len = sizeof(struct frhdr);
dev->addr_len = sizeof(short);
}
/* if slave is unregistering, then cleanup master */
static int dlci_dev_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
if (dev_net(dev) != &init_net)
return NOTIFY_DONE;
if (event == NETDEV_UNREGISTER) {
struct dlci_local *dlp;
list_for_each_entry(dlp, &dlci_devs, list) {
if (dlp->slave == dev) {
list_del(&dlp->list);
unregister_netdevice(dlp->master);
dev_put(dlp->slave);
break;
}
}
}
return NOTIFY_DONE;
}
static struct notifier_block dlci_notifier = {
.notifier_call = dlci_dev_event,
};
static int __init init_dlci(void)
{
dlci_ioctl_set(dlci_ioctl);
register_netdevice_notifier(&dlci_notifier);
printk("%s.\n", version);
return 0;
}
static void __exit dlci_exit(void)
{
struct dlci_local *dlp, *nxt;
dlci_ioctl_set(NULL);
unregister_netdevice_notifier(&dlci_notifier);
rtnl_lock();
list_for_each_entry_safe(dlp, nxt, &dlci_devs, list) {
unregister_netdevice(dlp->master);
dev_put(dlp->slave);
}
rtnl_unlock();
}
module_init(init_dlci);
module_exit(dlci_exit);
MODULE_AUTHOR("Mike McLagan");
MODULE_DESCRIPTION("Frame Relay DLCI layer");
MODULE_LICENSE("GPL");