diff options
Diffstat (limited to 'drivers/staging/dgnc/dgnc_proc.c')
-rw-r--r-- | drivers/staging/dgnc/dgnc_proc.c | 1551 |
1 files changed, 1551 insertions, 0 deletions
diff --git a/drivers/staging/dgnc/dgnc_proc.c b/drivers/staging/dgnc/dgnc_proc.c new file mode 100644 index 0000000..8fbaf3b --- /dev/null +++ b/drivers/staging/dgnc/dgnc_proc.c @@ -0,0 +1,1551 @@ +/* + * Copyright 2003 Digi International (www.digi.com) + * Scott H Kilau <Scott_Kilau 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * + * NOTE TO LINUX KERNEL HACKERS: DO NOT REFORMAT THIS CODE! + * + * This is shared code between Digi's CVS archive and the + * Linux Kernel sources. + * Changing the source just for reformatting needlessly breaks + * our CVS diff history. + * + * Send any bug fixes/changes to: Eng.Linux at digi dot com. + * Thank you. + * + * + * $Id: dgnc_proc.c,v 1.3 2011/06/22 12:16:35 markh Exp $ + */ + +#include <linux/kernel.h> +#include <linux/version.h> +#include <linux/sched.h> /* For jiffies, task states */ +#include <linux/interrupt.h> /* For tasklet and interrupt structs/defines */ +#include <linux/module.h> +#include <linux/ctype.h> +#include <linux/proc_fs.h> +#include <linux/serial_reg.h> +#include <linux/sched.h> /* For in_egroup_p() */ +#include <linux/string.h> +#include <asm/uaccess.h> /* For copy_from_user/copy_to_user */ + +#include "dgnc_driver.h" +#include "dgnc_proc.h" +#include "dgnc_mgmt.h" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37) +#define init_MUTEX(sem) sema_init(sem, 1) +#define DECLARE_MUTEX(name) \ + struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1) +#endif + + +/* The /proc/dgnc directory */ +static struct proc_dir_entry *ProcDGNC; + + +/* File operation declarations */ +static int dgnc_gen_proc_open(struct inode *, struct file *); +static int dgnc_gen_proc_close(struct inode *, struct file *); +static ssize_t dgnc_gen_proc_read(struct file *file, char __user *buf, size_t count, loff_t *ppos); +static ssize_t dgnc_gen_proc_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos); + +static int dgnc_proc_chk_perm(struct inode *, int); + +static const struct file_operations dgnc_proc_file_ops = +{ + .owner = THIS_MODULE, + .read = dgnc_gen_proc_read, /* read */ + .write = dgnc_gen_proc_write, /* write */ + .open = dgnc_gen_proc_open, /* open */ + .release = dgnc_gen_proc_close, /* release */ +}; + + +static struct inode_operations dgnc_proc_inode_ops = +{ + .permission = dgnc_proc_chk_perm +}; + + +static void dgnc_register_proc_table(struct dgnc_proc_entry *, struct proc_dir_entry *); +static void dgnc_unregister_proc_table(struct dgnc_proc_entry *, struct proc_dir_entry *); +static void dgnc_remove_proc_entry(struct proc_dir_entry *pde); + + +/* Stuff in /proc/ */ +static int dgnc_read_info(struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos); +static int dgnc_write_info(struct dgnc_proc_entry *table, int dir, struct file *filp, + const char __user *buffer, ssize_t *lenp, loff_t *ppos); + +static int dgnc_read_mknod(struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos); + +static struct dgnc_proc_entry dgnc_table[] = { + {DGNC_INFO, "info", 0600, NULL, NULL, NULL, &dgnc_read_info, &dgnc_write_info, + NULL, __SEMAPHORE_INITIALIZER(dgnc_table[0].excl_sem, 1), 0, NULL }, + {DGNC_MKNOD, "mknod", 0600, NULL, NULL, NULL, &dgnc_read_mknod, NULL, + NULL, __SEMAPHORE_INITIALIZER(dgnc_table[1].excl_sem, 1), 0, NULL }, + {0} +}; + + +/* Stuff in /proc/<board>/ */ +static int dgnc_read_board_info(struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos); +static int dgnc_read_board_vpd(struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos); +static int dgnc_read_board_mknod(struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos); +static int dgnc_read_board_ttystats(struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos); +static int dgnc_read_board_ttyintr(struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos); +static int dgnc_read_board_ttyflags(struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos); + +static struct dgnc_proc_entry dgnc_board_table[] = { + {DGNC_BOARD_INFO, "info", 0600, NULL, NULL, NULL, &dgnc_read_board_info, NULL, + NULL, __SEMAPHORE_INITIALIZER(dgnc_board_table[0].excl_sem, 1), 0, NULL }, + {DGNC_BOARD_VPD, "vpd", 0600, NULL, NULL, NULL, &dgnc_read_board_vpd, NULL, + NULL, __SEMAPHORE_INITIALIZER(dgnc_board_table[1].excl_sem, 1), 0, NULL }, + {DGNC_BOARD_TTYSTATS, "stats", 0600, NULL, NULL, NULL, &dgnc_read_board_ttystats, NULL, + NULL, __SEMAPHORE_INITIALIZER(dgnc_board_table[2].excl_sem, 1), 0, NULL }, + {DGNC_BOARD_TTYINTR, "intr", 0600, NULL, NULL, NULL, &dgnc_read_board_ttyintr, NULL, + NULL, __SEMAPHORE_INITIALIZER(dgnc_board_table[3].excl_sem, 1), 0, NULL }, + {DGNC_BOARD_TTYFLAGS, "flags", 0600, NULL, NULL, NULL, &dgnc_read_board_ttyflags, NULL, + NULL, __SEMAPHORE_INITIALIZER(dgnc_board_table[4].excl_sem, 1), 0, NULL }, + {DGNC_BOARD_MKNOD, "mknod", 0600, NULL, NULL, NULL, &dgnc_read_board_mknod, NULL, + NULL, __SEMAPHORE_INITIALIZER(dgnc_board_table[5].excl_sem, 1), 0, NULL }, + {0} +}; + + +/* Stuff in /proc/<board>/<channel> */ +static int dgnc_read_channel_info(struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos); +static int dgnc_open_channel_sniff(struct dgnc_proc_entry *table, int dir, struct file *filp, + void *buffer, ssize_t *lenp, loff_t *ppos); +static int dgnc_close_channel_sniff(struct dgnc_proc_entry *table, int dir, struct file *filp, + void *buffer, ssize_t *lenp, loff_t *ppos); +static int dgnc_read_channel_sniff(struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos); +static int dgnc_read_channel_custom_ttyname(struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos); +static int dgnc_read_channel_custom_prname(struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos); + +static struct dgnc_proc_entry dgnc_channel_table[] = { + {DGNC_PORT_INFO, "info", 0600, NULL, NULL, NULL, &dgnc_read_channel_info, NULL, + NULL, __SEMAPHORE_INITIALIZER(dgnc_channel_table[0].excl_sem, 1), 0, NULL }, + {DGNC_PORT_SNIFF, "sniff", 0600, NULL, &dgnc_open_channel_sniff, &dgnc_close_channel_sniff, &dgnc_read_channel_sniff, NULL, + NULL, __SEMAPHORE_INITIALIZER(dgnc_channel_table[1].excl_sem, 1), 0, NULL}, + {DGNC_PORT_CUSTOM_TTYNAME, "ttyname", 0600, NULL, NULL, NULL, &dgnc_read_channel_custom_ttyname, NULL, + NULL, __SEMAPHORE_INITIALIZER(dgnc_channel_table[2].excl_sem, 1), 0, NULL }, + {DGNC_PORT_CUSTOM_PRNAME, "prname", 0600, NULL, NULL, NULL, &dgnc_read_channel_custom_prname, NULL, + NULL, __SEMAPHORE_INITIALIZER(dgnc_channel_table[3].excl_sem, 1), 0, NULL }, + {0} +}; + + +/* + * dgnc_test_perm does NOT grant the superuser all rights automatically, because + * some entries are readonly even to root. + */ +static inline int dgnc_test_perm(int mode, int op) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) + if (!current->euid) +#else + if (!current_euid()) +#endif + mode >>= 6; + else if (in_egroup_p(0)) + mode >>= 3; + if ((mode & op & 0007) == op) + return 0; + if (capable(CAP_SYS_ADMIN)) + return 0; + return -EACCES; +} + + +/* + * /proc/sys support + */ +static inline int dgnc_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 dgnc_register_proc_table(struct dgnc_proc_entry *table, struct proc_dir_entry *root) +{ + struct proc_dir_entry *de; + int len; + mode_t mode; + + for (; table->magic; table++) { + /* Can't do anything without a proc name. */ + if (!table->name) { + DPR_PROC(("dgnc_register_proc_table, no name...\n")); + continue; + } + + /* Maybe we can't do anything with it... */ + if (!table->read_handler && !table->write_handler && !table->child) { + DPR_PROC((KERN_WARNING "DGNC PROC: 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 (dgnc_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 = &dgnc_proc_inode_ops; + de->proc_fops = &dgnc_proc_file_ops; + } + } + + table->de = de; + + if (de->mode & S_IFDIR) + dgnc_register_proc_table(table->child, de); + + } +} + + + +/* + * Unregister a /proc sysctl table and any subdirectories. + */ +static void dgnc_unregister_proc_table(struct dgnc_proc_entry *table, struct proc_dir_entry *root) +{ + struct proc_dir_entry *de; + + for (; table->magic; table++) { + if (!(de = table->de)) + continue; + + if (de->mode & S_IFDIR) { + if (!table->child) { + DPR_PROC((KERN_ALERT "Help - malformed sysctl tree on free\n")); + continue; + } + + /* recurse down into subdirectory... */ + DPR_PROC(("Recursing down a directory...\n")); + dgnc_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) { + DPR_PROC(("proc entry in use... Not removing...\n")); + continue; + } + + dgnc_remove_proc_entry(de); + table->de = NULL; + } +} + + + +static int dgnc_gen_proc_open(struct inode *inode, struct file *file) +{ + struct proc_dir_entry *de; + struct dgnc_proc_entry *entry; + int (*handler) (struct dgnc_proc_entry *table, int dir, struct file *filp, + void *buffer, ssize_t *lenp, loff_t *ppos); + int ret = 0, error = 0; + + de = (struct proc_dir_entry *) PDE(file->f_dentry->d_inode); + if (!de || !de->data) { + ret = -ENXIO; + goto done; + } + + entry = (struct dgnc_proc_entry *) de->data; + if (!entry) { + ret = -ENXIO; + goto done; + } + + down(&entry->excl_sem); + + if (entry->excl_cnt) { + ret = -EBUSY; + } else { + entry->excl_cnt++; + + handler = entry->open_handler; + if (handler) { + error = (*handler) (entry, OUTBOUND, file, NULL, NULL, NULL); + if (error) { + entry->excl_cnt--; + ret = error; + } + } + } + + up(&entry->excl_sem); + +done: + + return ret; +} + + +static int dgnc_gen_proc_close(struct inode *inode, struct file *file) +{ + struct proc_dir_entry *de; + int (*handler) (struct dgnc_proc_entry *table, int dir, struct file *filp, + void *buffer, ssize_t *lenp, loff_t *ppos); + struct dgnc_proc_entry *entry; + int error = 0; + + de = (struct proc_dir_entry *) PDE(file->f_dentry->d_inode); + if (!de || !de->data) + goto done; + + entry = (struct dgnc_proc_entry *) de->data; + if (!entry) + goto done; + + down(&entry->excl_sem); + + if (entry->excl_cnt) + entry->excl_cnt = 0; + + + handler = entry->close_handler; + if (handler) { + error = (*handler) (entry, OUTBOUND, file, NULL, NULL, NULL); + } + + up(&entry->excl_sem); + +done: + return 0; +} + + +static ssize_t dgnc_gen_proc_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) +{ + struct proc_dir_entry *de; + struct dgnc_proc_entry *entry; + int (*handler) (struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos2); + ssize_t res; + ssize_t error; + + de = (struct proc_dir_entry*) PDE(file->f_dentry->d_inode); + if (!de || !de->data) + return -ENXIO; + + entry = (struct dgnc_proc_entry *) de->data; + if (!entry) + return -ENXIO; + + /* Test for read permission */ + if (dgnc_test_perm(entry->mode, 4)) + return -EPERM; + + res = count; + + handler = entry->read_handler; + if (!handler) + return -ENXIO; + + error = (*handler) (entry, OUTBOUND, file, buf, &res, ppos); + if (error) + return error; + + return res; +} + + +static ssize_t dgnc_gen_proc_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct proc_dir_entry *de; + struct dgnc_proc_entry *entry; + int (*handler) (struct dgnc_proc_entry *table, int dir, struct file *filp, + const char __user *buffer, ssize_t *lenp, loff_t *ppos2); + ssize_t res; + ssize_t error; + + de = (struct proc_dir_entry *) PDE(file->f_dentry->d_inode); + if (!de || !de->data) + return -ENXIO; + + entry = (struct dgnc_proc_entry *) de->data; + if (!entry) + return -ENXIO; + + /* Test for write permission */ + if (dgnc_test_perm(entry->mode, 2)) + return -EPERM; + + res = count; + + handler = entry->write_handler; + if (!handler) + return -ENXIO; + + error = (*handler) (entry, INBOUND, file, buf, &res, ppos); + if (error) + return error; + + return res; +} + + +static int dgnc_proc_chk_perm(struct inode *inode, int op) +{ + return dgnc_test_perm(inode->i_mode, op); +} + + +/* + * Return what is (hopefully) useful information about the + * driver. + */ +static int dgnc_read_info(struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos) +{ + static int done = 0; + static char buf[4096]; + char *p = buf; + + DPR_PROC(("dgnc_proc_info\n")); + + if (done) { + done = 0; + *lenp = 0; + return 0; + } + + p += sprintf(p, "Driver:\t\t%s\n", DG_NAME); + p += sprintf(p, "\n"); + p += sprintf(p, "Debug:\t\t0x%x\n", dgnc_debug); + p += sprintf(p, "Sysfs Support:\t0x1\n"); + p += sprintf(p, "Rawreadok:\t0x%x\n", dgnc_rawreadok); + p += sprintf(p, "Max Boards:\t%d\n", MAXBOARDS); + p += sprintf(p, "Total Boards:\t%d\n", dgnc_NumBoards); + p += sprintf(p, "Poll rate:\t%dms\n", dgnc_poll_tick); + p += sprintf(p, "Poll counter:\t%ld\n", dgnc_poll_counter); + p += sprintf(p, "State:\t\t%s\n", dgnc_driver_state_text[dgnc_driver_state]); + + if (copy_to_user(buffer, buf, (p - (char *) buf))) + return -EFAULT; + + *lenp = p - (char *) buf; + *ppos += p - (char *) buf; + done = 1; + return 0; +} + + +/* + * When writing to the "info" entry point, I actually allow one + * to modify certain variables. This may be a sleazy overload + * of this /proc entry, but I don't want: + * + * a. to clutter /proc more than I have to + * b. to overload the "config" entry, which would be somewhat + * more natural + * c. necessarily advertise the fact this ability exists + * + * The continued support of this feature has not yet been + * guaranteed. + * + * Writing operates on a "state machine" principle. + * + * State 0: waiting for a symbol to start. Waiting for anything + * which isn't " ' = or whitespace. + * State 1: reading a symbol. If the character is a space, move + * to state 2. If =, move to state 3. If " or ', move + * to state 0. + * State 2: Waiting for =... suck whitespace. If anything other + * than whitespace, drop to state 0. + * State 3: Got =. Suck whitespace waiting for value to start. + * If " or ', go to state 4 (and remember which quote it + * was). Otherwise, go to state 5. + * State 4: Reading value, within quotes. Everything is added to + * value up until the matching quote. When you hit the + * matching quote, try to set the variable, then state 0. + * State 5: Reading value, outside quotes. Everything not " ' = + * or whitespace goes in value. Hitting one of the + * terminators tosses us back to state 0 after trying to + * set the variable. + */ +typedef enum { + INFO_NONE, INFO_INT, INFO_CHAR, INFO_SHORT, + INFO_LONG, INFO_PTR, INFO_STRING, INFO_END +} info_proc_var_val; + +static struct { + char *name; + info_proc_var_val type; + int rw; /* 0=readonly */ + void *val_ptr; +} dgnc_info_vars[] = { + { "rawreadok", INFO_INT, 1, (void *) &dgnc_rawreadok }, + { "pollrate", INFO_INT, 1, (void *) &dgnc_poll_tick }, + { NULL, INFO_NONE, 0, NULL }, + { "debug", INFO_LONG, 1, (void *) &dgnc_debug }, + { NULL, INFO_END, 0, NULL } +}; + +static void dgnc_set_info_var(char *name, char *val) +{ + int i; + unsigned long newval; + unsigned char charval; + unsigned short shortval; + unsigned int intval; + + for (i = 0; dgnc_info_vars[i].type != INFO_END; i++) { + if (dgnc_info_vars[i].name) + if (!strcmp(name, dgnc_info_vars[i].name)) + break; + } + + if (dgnc_info_vars[i].type == INFO_END) + return; + if (dgnc_info_vars[i].rw == 0) + return; + if (dgnc_info_vars[i].val_ptr == NULL) + return; + + newval = simple_strtoul(val, NULL, 0 ); + + switch (dgnc_info_vars[i].type) { + case INFO_CHAR: + charval = newval & 0xff; + APR(("Modifying %s (%lx) <= 0x%02x (%d)\n", + name, (long)(dgnc_info_vars[i].val_ptr ), + charval, charval)); + *(uchar *)(dgnc_info_vars[i].val_ptr) = charval; + break; + case INFO_SHORT: + shortval = newval & 0xffff; + APR(("Modifying %s (%lx) <= 0x%04x (%d)\n", + name, (long)(dgnc_info_vars[i].val_ptr), + shortval, shortval)); + *(ushort *)(dgnc_info_vars[i].val_ptr) = shortval; + break; + case INFO_INT: + intval = newval & 0xffffffff; + APR(("Modifying %s (%lx) <= 0x%08x (%d)\n", + name, (long)(dgnc_info_vars[i].val_ptr), + intval, intval)); + *(uint *)(dgnc_info_vars[i].val_ptr) = intval; + break; + case INFO_LONG: + APR(("Modifying %s (%lx) <= 0x%lx (%ld)\n", + name, (long)(dgnc_info_vars[i].val_ptr), + newval, newval)); + *(ulong *)(dgnc_info_vars[i].val_ptr) = newval; + break; + case INFO_PTR: + case INFO_STRING: + case INFO_END: + case INFO_NONE: + default: + break; + } +} + +static int dgnc_write_info(struct dgnc_proc_entry *table, int dir, struct file *filp, + const char __user *buffer, ssize_t *lenp, loff_t *ppos) +{ + static int state = 0; + #define MAXSYM 255 + static int sympos, valpos; + static char sym[MAXSYM + 1]; + static char val[MAXSYM + 1]; + static int quotchar = 0; + + int i; + + long len; + #define INBUFLEN 256 + char inbuf[INBUFLEN]; + + if (*ppos == 0) { + state = 0; + sympos = 0; sym[0] = 0; + valpos = 0; val[0] = 0; + quotchar = 0; + } + + if ((!*lenp) || (dir != INBOUND)) { + *lenp = 0; + return 0; + } + + len = *lenp; + + if (len > INBUFLEN - 1) + len = INBUFLEN - 1; + + if (copy_from_user(inbuf, buffer, len)) + return -EFAULT; + + inbuf[len] = 0; + + for (i = 0; i < len; i++) { + unsigned char c = inbuf[i]; + + switch (state) { + case 0: + quotchar = sympos = valpos = sym[0] = val[0] = 0; + if (!isspace(c) && (c != '\"') && + (c != '\'') && (c != '=')) { + sym[sympos++] = c; + state = 1; + break; + } + break; + case 1: + if (isspace(c)) { + sym[sympos] = 0; + state = 2; + break; + } + if (c == '=') { + sym[sympos] = 0; + state = 3; + break; + } + if ((c == '\"' ) || ( c == '\'' )) { + state = 0; + break; + } + if (sympos < MAXSYM) sym[sympos++] = c; + break; + case 2: + if (isspace(c)) break; + if (c == '=') { + state = 3; + break; + } + if ((c != '\"') && (c != '\'')) { + quotchar = sympos = valpos = sym[0] = val[0] = 0; + sym[sympos++] = c; + state = 1; + break; + } + state = 0; + break; + case 3: + if (isspace(c)) break; + if (c == '=') { + state = 0; + break; + } + if ((c == '\"') || (c == '\'')) { + state = 4; + quotchar = c; + break; + } + val[valpos++] = c; + state = 5; + break; + case 4: + if (c == quotchar) { + val[valpos] = 0; + dgnc_set_info_var(sym, val); + state = 0; + break; + } + if (valpos < MAXSYM) val[valpos++] = c; + break; + case 5: + if (isspace(c) || (c == '\"') || + (c == '\'') || (c == '=')) { + val[valpos] = 0; + dgnc_set_info_var(sym, val); + state = 0; + break; + } + if (valpos < MAXSYM) val[valpos++] = c; + break; + default: + break; + } + } + + *lenp = len; + *ppos += len; + + return len; +} + + +/* + * Return mknod information for the driver's devices. + */ +static int dgnc_read_mknod(struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos) +{ + static int done = 0; + static char buf[4096]; + char *p = buf; + int i = 0; + + DPR_PROC(("dgnc_proc_info\n")); + + if (done) { + done = 0; + *lenp = 0; + return 0; + } + + DPR_PROC(("dgnc_proc_mknod\n")); + + p += sprintf(p, "#\tCreate the management devices.\n"); + + for (i = 0; i < MAXMGMTDEVICES; i++) { + char tmp[100]; + sprintf(tmp, "/dev/dg/dgnc/mgmt%d", i); + p += sprintf(p, "%s\t%d\t%d\t%d\n", + tmp, dgnc_Major, i, 1); + } + + if (copy_to_user(buffer, buf, (p - (char *) buf))) + return -EFAULT; + + *lenp = p - (char *) buf; + *ppos += p - (char *) buf; + done = 1; + return 0; +} + + +/* + * Return what is (hopefully) useful information about the specific board. + */ +static int dgnc_read_board_info(struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos) +{ + struct board_t *brd; + static int done = 0; + static char buf[4096]; + char *p = buf; + char *name; + + DPR_PROC(("dgnc_proc_brd_info\n")); + + brd = (struct board_t *) table->data; + + if (done || !brd || (brd->magic != DGNC_BOARD_MAGIC)) { + done = 0; + *lenp = 0; + return 0; + } + + name = brd->name; + + p += sprintf(p, "Board Name = %s\n", name); + if (brd->serial_num[0] == '\0') + p += sprintf(p, "Serial number = <UNKNOWN>\n"); + else + p += sprintf(p, "Serial number = %s\n", brd->serial_num); + + p += sprintf(p, "Board Type = %d\n", brd->type); + p += sprintf(p, "Number of Ports = %d\n", brd->nasync); + + /* + * report some things about the PCI bus that are important + * to some applications + */ + p += sprintf(p, "Vendor ID = 0x%x\n", brd->vendor); + p += sprintf(p, "Device ID = 0x%x\n", brd->device); + p += sprintf(p, "Subvendor ID = 0x%x\n", brd->subvendor); + p += sprintf(p, "Subdevice ID = 0x%x\n", brd->subdevice); + p += sprintf(p, "Bus = %d\n", brd->pci_bus); + p += sprintf(p, "Slot = %d\n", brd->pci_slot); + + /* + * report the physical addresses assigned to us when we got + * registered + */ + p += sprintf(p, "Memory Base Address = 0x%lx\n", brd->membase); + p += sprintf(p, "Remapped Memory Base Address = 0x%p\n", brd->re_map_membase); + + p += sprintf(p, "Current state of board = %s\n", dgnc_state_text[brd->state]); + p += sprintf(p, "Interrupt #: %d. Times interrupted: %ld\n", + brd->irq, brd->intr_count); + + p += sprintf(p, "TX interrupts: %ld RX interrupts: %ld\n", + brd->intr_tx, brd->intr_rx); + p += sprintf(p, "Modem interrupts: %ld\n", brd->intr_modem); + + p += sprintf(p, "Majors allocated to board = TTY: %d PR: %d\n", + brd->SerialDriver.major, brd->PrintDriver.major); + + + if (copy_to_user(buffer, buf, (p - (char *) buf))) + return -EFAULT; + + *lenp = p - (char *) buf; + *ppos += p - (char *) buf; + done = 1; + return 0; +} + + + +static int dgnc_read_board_vpd(struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos) +{ + struct board_t *brd; + static int done = 0; + static char buf[4096]; + int i = 0, j = 0; + char *p = buf; + + DPR_PROC(("dgnc_proc_brd_info\n")); + + brd = (struct board_t *) table->data; + + if (done || !brd || (brd->magic != DGNC_BOARD_MAGIC)) { + done = 0; + *lenp = 0; + return 0; + } + + p += sprintf(p, "\n 0 1 2 3 4 5 6 7 8 9 A B C D E F ASCII\n"); + + for (i = 0; i < 0x40 * 2; i++) { + j = i; + if (!(i % 16)) { + if (j > 0) { + p += sprintf(p, " "); + for (j = i - 16; j < i; j++) { + if (0x20 <= brd->vpd[j] && brd->vpd[j] <= 0x7e) + p += sprintf(p, "%c", brd->vpd[j]); + else + p += sprintf(p, "."); + } + p += sprintf(p, "\n"); + } + p += sprintf(p, "%04X ", i); + } + p += sprintf(p, "%02X ", brd->vpd[i]); + } + if (!(i % 16)) { + p += sprintf(p, " "); + for (j = i - 16; j < i; j++) { + if (0x20 <= brd->vpd[j] && brd->vpd[j] <= 0x7e) + p += sprintf(p, "%c", brd->vpd[j]); + else + p += sprintf(p, "."); + } + p += sprintf(p, "\n"); + } + + p += sprintf(p, "\n"); + + if (copy_to_user(buffer, buf, (p - (char *) buf))) + return -EFAULT; + + *lenp = p - (char *) buf; + *ppos += p - (char *) buf; + done = 1; + return 0; +} + + +/* + * Return what is (hopefully) useful stats about the specific board's ttys + */ +static int dgnc_read_board_ttystats(struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos) +{ + struct board_t *brd; + static int done = 0; + static char buf[4096]; + char *p = buf; + int i = 0; + + DPR_PROC(("dgnc_proc_brd_info\n")); + + brd = (struct board_t *) table->data; + + if (done || !brd || (brd->magic != DGNC_BOARD_MAGIC)) { + done = 0; + *lenp = 0; + return 0; + } + + /* Prepare the Header Labels */ + p += sprintf(p, "%2s %10s %23s %10s %9s\n", + "Ch", "Chars Rx", " Rx Par--Brk--Frm--Ovr", + "Chars Tx", "XON XOFF"); + + for (i = 0; i < brd->nasync; i++) { + + struct channel_t *ch = brd->channels[i]; + + p += sprintf(p, "%2d ", i); + p += sprintf(p, "%10ld ", ch->ch_rxcount); + p += sprintf(p, " %4ld %4ld %4ld %4ld ", ch->ch_err_parity, + ch->ch_err_break, ch->ch_err_frame, ch->ch_err_overrun); + p += sprintf(p, "%10ld ", ch->ch_txcount); + p += sprintf(p, "%4ld %4ld ", ch->ch_xon_sends, ch->ch_xoff_sends); + + p += sprintf(p, "\n"); + } + + if (copy_to_user(buffer, buf, (p - (char *) buf))) + return -EFAULT; + + *lenp = p - (char *) buf; + *ppos += p - (char *) buf; + done = 1; + return 0; +} + + +/* + * Return what is (hopefully) useful stats about the specific board's tty intrs + */ +static int dgnc_read_board_ttyintr(struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos) +{ + struct board_t *brd; + static int done = 0; + static char buf[4096]; + char *p = buf; + int i = 0; + + DPR_PROC(("dgnc_proc_brd_info\n")); + + brd = (struct board_t *) table->data; + + if (done || !brd || (brd->magic != DGNC_BOARD_MAGIC)) { + done = 0; + *lenp = 0; + return 0; + } + + /* Prepare the Header Labels */ + p += sprintf(p, "%2s %14s %14s %14s\n", + "Ch", "TX interrupts", "RX interrupts", "Modem interrupts"); + + for (i = 0; i < brd->nasync; i++) { + + struct channel_t *ch = brd->channels[i]; + + p += sprintf(p, "%2d ", i); + + p += sprintf(p, " %14ld %14ld %14ld", + ch->ch_intr_tx, ch->ch_intr_rx, ch->ch_intr_modem); + + p += sprintf(p, "\n"); + } + + if (copy_to_user(buffer, buf, (p - (char *) buf))) + return -EFAULT; + + *lenp = p - (char *) buf; + *ppos += p - (char *) buf; + done = 1; + return 0; +} + + +/* + * Return what is (hopefully) useful flags about the specific board's ttys + */ +static int dgnc_read_board_ttyflags(struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos) +{ + struct board_t *brd; + static int done = 0; + static char buf[4096]; + char *p = buf; + int i = 0; + + DPR_PROC(("dgnc_proc_brd_info\n")); + + brd = (struct board_t *) table->data; + + if (done || !brd || (brd->magic != DGNC_BOARD_MAGIC)) { + done = 0; + *lenp = 0; + return 0; + } + + /* Prepare the Header Labels */ + p += sprintf(p, "%2s %5s %5s %5s %5s %5s %10s Line Status Flags\n", + "Ch", "CFlag", "IFlag", "OFlag", "LFlag", "DFlag", "Baud"); + + for (i = 0; i < brd->nasync; i++) { + + struct channel_t *ch = brd->channels[i]; + + p += sprintf(p, "%2d ", i); + p += sprintf(p, "%5x ", ch->ch_c_cflag); + p += sprintf(p, "%5x ", ch->ch_c_iflag); + p += sprintf(p, "%5x ", ch->ch_c_oflag); + p += sprintf(p, "%5x ", ch->ch_c_lflag); + p += sprintf(p, "%5x ", ch->ch_digi.digi_flags); + p += sprintf(p, "%10d ", ch->ch_old_baud); + + if (!ch->ch_open_count) { + p += sprintf(p, " -- -- -- -- -- -- --") ; + } else { + p += sprintf(p, " op %s %s %s %s %s %s", + (ch->ch_mostat & UART_MCR_RTS) ? "rs" : "--", + (ch->ch_mistat & UART_MSR_CTS) ? "cs" : "--", + (ch->ch_mostat & UART_MCR_DTR) ? "tr" : "--", + (ch->ch_mistat & UART_MSR_DSR) ? "mr" : "--", + (ch->ch_mistat & UART_MSR_DCD) ? "cd" : "--", + (ch->ch_mistat & UART_MSR_RI) ? "ri" : "--"); + } + + p += sprintf(p, "\n"); + } + if (copy_to_user(buffer, buf, (p - (char *) buf))) + return -EFAULT; + + *lenp = p - (char *) buf; + *ppos += p - (char *) buf; + done = 1; + return 0; +} + + +/* + * Return mknod information for the board's devices. + */ +static int dgnc_read_board_mknod(struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos) +{ + struct board_t *brd; + static int done = 0; + static char buf[4096]; + char str[MAXTTYNAMELEN]; + char *p = buf; + + DPR_PROC(("dgnc_proc_brd_info\n")); + + brd = (struct board_t *) table->data; + + if (done || !brd || (brd->magic != DGNC_BOARD_MAGIC)) { + done = 0; + *lenp = 0; + return 0; + } + + /* + * For each board, output the device information in + * a handy table format... + */ + p += sprintf(p, "# Create the TTY and PR devices\n"); + + /* TTY devices */ + sprintf(str, "ttyn%d%%p", brd->boardnum + 1); + p += sprintf(p, "%s\t\t\t%d\t%d\t%d\n", str, + brd->dgnc_Serial_Major, 0, brd->maxports); + + /* PR devices */ + sprintf(str, "prn%d%%p", brd->boardnum + 1); + p += sprintf(p, "%s\t\t\t%d\t%d\t%d\n", str, + brd->dgnc_TransparentPrint_Major, 128, brd->maxports); + + if (copy_to_user(buffer, buf, (p - (char *) buf))) + return -EFAULT; + + *lenp = p - (char *) buf; + *ppos += p - (char *) buf; + done = 1; + return 0; +} + + +/* + * Return what is (hopefully) useful information about the specific channel. + */ +static int dgnc_read_channel_info(struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos) +{ + struct channel_t *ch; + static int done = 0; + static char buf[4096]; + char *p = buf; + + DPR_PROC(("dgnc_proc_info\n")); + + ch = (struct channel_t *) table->data; + + if (done || !ch || (ch->magic != DGNC_CHANNEL_MAGIC)) { + done = 0; + *lenp = 0; + return 0; + } + + p += sprintf(p, "Port number:\t\t%d\n", ch->ch_portnum); + p += sprintf(p, "\n"); + + /* Prepare the Header Labels */ + p += sprintf(p, "%10s %23s %10s %9s\n", + "Chars Rx", " Rx Par--Brk--Frm--Ovr", + "Chars Tx", "XON XOFF"); + p += sprintf(p, "%10ld ", ch->ch_rxcount); + p += sprintf(p, " %4ld %4ld %4ld %4ld ", ch->ch_err_parity, + ch->ch_err_break, ch->ch_err_frame, ch->ch_err_overrun); + p += sprintf(p, "%10ld ", ch->ch_txcount); + p += sprintf(p, "%4ld %4ld ", ch->ch_xon_sends, ch->ch_xoff_sends); + p += sprintf(p, "\n\n"); + + /* Prepare the Header Labels */ + p += sprintf(p, "%5s %5s %5s %5s %5s %10s Line Status Flags\n", + "CFlag", "IFlag", "OFlag", "LFlag", "DFlag", "Baud"); + + p += sprintf(p, "%5x ", ch->ch_c_cflag); + p += sprintf(p, "%5x ", ch->ch_c_iflag); + p += sprintf(p, "%5x ", ch->ch_c_oflag); + p += sprintf(p, "%5x ", ch->ch_c_lflag); + p += sprintf(p, "%5x ", ch->ch_digi.digi_flags); + p += sprintf(p, "%10d ", ch->ch_old_baud); + if (!ch->ch_open_count) { + p += sprintf(p, " -- -- -- -- -- -- --") ; + } else { + p += sprintf(p, " op %s %s %s %s %s %s", + (ch->ch_mostat & UART_MCR_RTS) ? "rs" : "--", + (ch->ch_mistat & UART_MSR_CTS) ? "cs" : "--", + (ch->ch_mostat & UART_MCR_DTR) ? "tr" : "--", + (ch->ch_mistat & UART_MSR_DSR) ? "mr" : "--", + (ch->ch_mistat & UART_MSR_DCD) ? "cd" : "--", + (ch->ch_mistat & UART_MSR_RI) ? "ri" : "--"); + } + p += sprintf(p, "\n\n"); + + if (copy_to_user(buffer, buf, (p - (char *) buf))) + return -EFAULT; + + *lenp = p - (char *) buf; + *ppos += p - (char *) buf; + done = 1; + return 0; +} + + +/* + * Return mknod information for the board's devices. + */ +static int dgnc_read_channel_custom_ttyname(struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos) +{ + struct channel_t *ch; + struct board_t *brd; + static int done = 0; + static char buf[4096]; + char *p = buf; + int cn; + int bn; + + DPR_PROC(("dgnc_proc_brd_info\n")); + + ch = (struct channel_t *) table->data; + + if (done || !ch || (ch->magic != DGNC_CHANNEL_MAGIC)) { + done = 0; + *lenp = 0; + return 0; + } + + brd = ch->ch_bd; + + if (done || !brd || (brd->magic != DGNC_BOARD_MAGIC)) { + done = 0; + *lenp = 0; + return 0; + } + + bn = brd->boardnum; + cn = ch->ch_portnum; + + p += sprintf(p, "ttyn%d%c\n", bn + 1, 'a' + cn); + + if (copy_to_user(buffer, buf, (p - (char *) buf))) + return -EFAULT; + + *lenp = p - (char *) buf; + *ppos += p - (char *) buf; + done = 1; + return 0; +} + + + + +/* + * Return mknod information for the board's devices. + */ +static int dgnc_read_channel_custom_prname(struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos) +{ + struct channel_t *ch; + struct board_t *brd; + static int done = 0; + static char buf[4096]; + char *p = buf; + int cn; + int bn; + + DPR_PROC(("dgnc_proc_brd_info\n")); + + ch = (struct channel_t *) table->data; + + if (done || !ch || (ch->magic != DGNC_CHANNEL_MAGIC)) { + done = 0; + *lenp = 0; + return 0; + } + + brd = ch->ch_bd; + + if (done || !brd || (brd->magic != DGNC_BOARD_MAGIC)) { + done = 0; + *lenp = 0; + return 0; + } + + bn = brd->boardnum; + cn = ch->ch_portnum; + + p += sprintf(p, "prn%d%c\n", bn + 1, 'a' + cn); + + if (copy_to_user(buffer, buf, (p - (char *) buf))) + return -EFAULT; + + *lenp = p - (char *) buf; + *ppos += p - (char *) buf; + done = 1; + return 0; +} + + +static int dgnc_open_channel_sniff(struct dgnc_proc_entry *table, int dir, struct file *filp, + void *buffer, ssize_t *lenp, loff_t *ppos) +{ + struct channel_t *ch; + ulong lock_flags; + + ch = (struct channel_t *) table->data; + + if (!ch || (ch->magic != DGNC_CHANNEL_MAGIC)) + return 0; + + ch->ch_sniff_buf = dgnc_driver_kzmalloc(SNIFF_MAX, GFP_KERNEL); + + DGNC_LOCK(ch->ch_lock, lock_flags); + ch->ch_sniff_flags |= SNIFF_OPEN; + DGNC_UNLOCK(ch->ch_lock, lock_flags); + + return 0; +} + +static int dgnc_close_channel_sniff(struct dgnc_proc_entry *table, int dir, struct file *filp, + void *buffer, ssize_t *lenp, loff_t *ppos) +{ + struct channel_t *ch; + ulong lock_flags; + + ch = (struct channel_t *) table->data; + + if (!ch || (ch->magic != DGNC_CHANNEL_MAGIC)) + return 0; + + DGNC_LOCK(ch->ch_lock, lock_flags); + ch->ch_sniff_flags &= ~(SNIFF_OPEN); + kfree(ch->ch_sniff_buf); + DGNC_UNLOCK(ch->ch_lock, lock_flags); + + return 0; +} + + +/* + * Copy data from the monitoring buffer to the user, freeing space + * in the monitoring buffer for more messages + * + */ +static int dgnc_read_channel_sniff(struct dgnc_proc_entry *table, int dir, struct file *filp, + char __user *buffer, ssize_t *lenp, loff_t *ppos) +{ + struct channel_t *ch; + int n; + int r; + int offset = 0; + int res = 0; + ssize_t rtn = 0; + ulong lock_flags; + + ch = (struct channel_t *) table->data; + + if (!ch || (ch->magic != DGNC_CHANNEL_MAGIC)) { + rtn = -ENXIO; + goto done; + } + + /* + * Wait for some data to appear in the buffer. + */ + DGNC_LOCK(ch->ch_lock, lock_flags); + + for (;;) { + n = (ch->ch_sniff_in - ch->ch_sniff_out) & SNIFF_MASK; + + if (n != 0) + break; + + ch->ch_sniff_flags |= SNIFF_WAIT_DATA; + + DGNC_UNLOCK(ch->ch_lock, lock_flags); + + /* + * Go to sleep waiting until the condition becomes true. + */ + rtn = wait_event_interruptible(ch->ch_sniff_wait, + ((ch->ch_sniff_flags & SNIFF_WAIT_DATA) == 0)); + + if (rtn) + goto done; + + DGNC_LOCK(ch->ch_lock, lock_flags); + } + + /* + * Read whatever is there. + */ + + if (n > *lenp) + n = *lenp; + + res = n; + + r = SNIFF_MAX - ch->ch_sniff_out; + + if (r <= n) { + DGNC_UNLOCK(ch->ch_lock, lock_flags); + rtn = copy_to_user(buffer, ch->ch_sniff_buf + ch->ch_sniff_out, r); + if (rtn) { + rtn = -EFAULT; + goto done; + } + + DGNC_LOCK(ch->ch_lock, lock_flags); + + ch->ch_sniff_out = 0; + n -= r; + offset = r; + } + + DGNC_UNLOCK(ch->ch_lock, lock_flags); + rtn = copy_to_user(buffer + offset, ch->ch_sniff_buf + ch->ch_sniff_out, n); + if (rtn) { + rtn = -EFAULT; + goto done; + } + DGNC_LOCK(ch->ch_lock, lock_flags); + + ch->ch_sniff_out += n; + *ppos += res; + rtn = res; +// rtn = 0; + + /* + * Wakeup any thread waiting for buffer space. + */ + + if (ch->ch_sniff_flags & SNIFF_WAIT_SPACE) { + ch->ch_sniff_flags &= ~SNIFF_WAIT_SPACE; + wake_up_interruptible(&ch->ch_sniff_wait); + } + + DGNC_UNLOCK(ch->ch_lock, lock_flags); + +done: + return rtn; +} + + +/* + * Register the basic /proc/dgnc files that appear whenever + * the driver is loaded. + */ +void dgnc_proc_register_basic_prescan(void) +{ + /* + * Register /proc/dgnc + */ + ProcDGNC = proc_create("dgnc", (0700 | S_IFDIR), NULL, &dgnc_proc_file_ops); + dgnc_register_proc_table(dgnc_table, ProcDGNC); +} + + +/* + * Register the basic /proc/dgnc files that appear whenever + * the driver is loaded. + */ +void dgnc_proc_register_basic_postscan(int board_num) +{ + int i, j; + char board[10]; + sprintf(board, "%d", board_num); + + /* Set proc board entry pointer */ + dgnc_Board[board_num]->proc_entry_pointer = create_proc_entry(board, (0700 | S_IFDIR), ProcDGNC); + + /* Create a new copy of the board_table... */ + dgnc_Board[board_num]->dgnc_board_table = dgnc_driver_kzmalloc(sizeof(dgnc_board_table), + GFP_KERNEL); + + /* Now copy the default table into that memory */ + memcpy(dgnc_Board[board_num]->dgnc_board_table, dgnc_board_table, sizeof(dgnc_board_table)); + + /* Initialize semaphores in each table slot */ + for (i = 0; i < 999; i++) { + if (!dgnc_Board[board_num]->dgnc_board_table[i].magic) { + break; + } + + init_MUTEX(&(dgnc_Board[board_num]->dgnc_board_table[i].excl_sem)); + dgnc_Board[board_num]->dgnc_board_table[i].data = dgnc_Board[board_num]; + + } + + /* Register board table into proc */ + dgnc_register_proc_table(dgnc_Board[board_num]->dgnc_board_table, + dgnc_Board[board_num]->proc_entry_pointer); + + /* + * Add new entries for each port. + */ + for (i = 0; i < dgnc_Board[board_num]->nasync; i++) { + + char channel[10]; + sprintf(channel, "%d", i); + + /* Set proc channel entry pointer */ + dgnc_Board[board_num]->channels[i]->proc_entry_pointer = + create_proc_entry(channel, (0700 | S_IFDIR), + dgnc_Board[board_num]->proc_entry_pointer); + + /* Create a new copy of the channel_table... */ + dgnc_Board[board_num]->channels[i]->dgnc_channel_table = + dgnc_driver_kzmalloc(sizeof(dgnc_channel_table), GFP_KERNEL); + + /* Now copy the default table into that memory */ + memcpy(dgnc_Board[board_num]->channels[i]->dgnc_channel_table, + dgnc_channel_table, sizeof(dgnc_channel_table)); + + /* Initialize semaphores in each table slot */ + for (j = 0; j < 999; j++) { + if (!dgnc_Board[board_num]->channels[i]->dgnc_channel_table[j].magic) { + break; + } + + init_MUTEX(&(dgnc_Board[board_num]->channels[i]->dgnc_channel_table[j].excl_sem)); + dgnc_Board[board_num]->channels[i]->dgnc_channel_table[j].data = + dgnc_Board[board_num]->channels[i]; + } + + /* Register channel table into proc */ + dgnc_register_proc_table(dgnc_Board[board_num]->channels[i]->dgnc_channel_table, + dgnc_Board[board_num]->channels[i]->proc_entry_pointer); + } +} + + +static void dgnc_remove_proc_entry(struct proc_dir_entry *pde) +{ + if (!pde) { + DPR_PROC(("dgnc_remove_proc_entry... NULL entry... not removing...\n")); + return; + } + + remove_proc_entry(pde->name, pde->parent); +} + + +void dgnc_proc_unregister_all(void) +{ + int i = 0, j = 0; + + /* Walk each board, blowing away their proc entries... */ + for (i = 0; i < dgnc_NumBoards; i++) { + + /* Walk each channel, blowing away their proc entries... */ + for (j = 0; j < dgnc_Board[i]->nasync; j++) { + + dgnc_unregister_proc_table(dgnc_Board[i]->channels[j]->dgnc_channel_table, + dgnc_Board[i]->channels[j]->proc_entry_pointer); + dgnc_remove_proc_entry(dgnc_Board[i]->channels[j]->proc_entry_pointer); + kfree(dgnc_Board[i]->channels[j]->dgnc_channel_table); + } + + dgnc_unregister_proc_table(dgnc_Board[i]->dgnc_board_table, + dgnc_Board[i]->proc_entry_pointer); + dgnc_remove_proc_entry(dgnc_Board[i]->proc_entry_pointer); + kfree(dgnc_Board[i]->dgnc_board_table); + } + + /* Blow away the top proc entry */ + dgnc_unregister_proc_table(dgnc_table, ProcDGNC); + dgnc_remove_proc_entry(ProcDGNC); +} |