forked from luck/tmp_suning_uos_patched
2b8693c061
Many struct file_operations in the kernel can be "const". Marking them const moves these to the .rodata section, which avoids false sharing with potential dirty data. In addition it'll catch accidental writes at compile time to these shared resources. Signed-off-by: Arjan van de Ven <arjan@linux.intel.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
318 lines
8.4 KiB
C
318 lines
8.4 KiB
C
/* $Id: divert_procfs.c,v 1.11.6.2 2001/09/23 22:24:36 kai Exp $
|
|
*
|
|
* Filesystem handling for the diversion supplementary services.
|
|
*
|
|
* Copyright 1998 by Werner Cornelius (werner@isdn4linux.de)
|
|
*
|
|
* This software may be used and distributed according to the terms
|
|
* of the GNU General Public License, incorporated herein by reference.
|
|
*
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/smp_lock.h>
|
|
#ifdef CONFIG_PROC_FS
|
|
#include <linux/proc_fs.h>
|
|
#else
|
|
#include <linux/fs.h>
|
|
#endif
|
|
#include <linux/isdnif.h>
|
|
#include "isdn_divert.h"
|
|
|
|
|
|
/*********************************/
|
|
/* Variables for interface queue */
|
|
/*********************************/
|
|
ulong if_used = 0; /* number of interface users */
|
|
static struct divert_info *divert_info_head = NULL; /* head of queue */
|
|
static struct divert_info *divert_info_tail = NULL; /* pointer to last entry */
|
|
static DEFINE_SPINLOCK(divert_info_lock);/* lock for queue */
|
|
static wait_queue_head_t rd_queue;
|
|
|
|
/*********************************/
|
|
/* put an info buffer into queue */
|
|
/*********************************/
|
|
void
|
|
put_info_buffer(char *cp)
|
|
{
|
|
struct divert_info *ib;
|
|
unsigned long flags;
|
|
|
|
if (if_used <= 0)
|
|
return;
|
|
if (!cp)
|
|
return;
|
|
if (!*cp)
|
|
return;
|
|
if (!(ib = kmalloc(sizeof(struct divert_info) + strlen(cp), GFP_ATOMIC)))
|
|
return; /* no memory */
|
|
strcpy(ib->info_start, cp); /* set output string */
|
|
ib->next = NULL;
|
|
spin_lock_irqsave( &divert_info_lock, flags );
|
|
ib->usage_cnt = if_used;
|
|
if (!divert_info_head)
|
|
divert_info_head = ib; /* new head */
|
|
else
|
|
divert_info_tail->next = ib; /* follows existing messages */
|
|
divert_info_tail = ib; /* new tail */
|
|
|
|
/* delete old entrys */
|
|
while (divert_info_head->next) {
|
|
if ((divert_info_head->usage_cnt <= 0) &&
|
|
(divert_info_head->next->usage_cnt <= 0)) {
|
|
ib = divert_info_head;
|
|
divert_info_head = divert_info_head->next;
|
|
kfree(ib);
|
|
} else
|
|
break;
|
|
} /* divert_info_head->next */
|
|
spin_unlock_irqrestore( &divert_info_lock, flags );
|
|
wake_up_interruptible(&(rd_queue));
|
|
} /* put_info_buffer */
|
|
|
|
/**********************************/
|
|
/* deflection device read routine */
|
|
/**********************************/
|
|
static ssize_t
|
|
isdn_divert_read(struct file *file, char __user *buf, size_t count, loff_t * off)
|
|
{
|
|
struct divert_info *inf;
|
|
int len;
|
|
|
|
if (!*((struct divert_info **) file->private_data)) {
|
|
if (file->f_flags & O_NONBLOCK)
|
|
return -EAGAIN;
|
|
interruptible_sleep_on(&(rd_queue));
|
|
}
|
|
if (!(inf = *((struct divert_info **) file->private_data)))
|
|
return (0);
|
|
|
|
inf->usage_cnt--; /* new usage count */
|
|
file->private_data = &inf->next; /* next structure */
|
|
if ((len = strlen(inf->info_start)) <= count) {
|
|
if (copy_to_user(buf, inf->info_start, len))
|
|
return -EFAULT;
|
|
*off += len;
|
|
return (len);
|
|
}
|
|
return (0);
|
|
} /* isdn_divert_read */
|
|
|
|
/**********************************/
|
|
/* deflection device write routine */
|
|
/**********************************/
|
|
static ssize_t
|
|
isdn_divert_write(struct file *file, const char __user *buf, size_t count, loff_t * off)
|
|
{
|
|
return (-ENODEV);
|
|
} /* isdn_divert_write */
|
|
|
|
|
|
/***************************************/
|
|
/* select routines for various kernels */
|
|
/***************************************/
|
|
static unsigned int
|
|
isdn_divert_poll(struct file *file, poll_table * wait)
|
|
{
|
|
unsigned int mask = 0;
|
|
|
|
poll_wait(file, &(rd_queue), wait);
|
|
/* mask = POLLOUT | POLLWRNORM; */
|
|
if (*((struct divert_info **) file->private_data)) {
|
|
mask |= POLLIN | POLLRDNORM;
|
|
}
|
|
return mask;
|
|
} /* isdn_divert_poll */
|
|
|
|
/****************/
|
|
/* Open routine */
|
|
/****************/
|
|
static int
|
|
isdn_divert_open(struct inode *ino, struct file *filep)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave( &divert_info_lock, flags );
|
|
if_used++;
|
|
if (divert_info_head)
|
|
filep->private_data = &(divert_info_tail->next);
|
|
else
|
|
filep->private_data = &divert_info_head;
|
|
spin_unlock_irqrestore( &divert_info_lock, flags );
|
|
/* start_divert(); */
|
|
return nonseekable_open(ino, filep);
|
|
} /* isdn_divert_open */
|
|
|
|
/*******************/
|
|
/* close routine */
|
|
/*******************/
|
|
static int
|
|
isdn_divert_close(struct inode *ino, struct file *filep)
|
|
{
|
|
struct divert_info *inf;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave( &divert_info_lock, flags );
|
|
if_used--;
|
|
inf = *((struct divert_info **) filep->private_data);
|
|
while (inf) {
|
|
inf->usage_cnt--;
|
|
inf = inf->next;
|
|
}
|
|
if (if_used <= 0)
|
|
while (divert_info_head) {
|
|
inf = divert_info_head;
|
|
divert_info_head = divert_info_head->next;
|
|
kfree(inf);
|
|
}
|
|
spin_unlock_irqrestore( &divert_info_lock, flags );
|
|
return (0);
|
|
} /* isdn_divert_close */
|
|
|
|
/*********/
|
|
/* IOCTL */
|
|
/*********/
|
|
static int
|
|
isdn_divert_ioctl(struct inode *inode, struct file *file,
|
|
uint cmd, ulong arg)
|
|
{
|
|
divert_ioctl dioctl;
|
|
int i;
|
|
unsigned long flags;
|
|
divert_rule *rulep;
|
|
char *cp;
|
|
|
|
if (copy_from_user(&dioctl, (void __user *) arg, sizeof(dioctl)))
|
|
return -EFAULT;
|
|
|
|
switch (cmd) {
|
|
case IIOCGETVER:
|
|
dioctl.drv_version = DIVERT_IIOC_VERSION; /* set version */
|
|
break;
|
|
|
|
case IIOCGETDRV:
|
|
if ((dioctl.getid.drvid = divert_if.name_to_drv(dioctl.getid.drvnam)) < 0)
|
|
return (-EINVAL);
|
|
break;
|
|
|
|
case IIOCGETNAM:
|
|
cp = divert_if.drv_to_name(dioctl.getid.drvid);
|
|
if (!cp)
|
|
return (-EINVAL);
|
|
if (!*cp)
|
|
return (-EINVAL);
|
|
strcpy(dioctl.getid.drvnam, cp);
|
|
break;
|
|
|
|
case IIOCGETRULE:
|
|
if (!(rulep = getruleptr(dioctl.getsetrule.ruleidx)))
|
|
return (-EINVAL);
|
|
dioctl.getsetrule.rule = *rulep; /* copy data */
|
|
break;
|
|
|
|
case IIOCMODRULE:
|
|
if (!(rulep = getruleptr(dioctl.getsetrule.ruleidx)))
|
|
return (-EINVAL);
|
|
spin_lock_irqsave(&divert_lock, flags);
|
|
*rulep = dioctl.getsetrule.rule; /* copy data */
|
|
spin_unlock_irqrestore(&divert_lock, flags);
|
|
return (0); /* no copy required */
|
|
break;
|
|
|
|
case IIOCINSRULE:
|
|
return (insertrule(dioctl.getsetrule.ruleidx, &dioctl.getsetrule.rule));
|
|
break;
|
|
|
|
case IIOCDELRULE:
|
|
return (deleterule(dioctl.getsetrule.ruleidx));
|
|
break;
|
|
|
|
case IIOCDODFACT:
|
|
return (deflect_extern_action(dioctl.fwd_ctrl.subcmd,
|
|
dioctl.fwd_ctrl.callid,
|
|
dioctl.fwd_ctrl.to_nr));
|
|
|
|
case IIOCDOCFACT:
|
|
case IIOCDOCFDIS:
|
|
case IIOCDOCFINT:
|
|
if (!divert_if.drv_to_name(dioctl.cf_ctrl.drvid))
|
|
return (-EINVAL); /* invalid driver */
|
|
if ((i = cf_command(dioctl.cf_ctrl.drvid,
|
|
(cmd == IIOCDOCFACT) ? 1 : (cmd == IIOCDOCFDIS) ? 0 : 2,
|
|
dioctl.cf_ctrl.cfproc,
|
|
dioctl.cf_ctrl.msn,
|
|
dioctl.cf_ctrl.service,
|
|
dioctl.cf_ctrl.fwd_nr,
|
|
&dioctl.cf_ctrl.procid)))
|
|
return (i);
|
|
break;
|
|
|
|
default:
|
|
return (-EINVAL);
|
|
} /* switch cmd */
|
|
return copy_to_user((void __user *)arg, &dioctl, sizeof(dioctl)) ? -EFAULT : 0;
|
|
} /* isdn_divert_ioctl */
|
|
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
static const struct file_operations isdn_fops =
|
|
{
|
|
.owner = THIS_MODULE,
|
|
.llseek = no_llseek,
|
|
.read = isdn_divert_read,
|
|
.write = isdn_divert_write,
|
|
.poll = isdn_divert_poll,
|
|
.ioctl = isdn_divert_ioctl,
|
|
.open = isdn_divert_open,
|
|
.release = isdn_divert_close,
|
|
};
|
|
|
|
/****************************/
|
|
/* isdn subdir in /proc/net */
|
|
/****************************/
|
|
static struct proc_dir_entry *isdn_proc_entry = NULL;
|
|
static struct proc_dir_entry *isdn_divert_entry = NULL;
|
|
#endif /* CONFIG_PROC_FS */
|
|
|
|
/***************************************************************************/
|
|
/* divert_dev_init must be called before the proc filesystem may be used */
|
|
/***************************************************************************/
|
|
int
|
|
divert_dev_init(void)
|
|
{
|
|
|
|
init_waitqueue_head(&rd_queue);
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
isdn_proc_entry = proc_mkdir("net/isdn", NULL);
|
|
if (!isdn_proc_entry)
|
|
return (-1);
|
|
isdn_divert_entry = create_proc_entry("divert", S_IFREG | S_IRUGO, isdn_proc_entry);
|
|
if (!isdn_divert_entry) {
|
|
remove_proc_entry("net/isdn", NULL);
|
|
return (-1);
|
|
}
|
|
isdn_divert_entry->proc_fops = &isdn_fops;
|
|
isdn_divert_entry->owner = THIS_MODULE;
|
|
#endif /* CONFIG_PROC_FS */
|
|
|
|
return (0);
|
|
} /* divert_dev_init */
|
|
|
|
/***************************************************************************/
|
|
/* divert_dev_deinit must be called before leaving isdn when included as */
|
|
/* a module. */
|
|
/***************************************************************************/
|
|
int
|
|
divert_dev_deinit(void)
|
|
{
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
remove_proc_entry("divert", isdn_proc_entry);
|
|
remove_proc_entry("net/isdn", NULL);
|
|
#endif /* CONFIG_PROC_FS */
|
|
|
|
return (0);
|
|
} /* divert_dev_deinit */
|