830 lines
17 KiB
C
830 lines
17 KiB
C
/*
|
|
*
|
|
* Copyright 1999 Digi International (www.digi.com)
|
|
* James Puzzo <jamesp at digi dot com>
|
|
*
|
|
* 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, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the
|
|
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
* PURPOSE. See the GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
*
|
|
* Filename:
|
|
*
|
|
* dgrp_specproc.c
|
|
*
|
|
* Description:
|
|
*
|
|
* Handle the "config" proc entry for the linux realport device driver
|
|
* and provide slots for the "net" and "mon" devices
|
|
*
|
|
* Author:
|
|
*
|
|
* James A. Puzzo
|
|
*
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/tty.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/cred.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/vmalloc.h>
|
|
|
|
#include "dgrp_common.h"
|
|
|
|
static struct dgrp_proc_entry dgrp_table[];
|
|
static struct proc_dir_entry *dgrp_proc_dir_entry;
|
|
|
|
static int dgrp_add_id(long id);
|
|
static int dgrp_remove_nd(struct nd_struct *nd);
|
|
static void unregister_dgrp_device(struct proc_dir_entry *de);
|
|
static void register_dgrp_device(struct nd_struct *node,
|
|
struct proc_dir_entry *root,
|
|
void (*register_hook)(struct proc_dir_entry *de));
|
|
|
|
/* File operation declarations */
|
|
static int dgrp_gen_proc_open(struct inode *, struct file *);
|
|
static int dgrp_gen_proc_close(struct inode *, struct file *);
|
|
static int parse_write_config(char *);
|
|
|
|
|
|
static const struct file_operations dgrp_proc_file_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = dgrp_gen_proc_open,
|
|
.release = dgrp_gen_proc_close,
|
|
};
|
|
|
|
static struct inode_operations proc_inode_ops = {
|
|
.permission = dgrp_inode_permission
|
|
};
|
|
|
|
|
|
static void register_proc_table(struct dgrp_proc_entry *,
|
|
struct proc_dir_entry *);
|
|
static void unregister_proc_table(struct dgrp_proc_entry *,
|
|
struct proc_dir_entry *);
|
|
|
|
static struct dgrp_proc_entry dgrp_net_table[];
|
|
static struct dgrp_proc_entry dgrp_mon_table[];
|
|
static struct dgrp_proc_entry dgrp_ports_table[];
|
|
static struct dgrp_proc_entry dgrp_dpa_table[];
|
|
|
|
static ssize_t config_proc_write(struct file *file, const char __user *buffer,
|
|
size_t count, loff_t *pos);
|
|
|
|
static int nodeinfo_proc_open(struct inode *inode, struct file *file);
|
|
static int info_proc_open(struct inode *inode, struct file *file);
|
|
static int config_proc_open(struct inode *inode, struct file *file);
|
|
|
|
static struct file_operations config_proc_file_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = config_proc_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = seq_release,
|
|
.write = config_proc_write
|
|
};
|
|
|
|
static struct file_operations info_proc_file_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = info_proc_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = seq_release,
|
|
};
|
|
|
|
static struct file_operations nodeinfo_proc_file_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = nodeinfo_proc_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = seq_release,
|
|
};
|
|
|
|
static struct dgrp_proc_entry dgrp_table[] = {
|
|
{
|
|
.id = DGRP_CONFIG,
|
|
.name = "config",
|
|
.mode = 0644,
|
|
.proc_file_ops = &config_proc_file_ops,
|
|
},
|
|
{
|
|
.id = DGRP_INFO,
|
|
.name = "info",
|
|
.mode = 0644,
|
|
.proc_file_ops = &info_proc_file_ops,
|
|
},
|
|
{
|
|
.id = DGRP_NODEINFO,
|
|
.name = "nodeinfo",
|
|
.mode = 0644,
|
|
.proc_file_ops = &nodeinfo_proc_file_ops,
|
|
},
|
|
{
|
|
.id = DGRP_NETDIR,
|
|
.name = "net",
|
|
.mode = 0500,
|
|
.child = dgrp_net_table
|
|
},
|
|
{
|
|
.id = DGRP_MONDIR,
|
|
.name = "mon",
|
|
.mode = 0500,
|
|
.child = dgrp_mon_table
|
|
},
|
|
{
|
|
.id = DGRP_PORTSDIR,
|
|
.name = "ports",
|
|
.mode = 0500,
|
|
.child = dgrp_ports_table
|
|
},
|
|
{
|
|
.id = DGRP_DPADIR,
|
|
.name = "dpa",
|
|
.mode = 0500,
|
|
.child = dgrp_dpa_table
|
|
}
|
|
};
|
|
|
|
static struct proc_dir_entry *net_entry_pointer;
|
|
static struct proc_dir_entry *mon_entry_pointer;
|
|
static struct proc_dir_entry *dpa_entry_pointer;
|
|
static struct proc_dir_entry *ports_entry_pointer;
|
|
|
|
static struct dgrp_proc_entry dgrp_net_table[] = {
|
|
{0}
|
|
};
|
|
|
|
static struct dgrp_proc_entry dgrp_mon_table[] = {
|
|
{0}
|
|
};
|
|
|
|
static struct dgrp_proc_entry dgrp_ports_table[] = {
|
|
{0}
|
|
};
|
|
|
|
static struct dgrp_proc_entry dgrp_dpa_table[] = {
|
|
{0}
|
|
};
|
|
|
|
void dgrp_unregister_proc(void)
|
|
{
|
|
unregister_proc_table(dgrp_table, dgrp_proc_dir_entry);
|
|
net_entry_pointer = NULL;
|
|
mon_entry_pointer = NULL;
|
|
dpa_entry_pointer = NULL;
|
|
ports_entry_pointer = NULL;
|
|
|
|
if (dgrp_proc_dir_entry) {
|
|
remove_proc_entry(dgrp_proc_dir_entry->name,
|
|
dgrp_proc_dir_entry->parent);
|
|
dgrp_proc_dir_entry = NULL;
|
|
}
|
|
|
|
}
|
|
|
|
void dgrp_register_proc(void)
|
|
{
|
|
/*
|
|
* Register /proc/dgrp
|
|
*/
|
|
dgrp_proc_dir_entry = proc_create("dgrp", S_IFDIR, NULL,
|
|
&dgrp_proc_file_ops);
|
|
register_proc_table(dgrp_table, dgrp_proc_dir_entry);
|
|
}
|
|
|
|
/*
|
|
* /proc/sys support
|
|
*/
|
|
static int dgrp_proc_match(int len, const char *name, struct proc_dir_entry *de)
|
|
{
|
|
if (!de || !de->low_ino)
|
|
return 0;
|
|
if (de->namelen != len)
|
|
return 0;
|
|
return !memcmp(name, de->name, len);
|
|
}
|
|
|
|
|
|
/*
|
|
* Scan the entries in table and add them all to /proc at the position
|
|
* referred to by "root"
|
|
*/
|
|
static void register_proc_table(struct dgrp_proc_entry *table,
|
|
struct proc_dir_entry *root)
|
|
{
|
|
struct proc_dir_entry *de;
|
|
int len;
|
|
mode_t mode;
|
|
|
|
if (table == NULL)
|
|
return;
|
|
|
|
for (; table->id; table++) {
|
|
/* Can't do anything without a proc name. */
|
|
if (!table->name)
|
|
continue;
|
|
|
|
/* Maybe we can't do anything with it... */
|
|
if (!table->proc_file_ops &&
|
|
!table->child) {
|
|
pr_warn("dgrp: Can't register %s\n",
|
|
table->name);
|
|
continue;
|
|
}
|
|
|
|
len = strlen(table->name);
|
|
mode = table->mode;
|
|
|
|
de = NULL;
|
|
if (!table->child)
|
|
mode |= S_IFREG;
|
|
else {
|
|
mode |= S_IFDIR;
|
|
for (de = root->subdir; de; de = de->next) {
|
|
if (dgrp_proc_match(len, table->name, de))
|
|
break;
|
|
}
|
|
/* If the subdir exists already, de is non-NULL */
|
|
}
|
|
|
|
if (!de) {
|
|
de = create_proc_entry(table->name, mode, root);
|
|
if (!de)
|
|
continue;
|
|
de->data = (void *) table;
|
|
if (!table->child) {
|
|
de->proc_iops = &proc_inode_ops;
|
|
if (table->proc_file_ops)
|
|
de->proc_fops = table->proc_file_ops;
|
|
else
|
|
de->proc_fops = &dgrp_proc_file_ops;
|
|
}
|
|
}
|
|
table->de = de;
|
|
if (de->mode & S_IFDIR)
|
|
register_proc_table(table->child, de);
|
|
|
|
if (table->id == DGRP_NETDIR)
|
|
net_entry_pointer = de;
|
|
|
|
if (table->id == DGRP_MONDIR)
|
|
mon_entry_pointer = de;
|
|
|
|
if (table->id == DGRP_DPADIR)
|
|
dpa_entry_pointer = de;
|
|
|
|
if (table->id == DGRP_PORTSDIR)
|
|
ports_entry_pointer = de;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Unregister a /proc sysctl table and any subdirectories.
|
|
*/
|
|
static void unregister_proc_table(struct dgrp_proc_entry *table,
|
|
struct proc_dir_entry *root)
|
|
{
|
|
struct proc_dir_entry *de;
|
|
struct nd_struct *tmp;
|
|
|
|
if (table == NULL)
|
|
return;
|
|
|
|
list_for_each_entry(tmp, &nd_struct_list, list) {
|
|
if ((table == dgrp_net_table) && (tmp->nd_net_de)) {
|
|
unregister_dgrp_device(tmp->nd_net_de);
|
|
dgrp_remove_node_class_sysfs_files(tmp);
|
|
}
|
|
|
|
if ((table == dgrp_mon_table) && (tmp->nd_mon_de))
|
|
unregister_dgrp_device(tmp->nd_mon_de);
|
|
|
|
if ((table == dgrp_dpa_table) && (tmp->nd_dpa_de))
|
|
unregister_dgrp_device(tmp->nd_dpa_de);
|
|
|
|
if ((table == dgrp_ports_table) && (tmp->nd_ports_de))
|
|
unregister_dgrp_device(tmp->nd_ports_de);
|
|
}
|
|
|
|
for (; table->id; table++) {
|
|
de = table->de;
|
|
|
|
if (!de)
|
|
continue;
|
|
if (de->mode & S_IFDIR) {
|
|
if (!table->child) {
|
|
pr_alert("dgrp: malformed sysctl tree on free\n");
|
|
continue;
|
|
}
|
|
unregister_proc_table(table->child, de);
|
|
|
|
/* Don't unregister directories which still have entries */
|
|
if (de->subdir)
|
|
continue;
|
|
}
|
|
|
|
/* Don't unregister proc entries that are still being used.. */
|
|
if ((atomic_read(&de->count)) != 1) {
|
|
pr_alert("proc entry %s in use, not removing\n",
|
|
de->name);
|
|
continue;
|
|
}
|
|
|
|
remove_proc_entry(de->name, de->parent);
|
|
table->de = NULL;
|
|
}
|
|
}
|
|
|
|
static int dgrp_gen_proc_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct proc_dir_entry *de;
|
|
struct dgrp_proc_entry *entry;
|
|
int ret = 0;
|
|
|
|
de = (struct proc_dir_entry *) PDE(file->f_dentry->d_inode);
|
|
if (!de || !de->data) {
|
|
ret = -ENXIO;
|
|
goto done;
|
|
}
|
|
|
|
entry = (struct dgrp_proc_entry *) de->data;
|
|
if (!entry) {
|
|
ret = -ENXIO;
|
|
goto done;
|
|
}
|
|
|
|
down(&entry->excl_sem);
|
|
|
|
if (entry->excl_cnt)
|
|
ret = -EBUSY;
|
|
else
|
|
entry->excl_cnt++;
|
|
|
|
up(&entry->excl_sem);
|
|
|
|
done:
|
|
return ret;
|
|
}
|
|
|
|
static int dgrp_gen_proc_close(struct inode *inode, struct file *file)
|
|
{
|
|
struct proc_dir_entry *de;
|
|
struct dgrp_proc_entry *entry;
|
|
|
|
de = (struct proc_dir_entry *) PDE(file->f_dentry->d_inode);
|
|
if (!de || !de->data)
|
|
goto done;
|
|
|
|
entry = (struct dgrp_proc_entry *) de->data;
|
|
if (!entry)
|
|
goto done;
|
|
|
|
down(&entry->excl_sem);
|
|
|
|
if (entry->excl_cnt)
|
|
entry->excl_cnt = 0;
|
|
|
|
up(&entry->excl_sem);
|
|
|
|
done:
|
|
return 0;
|
|
}
|
|
|
|
static void *config_proc_start(struct seq_file *m, loff_t *pos)
|
|
{
|
|
return seq_list_start_head(&nd_struct_list, *pos);
|
|
}
|
|
|
|
static void *config_proc_next(struct seq_file *p, void *v, loff_t *pos)
|
|
{
|
|
return seq_list_next(v, &nd_struct_list, pos);
|
|
}
|
|
|
|
static void config_proc_stop(struct seq_file *m, void *v)
|
|
{
|
|
}
|
|
|
|
static int config_proc_show(struct seq_file *m, void *v)
|
|
{
|
|
struct nd_struct *nd;
|
|
char tmp_id[4];
|
|
|
|
if (v == &nd_struct_list) {
|
|
seq_puts(m, "#-----------------------------------------------------------------------------\n");
|
|
seq_puts(m, "# Avail\n");
|
|
seq_puts(m, "# ID Major State Ports\n");
|
|
return 0;
|
|
}
|
|
|
|
nd = list_entry(v, struct nd_struct, list);
|
|
|
|
ID_TO_CHAR(nd->nd_ID, tmp_id);
|
|
|
|
seq_printf(m, " %-2.2s %-5ld %-10.10s %-5d\n",
|
|
tmp_id,
|
|
nd->nd_major,
|
|
ND_STATE_STR(nd->nd_state),
|
|
nd->nd_chan_count);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct seq_operations proc_config_ops = {
|
|
.start = config_proc_start,
|
|
.next = config_proc_next,
|
|
.stop = config_proc_stop,
|
|
.show = config_proc_show
|
|
};
|
|
|
|
static int config_proc_open(struct inode *inode, struct file *file)
|
|
{
|
|
return seq_open(file, &proc_config_ops);
|
|
}
|
|
|
|
|
|
/*
|
|
* When writing configuration information, each "record" (i.e. each
|
|
* write) is treated as an independent request. See the "parse"
|
|
* description for more details.
|
|
*/
|
|
static ssize_t config_proc_write(struct file *file, const char __user *buffer,
|
|
size_t count, loff_t *pos)
|
|
{
|
|
ssize_t retval;
|
|
char *inbuf, *sp;
|
|
char *line, *ldelim;
|
|
|
|
if (count > 32768)
|
|
return -EINVAL;
|
|
|
|
inbuf = sp = vzalloc(count + 1);
|
|
if (!inbuf)
|
|
return -ENOMEM;
|
|
|
|
if (copy_from_user(inbuf, buffer, count)) {
|
|
retval = -EFAULT;
|
|
goto done;
|
|
}
|
|
|
|
inbuf[count] = 0;
|
|
|
|
ldelim = "\n";
|
|
|
|
line = strpbrk(sp, ldelim);
|
|
while (line) {
|
|
*line = 0;
|
|
retval = parse_write_config(sp);
|
|
if (retval)
|
|
goto done;
|
|
|
|
sp = line + 1;
|
|
line = strpbrk(sp, ldelim);
|
|
}
|
|
|
|
retval = count;
|
|
done:
|
|
vfree(inbuf);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* ------------------------------------------------------------------------
|
|
*
|
|
* The following are the functions to parse input
|
|
*
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
static inline char *skip_past_ws(const char *str)
|
|
{
|
|
while ((*str) && !isspace(*str))
|
|
++str;
|
|
|
|
return skip_spaces(str);
|
|
}
|
|
|
|
static int parse_id(char **c, char *cID)
|
|
{
|
|
int tmp = **c;
|
|
|
|
if (isalnum(tmp) || (tmp == '_'))
|
|
cID[0] = tmp;
|
|
else
|
|
return -EINVAL;
|
|
|
|
(*c)++; tmp = **c;
|
|
|
|
if (isalnum(tmp) || (tmp == '_')) {
|
|
cID[1] = tmp;
|
|
(*c)++;
|
|
} else
|
|
cID[1] = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_add_config(char *buf)
|
|
{
|
|
char *c = buf;
|
|
int retval;
|
|
char cID[2];
|
|
long ID;
|
|
|
|
c = skip_past_ws(c);
|
|
|
|
retval = parse_id(&c, cID);
|
|
if (retval < 0)
|
|
return retval;
|
|
|
|
ID = CHAR_TO_ID(cID);
|
|
|
|
c = skip_past_ws(c);
|
|
|
|
return dgrp_add_id(ID);
|
|
}
|
|
|
|
static int parse_del_config(char *buf)
|
|
{
|
|
char *c = buf;
|
|
int retval;
|
|
struct nd_struct *nd;
|
|
char cID[2];
|
|
long ID;
|
|
long major;
|
|
|
|
c = skip_past_ws(c);
|
|
|
|
retval = parse_id(&c, cID);
|
|
if (retval < 0)
|
|
return retval;
|
|
|
|
ID = CHAR_TO_ID(cID);
|
|
|
|
c = skip_past_ws(c);
|
|
|
|
retval = kstrtol(c, 10, &major);
|
|
if (retval)
|
|
return retval;
|
|
|
|
nd = nd_struct_get(major);
|
|
if (!nd)
|
|
return -EINVAL;
|
|
|
|
if ((nd->nd_major != major) || (nd->nd_ID != ID))
|
|
return -EINVAL;
|
|
|
|
return dgrp_remove_nd(nd);
|
|
}
|
|
|
|
static int parse_chg_config(char *buf)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* The passed character buffer represents a single configuration request.
|
|
* If the first character is a "+", it is parsed as a request to add a
|
|
* PortServer
|
|
* If the first character is a "-", it is parsed as a request to delete a
|
|
* PortServer
|
|
* If the first character is a "*", it is parsed as a request to change a
|
|
* PortServer
|
|
* Any other character (including whitespace) causes the record to be
|
|
* ignored.
|
|
*/
|
|
static int parse_write_config(char *buf)
|
|
{
|
|
int retval;
|
|
|
|
switch (buf[0]) {
|
|
case '+':
|
|
retval = parse_add_config(buf);
|
|
break;
|
|
case '-':
|
|
retval = parse_del_config(buf);
|
|
break;
|
|
case '*':
|
|
retval = parse_chg_config(buf);
|
|
break;
|
|
default:
|
|
retval = -EINVAL;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int info_proc_show(struct seq_file *m, void *v)
|
|
{
|
|
seq_printf(m, "version: %s\n", DIGI_VERSION);
|
|
seq_puts(m, "register_with_sysfs: 1\n");
|
|
seq_printf(m, "rawreadok: 0x%08x\t(%d)\n",
|
|
dgrp_rawreadok, dgrp_rawreadok);
|
|
seq_printf(m, "pollrate: 0x%08x\t(%d)\n",
|
|
dgrp_poll_tick, dgrp_poll_tick);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int info_proc_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, info_proc_show, NULL);
|
|
}
|
|
|
|
|
|
static void *nodeinfo_start(struct seq_file *m, loff_t *pos)
|
|
{
|
|
return seq_list_start_head(&nd_struct_list, *pos);
|
|
}
|
|
|
|
static void *nodeinfo_next(struct seq_file *p, void *v, loff_t *pos)
|
|
{
|
|
return seq_list_next(v, &nd_struct_list, pos);
|
|
}
|
|
|
|
static void nodeinfo_stop(struct seq_file *m, void *v)
|
|
{
|
|
}
|
|
|
|
static int nodeinfo_show(struct seq_file *m, void *v)
|
|
{
|
|
struct nd_struct *nd;
|
|
char hwver[8];
|
|
char swver[8];
|
|
char tmp_id[4];
|
|
|
|
if (v == &nd_struct_list) {
|
|
seq_puts(m, "#-----------------------------------------------------------------------------\n");
|
|
seq_puts(m, "# HW HW SW\n");
|
|
seq_puts(m, "# ID State Version ID Version Description\n");
|
|
return 0;
|
|
}
|
|
|
|
nd = list_entry(v, struct nd_struct, list);
|
|
|
|
ID_TO_CHAR(nd->nd_ID, tmp_id);
|
|
|
|
if (nd->nd_state == NS_READY) {
|
|
sprintf(hwver, "%d.%d", (nd->nd_hw_ver >> 8) & 0xff,
|
|
nd->nd_hw_ver & 0xff);
|
|
sprintf(swver, "%d.%d", (nd->nd_sw_ver >> 8) & 0xff,
|
|
nd->nd_sw_ver & 0xff);
|
|
seq_printf(m, " %-2.2s %-10.10s %-7.7s %-3d %-7.7s %-35.35s\n",
|
|
tmp_id,
|
|
ND_STATE_STR(nd->nd_state),
|
|
hwver,
|
|
nd->nd_hw_id,
|
|
swver,
|
|
nd->nd_ps_desc);
|
|
|
|
} else {
|
|
seq_printf(m, " %-2.2s %-10.10s\n",
|
|
tmp_id,
|
|
ND_STATE_STR(nd->nd_state));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static const struct seq_operations nodeinfo_ops = {
|
|
.start = nodeinfo_start,
|
|
.next = nodeinfo_next,
|
|
.stop = nodeinfo_stop,
|
|
.show = nodeinfo_show
|
|
};
|
|
|
|
static int nodeinfo_proc_open(struct inode *inode, struct file *file)
|
|
{
|
|
return seq_open(file, &nodeinfo_ops);
|
|
}
|
|
|
|
/**
|
|
* dgrp_add_id() -- creates new nd struct and adds it to list
|
|
* @id: id of device to add
|
|
*/
|
|
static int dgrp_add_id(long id)
|
|
{
|
|
struct nd_struct *nd;
|
|
int ret;
|
|
int i;
|
|
|
|
nd = kzalloc(sizeof(struct nd_struct), GFP_KERNEL);
|
|
if (!nd)
|
|
return -ENOMEM;
|
|
|
|
nd->nd_major = 0;
|
|
nd->nd_ID = id;
|
|
|
|
spin_lock_init(&nd->nd_lock);
|
|
|
|
init_waitqueue_head(&nd->nd_tx_waitq);
|
|
init_waitqueue_head(&nd->nd_mon_wqueue);
|
|
init_waitqueue_head(&nd->nd_dpa_wqueue);
|
|
for (i = 0; i < SEQ_MAX; i++)
|
|
init_waitqueue_head(&nd->nd_seq_wque[i]);
|
|
|
|
/* setup the structures to get the major number */
|
|
ret = dgrp_tty_init(nd);
|
|
if (ret)
|
|
goto error_out;
|
|
|
|
nd->nd_major = nd->nd_serial_ttdriver->major;
|
|
|
|
ret = nd_struct_add(nd);
|
|
if (ret)
|
|
goto error_out;
|
|
|
|
register_dgrp_device(nd, net_entry_pointer, dgrp_register_net_hook);
|
|
register_dgrp_device(nd, mon_entry_pointer, dgrp_register_mon_hook);
|
|
register_dgrp_device(nd, dpa_entry_pointer, dgrp_register_dpa_hook);
|
|
register_dgrp_device(nd, ports_entry_pointer,
|
|
dgrp_register_ports_hook);
|
|
|
|
return 0;
|
|
|
|
error_out:
|
|
kfree(nd);
|
|
return ret;
|
|
|
|
}
|
|
|
|
static int dgrp_remove_nd(struct nd_struct *nd)
|
|
{
|
|
int ret;
|
|
|
|
/* Check to see if the selected structure is in use */
|
|
if (nd->nd_tty_ref_cnt)
|
|
return -EBUSY;
|
|
|
|
if (nd->nd_net_de) {
|
|
unregister_dgrp_device(nd->nd_net_de);
|
|
dgrp_remove_node_class_sysfs_files(nd);
|
|
}
|
|
|
|
if (nd->nd_mon_de)
|
|
unregister_dgrp_device(nd->nd_mon_de);
|
|
|
|
if (nd->nd_ports_de)
|
|
unregister_dgrp_device(nd->nd_ports_de);
|
|
|
|
if (nd->nd_dpa_de)
|
|
unregister_dgrp_device(nd->nd_dpa_de);
|
|
|
|
dgrp_tty_uninit(nd);
|
|
|
|
ret = nd_struct_del(nd);
|
|
if (ret)
|
|
return ret;
|
|
|
|
kfree(nd);
|
|
return 0;
|
|
}
|
|
|
|
static void register_dgrp_device(struct nd_struct *node,
|
|
struct proc_dir_entry *root,
|
|
void (*register_hook)(struct proc_dir_entry *de))
|
|
{
|
|
char buf[3];
|
|
struct proc_dir_entry *de;
|
|
|
|
ID_TO_CHAR(node->nd_ID, buf);
|
|
|
|
de = create_proc_entry(buf, 0600 | S_IFREG, root);
|
|
if (!de)
|
|
return;
|
|
|
|
de->data = (void *) node;
|
|
|
|
if (register_hook)
|
|
register_hook(de);
|
|
|
|
}
|
|
|
|
static void unregister_dgrp_device(struct proc_dir_entry *de)
|
|
{
|
|
if (!de)
|
|
return;
|
|
|
|
/* Don't unregister proc entries that are still being used.. */
|
|
if ((atomic_read(&de->count)) != 1) {
|
|
pr_alert("%s - proc entry %s in use. Not removing.\n",
|
|
__func__, de->name);
|
|
return;
|
|
}
|
|
|
|
remove_proc_entry(de->name, de->parent);
|
|
de = NULL;
|
|
}
|