aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/staging/dgrp/dgrp_specproc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/dgrp/dgrp_specproc.c')
-rw-r--r--drivers/staging/dgrp/dgrp_specproc.c822
1 files changed, 822 insertions, 0 deletions
diff --git a/drivers/staging/dgrp/dgrp_specproc.c b/drivers/staging/dgrp/dgrp_specproc.c
new file mode 100644
index 000000000000..28f5c9ab6b43
--- /dev/null
+++ b/drivers/staging/dgrp/dgrp_specproc.c
@@ -0,0 +1,822 @@
+/*
+ *
+ * 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/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;
+
+ 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;
+
+ 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;
+}