/* * * 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/slab.h> #include <linux/ctype.h> #include <linux/seq_file.h> #include <linux/uaccess.h> #include <linux/vmalloc.h> #include "dgrp_common.h" 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 struct proc_dir_entry *add_proc_file(struct nd_struct *node, struct proc_dir_entry *root, const struct file_operations *fops); /* File operation declarations */ static int parse_write_config(char *); 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 const 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 const 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 const 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 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 void remove_files(struct nd_struct *nd) { char buf[3]; ID_TO_CHAR(nd->nd_ID, buf); dgrp_remove_node_class_sysfs_files(nd); if (nd->nd_net_de) remove_proc_entry(buf, net_entry_pointer); if (nd->nd_mon_de) remove_proc_entry(buf, mon_entry_pointer); if (nd->nd_dpa_de) remove_proc_entry(buf, dpa_entry_pointer); if (nd->nd_ports_de) remove_proc_entry(buf, ports_entry_pointer); } 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) { struct nd_struct *nd; list_for_each_entry(nd, &nd_struct_list, list) remove_files(nd); remove_proc_entry("dgrp/config", NULL); remove_proc_entry("dgrp/info", NULL); remove_proc_entry("dgrp/nodeinfo", NULL); remove_proc_entry("dgrp/net", NULL); remove_proc_entry("dgrp/mon", NULL); remove_proc_entry("dgrp/dpa", NULL); remove_proc_entry("dgrp/ports", NULL); remove_proc_entry("dgrp", NULL); dgrp_proc_dir_entry = NULL; } } void dgrp_register_proc(void) { /* * Register /proc/dgrp */ dgrp_proc_dir_entry = proc_mkdir("dgrp", NULL); if (!dgrp_proc_dir_entry) return; proc_create("dgrp/config", 0644, NULL, &config_proc_file_ops); proc_create("dgrp/info", 0644, NULL, &info_proc_file_ops); proc_create("dgrp/nodeinfo", 0644, NULL, &nodeinfo_proc_file_ops); net_entry_pointer = proc_mkdir_mode("dgrp/net", 0500, NULL); mon_entry_pointer = proc_mkdir_mode("dgrp/mon", 0500, NULL); dpa_entry_pointer = proc_mkdir_mode("dgrp/dpa", 0500, NULL); ports_entry_pointer = proc_mkdir_mode("dgrp/ports", 0500, NULL); } 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); sema_init(&nd->nd_mon_semaphore, 1); sema_init(&nd->nd_net_semaphore, 1); spin_lock_init(&nd->nd_dpa_lock); nd->nd_state = NS_CLOSED; 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; dgrp_create_node_class_sysfs_files(nd); nd->nd_net_de = add_proc_file(nd, net_entry_pointer, &dgrp_net_ops); nd->nd_mon_de = add_proc_file(nd, mon_entry_pointer, &dgrp_mon_ops); nd->nd_dpa_de = add_proc_file(nd, dpa_entry_pointer, &dgrp_dpa_ops); nd->nd_ports_de = add_proc_file(nd, ports_entry_pointer, &dgrp_ports_ops); 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; remove_files(nd); dgrp_tty_uninit(nd); ret = nd_struct_del(nd); if (ret) return ret; kfree(nd); return 0; } static struct proc_dir_entry *add_proc_file(struct nd_struct *node, struct proc_dir_entry *root, const struct file_operations *fops) { char buf[3]; ID_TO_CHAR(node->nd_ID, buf); return proc_create_data(buf, 0600, root, fops, node); }