/* * * Copyright 1999 Digi International (www.digi.com) * James Puzzo * * 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 #include #include #include #include #include #include #include #include #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 dgrp_config_proc_write(struct file *file, const char __user *buffer, size_t count, loff_t *pos); static int dgrp_nodeinfo_proc_open(struct inode *inode, struct file *file); static int dgrp_info_proc_open(struct inode *inode, struct file *file); static int dgrp_config_proc_open(struct inode *inode, struct file *file); static struct file_operations config_proc_file_ops = { .owner = THIS_MODULE, .open = dgrp_config_proc_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, .write = dgrp_config_proc_write, }; static struct file_operations info_proc_file_ops = { .owner = THIS_MODULE, .open = dgrp_info_proc_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static struct file_operations nodeinfo_proc_file_ops = { .owner = THIS_MODULE, .open = dgrp_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) { net_entry_pointer = NULL; mon_entry_pointer = NULL; dpa_entry_pointer = NULL; ports_entry_pointer = NULL; if (dgrp_proc_dir_entry) { unregister_proc_table(dgrp_table, 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; if (root == 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_inode(file)); 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_inode(file)); 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 *dgrp_config_proc_start(struct seq_file *m, loff_t *pos) { return seq_list_start_head(&nd_struct_list, *pos); } static void *dgrp_config_proc_next(struct seq_file *p, void *v, loff_t *pos) { return seq_list_next(v, &nd_struct_list, pos); } static void dgrp_config_proc_stop(struct seq_file *m, void *v) { } static int dgrp_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 = dgrp_config_proc_start, .next = dgrp_config_proc_next, .stop = dgrp_config_proc_stop, .show = dgrp_config_proc_show, }; static int dgrp_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 dgrp_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 dgrp_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 dgrp_info_proc_open(struct inode *inode, struct file *file) { return single_open(file, dgrp_info_proc_show, NULL); } static void *dgrp_nodeinfo_start(struct seq_file *m, loff_t *pos) { return seq_list_start_head(&nd_struct_list, *pos); } static void *dgrp_nodeinfo_next(struct seq_file *p, void *v, loff_t *pos) { return seq_list_next(v, &nd_struct_list, pos); } static void dgrp_nodeinfo_stop(struct seq_file *m, void *v) { } static int dgrp_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 = dgrp_nodeinfo_start, .next = dgrp_nodeinfo_next, .stop = dgrp_nodeinfo_stop, .show = dgrp_nodeinfo_show, }; static int dgrp_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); } unregister_dgrp_device(nd->nd_mon_de); unregister_dgrp_device(nd->nd_ports_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; }