blob: c214078a89e91a650fac11e763c7746bb09e8ba7 [file] [log] [blame]
/*
*
* 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, "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;
/* FIXME this guy should free the tty driver stored in nd and destroy
* all channel ports */
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;
}