99b7623380
Setting ->owner as done currently (pde->owner = THIS_MODULE) is racy as correctly noted at bug #12454. Someone can lookup entry with NULL ->owner, thus not pinning enything, and release it later resulting in module refcount underflow. We can keep ->owner and supply it at registration time like ->proc_fops and ->data. But this leaves ->owner as easy-manipulative field (just one C assignment) and somebody will forget to unpin previous/pin current module when switching ->owner. ->proc_fops is declared as "const" which should give some thoughts. ->read_proc/->write_proc were just fixed to not require ->owner for protection. rmmod'ed directories will be empty and return "." and ".." -- no harm. And directories with tricky enough readdir and lookup shouldn't be modular. We definitely don't want such modular code. Removing ->owner will also make PDE smaller. So, let's nuke it. Kudos to Jeff Layton for reminding about this, let's say, oversight. http://bugzilla.kernel.org/show_bug.cgi?id=12454 Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com>
578 lines
12 KiB
C
578 lines
12 KiB
C
/* $Id: divasi.c,v 1.25.6.2 2005/01/31 12:22:20 armin Exp $
|
|
*
|
|
* Driver for Eicon DIVA Server ISDN cards.
|
|
* User Mode IDI Interface
|
|
*
|
|
* Copyright 2000-2003 by Armin Schindler (mac@melware.de)
|
|
* Copyright 2000-2003 Cytronics & Melware (info@melware.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/init.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/smp_lock.h>
|
|
#include <asm/uaccess.h>
|
|
|
|
#include "platform.h"
|
|
#include "di_defs.h"
|
|
#include "divasync.h"
|
|
#include "um_xdi.h"
|
|
#include "um_idi.h"
|
|
|
|
static char *main_revision = "$Revision: 1.25.6.2 $";
|
|
|
|
static int major;
|
|
|
|
MODULE_DESCRIPTION("User IDI Interface for Eicon ISDN cards");
|
|
MODULE_AUTHOR("Cytronics & Melware, Eicon Networks");
|
|
MODULE_SUPPORTED_DEVICE("DIVA card driver");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
typedef struct _diva_um_idi_os_context {
|
|
wait_queue_head_t read_wait;
|
|
wait_queue_head_t close_wait;
|
|
struct timer_list diva_timer_id;
|
|
int aborted;
|
|
int adapter_nr;
|
|
} diva_um_idi_os_context_t;
|
|
|
|
static char *DRIVERNAME = "Eicon DIVA - User IDI (http://www.melware.net)";
|
|
static char *DRIVERLNAME = "diva_idi";
|
|
static char *DEVNAME = "DivasIDI";
|
|
char *DRIVERRELEASE_IDI = "2.0";
|
|
|
|
extern int idifunc_init(void);
|
|
extern void idifunc_finit(void);
|
|
|
|
/*
|
|
* helper functions
|
|
*/
|
|
static char *getrev(const char *revision)
|
|
{
|
|
char *rev;
|
|
char *p;
|
|
if ((p = strchr(revision, ':'))) {
|
|
rev = p + 2;
|
|
p = strchr(rev, '$');
|
|
*--p = 0;
|
|
} else
|
|
rev = "1.0";
|
|
return rev;
|
|
}
|
|
|
|
/*
|
|
* LOCALS
|
|
*/
|
|
static ssize_t um_idi_read(struct file *file, char __user *buf, size_t count,
|
|
loff_t * offset);
|
|
static ssize_t um_idi_write(struct file *file, const char __user *buf,
|
|
size_t count, loff_t * offset);
|
|
static unsigned int um_idi_poll(struct file *file, poll_table * wait);
|
|
static int um_idi_open(struct inode *inode, struct file *file);
|
|
static int um_idi_release(struct inode *inode, struct file *file);
|
|
static int remove_entity(void *entity);
|
|
static void diva_um_timer_function(unsigned long data);
|
|
|
|
/*
|
|
* proc entry
|
|
*/
|
|
extern struct proc_dir_entry *proc_net_eicon;
|
|
static struct proc_dir_entry *um_idi_proc_entry = NULL;
|
|
|
|
static int
|
|
um_idi_proc_read(char *page, char **start, off_t off, int count, int *eof,
|
|
void *data)
|
|
{
|
|
int len = 0;
|
|
char tmprev[32];
|
|
|
|
len += sprintf(page + len, "%s\n", DRIVERNAME);
|
|
len += sprintf(page + len, "name : %s\n", DRIVERLNAME);
|
|
len += sprintf(page + len, "release : %s\n", DRIVERRELEASE_IDI);
|
|
strcpy(tmprev, main_revision);
|
|
len += sprintf(page + len, "revision : %s\n", getrev(tmprev));
|
|
len += sprintf(page + len, "build : %s\n", DIVA_BUILD);
|
|
len += sprintf(page + len, "major : %d\n", major);
|
|
|
|
if (off + count >= len)
|
|
*eof = 1;
|
|
if (len < off)
|
|
return 0;
|
|
*start = page + off;
|
|
return ((count < len - off) ? count : len - off);
|
|
}
|
|
|
|
static int DIVA_INIT_FUNCTION create_um_idi_proc(void)
|
|
{
|
|
um_idi_proc_entry = create_proc_entry(DRIVERLNAME,
|
|
S_IFREG | S_IRUGO | S_IWUSR,
|
|
proc_net_eicon);
|
|
if (!um_idi_proc_entry)
|
|
return (0);
|
|
|
|
um_idi_proc_entry->read_proc = um_idi_proc_read;
|
|
|
|
return (1);
|
|
}
|
|
|
|
static void remove_um_idi_proc(void)
|
|
{
|
|
if (um_idi_proc_entry) {
|
|
remove_proc_entry(DRIVERLNAME, proc_net_eicon);
|
|
um_idi_proc_entry = NULL;
|
|
}
|
|
}
|
|
|
|
static const struct file_operations divas_idi_fops = {
|
|
.owner = THIS_MODULE,
|
|
.llseek = no_llseek,
|
|
.read = um_idi_read,
|
|
.write = um_idi_write,
|
|
.poll = um_idi_poll,
|
|
.open = um_idi_open,
|
|
.release = um_idi_release
|
|
};
|
|
|
|
static void divas_idi_unregister_chrdev(void)
|
|
{
|
|
unregister_chrdev(major, DEVNAME);
|
|
}
|
|
|
|
static int DIVA_INIT_FUNCTION divas_idi_register_chrdev(void)
|
|
{
|
|
if ((major = register_chrdev(0, DEVNAME, &divas_idi_fops)) < 0)
|
|
{
|
|
printk(KERN_ERR "%s: failed to create /dev entry.\n",
|
|
DRIVERLNAME);
|
|
return (0);
|
|
}
|
|
|
|
return (1);
|
|
}
|
|
|
|
/*
|
|
** Driver Load
|
|
*/
|
|
static int DIVA_INIT_FUNCTION divasi_init(void)
|
|
{
|
|
char tmprev[50];
|
|
int ret = 0;
|
|
|
|
printk(KERN_INFO "%s\n", DRIVERNAME);
|
|
printk(KERN_INFO "%s: Rel:%s Rev:", DRIVERLNAME, DRIVERRELEASE_IDI);
|
|
strcpy(tmprev, main_revision);
|
|
printk("%s Build: %s\n", getrev(tmprev), DIVA_BUILD);
|
|
|
|
if (!divas_idi_register_chrdev()) {
|
|
ret = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
if (!create_um_idi_proc()) {
|
|
divas_idi_unregister_chrdev();
|
|
printk(KERN_ERR "%s: failed to create proc entry.\n",
|
|
DRIVERLNAME);
|
|
ret = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
if (!(idifunc_init())) {
|
|
remove_um_idi_proc();
|
|
divas_idi_unregister_chrdev();
|
|
printk(KERN_ERR "%s: failed to connect to DIDD.\n",
|
|
DRIVERLNAME);
|
|
ret = -EIO;
|
|
goto out;
|
|
}
|
|
printk(KERN_INFO "%s: started with major %d\n", DRIVERLNAME, major);
|
|
|
|
out:
|
|
return (ret);
|
|
}
|
|
|
|
|
|
/*
|
|
** Driver Unload
|
|
*/
|
|
static void DIVA_EXIT_FUNCTION divasi_exit(void)
|
|
{
|
|
idifunc_finit();
|
|
remove_um_idi_proc();
|
|
divas_idi_unregister_chrdev();
|
|
|
|
printk(KERN_INFO "%s: module unloaded.\n", DRIVERLNAME);
|
|
}
|
|
|
|
module_init(divasi_init);
|
|
module_exit(divasi_exit);
|
|
|
|
|
|
/*
|
|
* FILE OPERATIONS
|
|
*/
|
|
|
|
static int
|
|
divas_um_idi_copy_to_user(void *os_handle, void *dst, const void *src,
|
|
int length)
|
|
{
|
|
memcpy(dst, src, length);
|
|
return (length);
|
|
}
|
|
|
|
static ssize_t
|
|
um_idi_read(struct file *file, char __user *buf, size_t count, loff_t * offset)
|
|
{
|
|
diva_um_idi_os_context_t *p_os;
|
|
int ret = -EINVAL;
|
|
void *data;
|
|
|
|
if (!file->private_data) {
|
|
return (-ENODEV);
|
|
}
|
|
|
|
if (!
|
|
(p_os =
|
|
(diva_um_idi_os_context_t *) diva_um_id_get_os_context(file->
|
|
private_data)))
|
|
{
|
|
return (-ENODEV);
|
|
}
|
|
if (p_os->aborted) {
|
|
return (-ENODEV);
|
|
}
|
|
|
|
if (!(data = diva_os_malloc(0, count))) {
|
|
return (-ENOMEM);
|
|
}
|
|
|
|
ret = diva_um_idi_read(file->private_data,
|
|
file, data, count,
|
|
divas_um_idi_copy_to_user);
|
|
switch (ret) {
|
|
case 0: /* no message available */
|
|
ret = (-EAGAIN);
|
|
break;
|
|
case (-1): /* adapter was removed */
|
|
ret = (-ENODEV);
|
|
break;
|
|
case (-2): /* message_length > length of user buffer */
|
|
ret = (-EFAULT);
|
|
break;
|
|
}
|
|
|
|
if (ret > 0) {
|
|
if (copy_to_user(buf, data, ret)) {
|
|
ret = (-EFAULT);
|
|
}
|
|
}
|
|
|
|
diva_os_free(0, data);
|
|
DBG_TRC(("read: ret %d", ret));
|
|
return (ret);
|
|
}
|
|
|
|
|
|
static int
|
|
divas_um_idi_copy_from_user(void *os_handle, void *dst, const void *src,
|
|
int length)
|
|
{
|
|
memcpy(dst, src, length);
|
|
return (length);
|
|
}
|
|
|
|
static int um_idi_open_adapter(struct file *file, int adapter_nr)
|
|
{
|
|
diva_um_idi_os_context_t *p_os;
|
|
void *e =
|
|
divas_um_idi_create_entity((dword) adapter_nr, (void *) file);
|
|
|
|
if (!(file->private_data = e)) {
|
|
return (0);
|
|
}
|
|
p_os = (diva_um_idi_os_context_t *) diva_um_id_get_os_context(e);
|
|
init_waitqueue_head(&p_os->read_wait);
|
|
init_waitqueue_head(&p_os->close_wait);
|
|
init_timer(&p_os->diva_timer_id);
|
|
p_os->diva_timer_id.function = (void *) diva_um_timer_function;
|
|
p_os->diva_timer_id.data = (unsigned long) p_os;
|
|
p_os->aborted = 0;
|
|
p_os->adapter_nr = adapter_nr;
|
|
return (1);
|
|
}
|
|
|
|
static ssize_t
|
|
um_idi_write(struct file *file, const char __user *buf, size_t count,
|
|
loff_t * offset)
|
|
{
|
|
diva_um_idi_os_context_t *p_os;
|
|
int ret = -EINVAL;
|
|
void *data;
|
|
int adapter_nr = 0;
|
|
|
|
if (!file->private_data) {
|
|
/* the first write() selects the adapter_nr */
|
|
if (count == sizeof(int)) {
|
|
if (copy_from_user
|
|
((void *) &adapter_nr, buf,
|
|
count)) return (-EFAULT);
|
|
if (!(um_idi_open_adapter(file, adapter_nr)))
|
|
return (-ENODEV);
|
|
return (count);
|
|
} else
|
|
return (-ENODEV);
|
|
}
|
|
|
|
if (!(p_os =
|
|
(diva_um_idi_os_context_t *) diva_um_id_get_os_context(file->
|
|
private_data)))
|
|
{
|
|
return (-ENODEV);
|
|
}
|
|
if (p_os->aborted) {
|
|
return (-ENODEV);
|
|
}
|
|
|
|
if (!(data = diva_os_malloc(0, count))) {
|
|
return (-ENOMEM);
|
|
}
|
|
|
|
if (copy_from_user(data, buf, count)) {
|
|
ret = -EFAULT;
|
|
} else {
|
|
ret = diva_um_idi_write(file->private_data,
|
|
file, data, count,
|
|
divas_um_idi_copy_from_user);
|
|
switch (ret) {
|
|
case 0: /* no space available */
|
|
ret = (-EAGAIN);
|
|
break;
|
|
case (-1): /* adapter was removed */
|
|
ret = (-ENODEV);
|
|
break;
|
|
case (-2): /* length of user buffer > max message_length */
|
|
ret = (-EFAULT);
|
|
break;
|
|
}
|
|
}
|
|
diva_os_free(0, data);
|
|
DBG_TRC(("write: ret %d", ret));
|
|
return (ret);
|
|
}
|
|
|
|
static unsigned int um_idi_poll(struct file *file, poll_table * wait)
|
|
{
|
|
diva_um_idi_os_context_t *p_os;
|
|
|
|
if (!file->private_data) {
|
|
return (POLLERR);
|
|
}
|
|
|
|
if ((!(p_os =
|
|
(diva_um_idi_os_context_t *)
|
|
diva_um_id_get_os_context(file->private_data)))
|
|
|| p_os->aborted) {
|
|
return (POLLERR);
|
|
}
|
|
|
|
poll_wait(file, &p_os->read_wait, wait);
|
|
|
|
if (p_os->aborted) {
|
|
return (POLLERR);
|
|
}
|
|
|
|
switch (diva_user_mode_idi_ind_ready(file->private_data, file)) {
|
|
case (-1):
|
|
return (POLLERR);
|
|
|
|
case 0:
|
|
return (0);
|
|
}
|
|
|
|
return (POLLIN | POLLRDNORM);
|
|
}
|
|
|
|
static int um_idi_open(struct inode *inode, struct file *file)
|
|
{
|
|
cycle_kernel_lock();
|
|
return (0);
|
|
}
|
|
|
|
|
|
static int um_idi_release(struct inode *inode, struct file *file)
|
|
{
|
|
diva_um_idi_os_context_t *p_os;
|
|
unsigned int adapter_nr;
|
|
int ret = 0;
|
|
|
|
if (!(file->private_data)) {
|
|
ret = -ENODEV;
|
|
goto out;
|
|
}
|
|
|
|
if (!(p_os =
|
|
(diva_um_idi_os_context_t *) diva_um_id_get_os_context(file->private_data))) {
|
|
ret = -ENODEV;
|
|
goto out;
|
|
}
|
|
|
|
adapter_nr = p_os->adapter_nr;
|
|
|
|
if ((ret = remove_entity(file->private_data))) {
|
|
goto out;
|
|
}
|
|
|
|
if (divas_um_idi_delete_entity
|
|
((int) adapter_nr, file->private_data)) {
|
|
ret = -ENODEV;
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
return (ret);
|
|
}
|
|
|
|
int diva_os_get_context_size(void)
|
|
{
|
|
return (sizeof(diva_um_idi_os_context_t));
|
|
}
|
|
|
|
void diva_os_wakeup_read(void *os_context)
|
|
{
|
|
diva_um_idi_os_context_t *p_os =
|
|
(diva_um_idi_os_context_t *) os_context;
|
|
wake_up_interruptible(&p_os->read_wait);
|
|
}
|
|
|
|
void diva_os_wakeup_close(void *os_context)
|
|
{
|
|
diva_um_idi_os_context_t *p_os =
|
|
(diva_um_idi_os_context_t *) os_context;
|
|
wake_up_interruptible(&p_os->close_wait);
|
|
}
|
|
|
|
static
|
|
void diva_um_timer_function(unsigned long data)
|
|
{
|
|
diva_um_idi_os_context_t *p_os = (diva_um_idi_os_context_t *) data;
|
|
|
|
p_os->aborted = 1;
|
|
wake_up_interruptible(&p_os->read_wait);
|
|
wake_up_interruptible(&p_os->close_wait);
|
|
DBG_ERR(("entity removal watchdog"))
|
|
}
|
|
|
|
/*
|
|
** If application exits without entity removal this function will remove
|
|
** entity and block until removal is complete
|
|
*/
|
|
static int remove_entity(void *entity)
|
|
{
|
|
struct task_struct *curtask = current;
|
|
diva_um_idi_os_context_t *p_os;
|
|
|
|
diva_um_idi_stop_wdog(entity);
|
|
|
|
if (!entity) {
|
|
DBG_FTL(("Zero entity on remove"))
|
|
return (0);
|
|
}
|
|
|
|
if (!(p_os =
|
|
(diva_um_idi_os_context_t *)
|
|
diva_um_id_get_os_context(entity))) {
|
|
DBG_FTL(("Zero entity os context on remove"))
|
|
return (0);
|
|
}
|
|
|
|
if (!divas_um_idi_entity_assigned(entity) || p_os->aborted) {
|
|
/*
|
|
Entity is not assigned, also can be removed
|
|
*/
|
|
return (0);
|
|
}
|
|
|
|
DBG_TRC(("E(%08x) check remove", entity))
|
|
|
|
/*
|
|
If adapter not answers on remove request inside of
|
|
10 Sec, then adapter is dead
|
|
*/
|
|
diva_um_idi_start_wdog(entity);
|
|
|
|
{
|
|
DECLARE_WAITQUEUE(wait, curtask);
|
|
|
|
add_wait_queue(&p_os->close_wait, &wait);
|
|
for (;;) {
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
if (!divas_um_idi_entity_start_remove(entity)
|
|
|| p_os->aborted) {
|
|
break;
|
|
}
|
|
schedule();
|
|
}
|
|
set_current_state(TASK_RUNNING);
|
|
remove_wait_queue(&p_os->close_wait, &wait);
|
|
}
|
|
|
|
DBG_TRC(("E(%08x) start remove", entity))
|
|
{
|
|
DECLARE_WAITQUEUE(wait, curtask);
|
|
|
|
add_wait_queue(&p_os->close_wait, &wait);
|
|
for (;;) {
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
if (!divas_um_idi_entity_assigned(entity)
|
|
|| p_os->aborted) {
|
|
break;
|
|
}
|
|
schedule();
|
|
}
|
|
set_current_state(TASK_RUNNING);
|
|
remove_wait_queue(&p_os->close_wait, &wait);
|
|
}
|
|
|
|
DBG_TRC(("E(%08x) remove complete, aborted:%d", entity,
|
|
p_os->aborted))
|
|
|
|
diva_um_idi_stop_wdog(entity);
|
|
|
|
p_os->aborted = 0;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* timer watchdog
|
|
*/
|
|
void diva_um_idi_start_wdog(void *entity)
|
|
{
|
|
diva_um_idi_os_context_t *p_os;
|
|
|
|
if (entity &&
|
|
((p_os =
|
|
(diva_um_idi_os_context_t *)
|
|
diva_um_id_get_os_context(entity)))) {
|
|
mod_timer(&p_os->diva_timer_id, jiffies + 10 * HZ);
|
|
}
|
|
}
|
|
|
|
void diva_um_idi_stop_wdog(void *entity)
|
|
{
|
|
diva_um_idi_os_context_t *p_os;
|
|
|
|
if (entity &&
|
|
((p_os =
|
|
(diva_um_idi_os_context_t *)
|
|
diva_um_id_get_os_context(entity)))) {
|
|
del_timer(&p_os->diva_timer_id);
|
|
}
|
|
}
|