diff options
Diffstat (limited to 'drivers/staging/dgrp/dgrp_specproc.c')
-rw-r--r-- | drivers/staging/dgrp/dgrp_specproc.c | 822 |
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; +} |