/***
|
*
|
* stack/rtnet_chrdev.c - implements char device for management interface
|
*
|
* Copyright (C) 1999 Lineo, Inc
|
* 1999, 2002 David A. Schleef <ds@schleef.org>
|
* 2002 Ulrich Marx <marx@fet.uni-hannover.de>
|
* 2003-2005 Jan Kiszka <jan.kiszka@web.de>
|
*
|
* 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.
|
*
|
* This program is distributed in the hope that it will be useful,
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* GNU General Public License for more details.
|
*
|
* You should have received a copy of the GNU General Public License
|
* along with this program; if not, write to the Free Software
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
*
|
*/
|
|
#include <linux/kernel.h>
|
#include <linux/errno.h>
|
#include <linux/if_arp.h>
|
#include <linux/kmod.h>
|
#include <linux/miscdevice.h>
|
#include <linux/netdevice.h>
|
#include <linux/spinlock.h>
|
|
#include <rtnet_chrdev.h>
|
#include <rtnet_internal.h>
|
#include <ipv4/route.h>
|
|
static DEFINE_SPINLOCK(ioctl_handler_lock);
|
static LIST_HEAD(ioctl_handlers);
|
|
static long rtnet_ioctl(struct file *file, unsigned int request,
|
unsigned long arg)
|
{
|
struct rtnet_ioctl_head head;
|
struct rtnet_device *rtdev = NULL;
|
struct rtnet_ioctls *ioctls;
|
struct list_head *entry;
|
int ret;
|
|
if (!capable(CAP_SYS_ADMIN))
|
return -EPERM;
|
|
ret = copy_from_user(&head, (void *)arg, sizeof(head));
|
if (ret != 0)
|
return -EFAULT;
|
|
spin_lock(&ioctl_handler_lock);
|
|
list_for_each (entry, &ioctl_handlers) {
|
ioctls = list_entry(entry, struct rtnet_ioctls, entry);
|
|
if (ioctls->ioctl_type == _IOC_TYPE(request)) {
|
atomic_inc(&ioctls->ref_count);
|
|
spin_unlock(&ioctl_handler_lock);
|
|
if ((_IOC_NR(request) & RTNET_IOC_NODEV_PARAM) == 0) {
|
rtdev = rtdev_get_by_name(head.if_name);
|
if (!rtdev) {
|
atomic_dec(&ioctls->ref_count);
|
return -ENODEV;
|
}
|
}
|
|
ret = ioctls->handler(rtdev, request, arg);
|
|
if (rtdev)
|
rtdev_dereference(rtdev);
|
atomic_dec(&ioctls->ref_count);
|
|
return ret;
|
}
|
}
|
|
spin_unlock(&ioctl_handler_lock);
|
|
return -ENOTTY;
|
}
|
|
static int rtnet_core_ioctl(struct rtnet_device *rtdev, unsigned int request,
|
unsigned long arg)
|
{
|
struct rtnet_core_cmd cmd;
|
int ret;
|
|
ret = copy_from_user(&cmd, (void *)arg, sizeof(cmd));
|
if (ret != 0)
|
return -EFAULT;
|
|
switch (request) {
|
case IOC_RT_IFUP:
|
ret = rtdev_up(rtdev, &cmd);
|
break;
|
|
case IOC_RT_IFDOWN:
|
ret = rtdev_down(rtdev);
|
break;
|
|
case IOC_RT_IFINFO:
|
if (cmd.args.info.ifindex > 0)
|
rtdev = rtdev_get_by_index(cmd.args.info.ifindex);
|
else
|
rtdev = rtdev_get_by_name(cmd.head.if_name);
|
if (rtdev == NULL)
|
return -ENODEV;
|
|
if (mutex_lock_interruptible(&rtdev->nrt_lock)) {
|
rtdev_dereference(rtdev);
|
return -ERESTARTSYS;
|
}
|
|
memcpy(cmd.head.if_name, rtdev->name, IFNAMSIZ);
|
cmd.args.info.ifindex = rtdev->ifindex;
|
cmd.args.info.type = rtdev->type;
|
cmd.args.info.ip_addr = rtdev->local_ip;
|
cmd.args.info.broadcast_ip = rtdev->broadcast_ip;
|
cmd.args.info.mtu = rtdev->mtu;
|
cmd.args.info.flags = rtdev->flags;
|
if ((cmd.args.info.flags & IFF_UP) &&
|
(rtdev->link_state &
|
(RTNET_LINK_STATE_PRESENT | RTNET_LINK_STATE_NOCARRIER)) ==
|
RTNET_LINK_STATE_PRESENT)
|
cmd.args.info.flags |= IFF_RUNNING;
|
|
memcpy(cmd.args.info.dev_addr, rtdev->dev_addr, MAX_ADDR_LEN);
|
|
mutex_unlock(&rtdev->nrt_lock);
|
|
rtdev_dereference(rtdev);
|
|
if (copy_to_user((void *)arg, &cmd, sizeof(cmd)) != 0)
|
return -EFAULT;
|
break;
|
|
default:
|
ret = -ENOTTY;
|
}
|
|
return ret;
|
}
|
|
int rtnet_register_ioctls(struct rtnet_ioctls *ioctls)
|
{
|
struct list_head *entry;
|
struct rtnet_ioctls *registered_ioctls;
|
|
RTNET_ASSERT(ioctls->handler != NULL, return -EINVAL;);
|
|
spin_lock(&ioctl_handler_lock);
|
|
list_for_each (entry, &ioctl_handlers) {
|
registered_ioctls =
|
list_entry(entry, struct rtnet_ioctls, entry);
|
if (registered_ioctls->ioctl_type == ioctls->ioctl_type) {
|
spin_unlock(&ioctl_handler_lock);
|
return -EEXIST;
|
}
|
}
|
|
list_add_tail(&ioctls->entry, &ioctl_handlers);
|
atomic_set(&ioctls->ref_count, 0);
|
|
spin_unlock(&ioctl_handler_lock);
|
|
return 0;
|
}
|
|
void rtnet_unregister_ioctls(struct rtnet_ioctls *ioctls)
|
{
|
spin_lock(&ioctl_handler_lock);
|
|
while (atomic_read(&ioctls->ref_count) != 0) {
|
spin_unlock(&ioctl_handler_lock);
|
|
set_current_state(TASK_UNINTERRUPTIBLE);
|
schedule_timeout(1 * HZ); /* wait a second */
|
|
spin_lock(&ioctl_handler_lock);
|
}
|
|
list_del(&ioctls->entry);
|
|
spin_unlock(&ioctl_handler_lock);
|
}
|
|
static struct file_operations rtnet_fops = {
|
.owner = THIS_MODULE,
|
.unlocked_ioctl = rtnet_ioctl,
|
.compat_ioctl = rtnet_ioctl,
|
};
|
|
static struct miscdevice rtnet_chr_misc_dev = {
|
.minor = RTNET_MINOR,
|
.name = "rtnet",
|
.fops = &rtnet_fops,
|
};
|
|
static struct rtnet_ioctls core_ioctls = { .service_name = "RTnet Core",
|
.ioctl_type = RTNET_IOC_TYPE_CORE,
|
.handler = rtnet_core_ioctl };
|
|
/**
|
* rtnet_chrdev_init -
|
*
|
*/
|
int __init rtnet_chrdev_init(void)
|
{
|
int err;
|
|
err = misc_register(&rtnet_chr_misc_dev);
|
if (err) {
|
printk("RTnet: unable to register rtnet management device/class "
|
"(error %d)\n",
|
err);
|
return err;
|
}
|
|
rtnet_register_ioctls(&core_ioctls);
|
return 0;
|
}
|
|
/**
|
* rtnet_chrdev_release -
|
*
|
*/
|
void rtnet_chrdev_release(void)
|
{
|
misc_deregister(&rtnet_chr_misc_dev);
|
}
|
|
EXPORT_SYMBOL_GPL(rtnet_register_ioctls);
|
EXPORT_SYMBOL_GPL(rtnet_unregister_ioctls);
|