]> git.karo-electronics.de Git - linux-beck.git/commitdiff
staging: Add dgrp driver for Digi Realport devices
authorBill Pemberton <wfp5p@virginia.edu>
Thu, 20 Sep 2012 20:55:27 +0000 (16:55 -0400)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 21 Sep 2012 16:04:18 +0000 (09:04 -0700)
This is based on dgrp-1.9 available from
ftp://ftp1.digi.com/support/beta/linux/dgrp/dgrp-1.9.tgz

Signed-off-by: Bill Pemberton <wfp5p@virginia.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
16 files changed:
drivers/staging/dgrp/Kconfig [new file with mode: 0644]
drivers/staging/dgrp/Makefile [new file with mode: 0644]
drivers/staging/dgrp/README [new file with mode: 0644]
drivers/staging/dgrp/TODO [new file with mode: 0644]
drivers/staging/dgrp/dgrp_common.c [new file with mode: 0644]
drivers/staging/dgrp/dgrp_common.h [new file with mode: 0644]
drivers/staging/dgrp/dgrp_dpa_ops.c [new file with mode: 0644]
drivers/staging/dgrp/dgrp_driver.c [new file with mode: 0644]
drivers/staging/dgrp/dgrp_mon_ops.c [new file with mode: 0644]
drivers/staging/dgrp/dgrp_net_ops.c [new file with mode: 0644]
drivers/staging/dgrp/dgrp_ports_ops.c [new file with mode: 0644]
drivers/staging/dgrp/dgrp_specproc.c [new file with mode: 0644]
drivers/staging/dgrp/dgrp_sysfs.c [new file with mode: 0644]
drivers/staging/dgrp/dgrp_tty.c [new file with mode: 0644]
drivers/staging/dgrp/digirp.h [new file with mode: 0644]
drivers/staging/dgrp/drp.h [new file with mode: 0644]

diff --git a/drivers/staging/dgrp/Kconfig b/drivers/staging/dgrp/Kconfig
new file mode 100644 (file)
index 0000000..39f4bb6
--- /dev/null
@@ -0,0 +1,9 @@
+config DGRP
+       tristate "Digi Realport driver"
+       default n
+       depends on SYSFS
+       ---help---
+       Support for Digi Realport devices.  These devices allow you to
+       access remote serial ports as if they are local tty devices.  This
+       will build the kernel driver, you will still need the userspace
+       component to make your Realport device work.
diff --git a/drivers/staging/dgrp/Makefile b/drivers/staging/dgrp/Makefile
new file mode 100644 (file)
index 0000000..d9c3b88
--- /dev/null
@@ -0,0 +1,12 @@
+obj-$(CONFIG_DGRP) += dgrp.o
+
+dgrp-y :=                      \
+       dgrp_common.o           \
+       dgrp_dpa_ops.o          \
+       dgrp_driver.o           \
+       dgrp_mon_ops.o          \
+       dgrp_net_ops.o          \
+       dgrp_ports_ops.o        \
+       dgrp_specproc.o         \
+       dgrp_tty.o              \
+       dgrp_sysfs.o
diff --git a/drivers/staging/dgrp/README b/drivers/staging/dgrp/README
new file mode 100644 (file)
index 0000000..1d8aaee
--- /dev/null
@@ -0,0 +1,2 @@
+The user space code to work with this driver is located at
+https://github.com/wfp5p/dgrp-utils
diff --git a/drivers/staging/dgrp/TODO b/drivers/staging/dgrp/TODO
new file mode 100644 (file)
index 0000000..63341ad
--- /dev/null
@@ -0,0 +1,7 @@
+- Use configfs for config stuff.  This will require changes to the
+  user space code.
+
+- Check the calls to tty_register_device.  In particular, check to see
+  if there should be some handling for IS_ERR(classp).
+
+- dgrp_send() and dgrp_receive() could use some refactoring
diff --git a/drivers/staging/dgrp/dgrp_common.c b/drivers/staging/dgrp/dgrp_common.c
new file mode 100644 (file)
index 0000000..fce74e7
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ *
+ * 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_common.c
+ *
+ *  Description:
+ *
+ *     Definitions of global variables and functions which are either
+ *     shared by the tty, mon, and net drivers; or which cross them
+ *     functionally (like the poller).
+ *
+ *  Author:
+ *
+ *     James A. Puzzo
+ *
+ */
+
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <linux/sched.h>
+#include <linux/cred.h>
+
+#include "dgrp_common.h"
+
+/**
+ * dgrp_carrier -- check for carrier change state and act
+ * @ch: struct ch_struct *
+ */
+void dgrp_carrier(struct ch_struct *ch)
+{
+       struct nd_struct *nd;
+
+       int virt_carrier = 0;
+       int phys_carrier = 0;
+
+       /* fix case when the tty has already closed. */
+
+       if (!ch)
+               return;
+       nd  = ch->ch_nd;
+       if (!nd)
+               return;
+
+       /*
+        *  If we are currently waiting to determine the status of the port,
+        *  we don't yet know the state of the modem lines.  As a result,
+        *  we ignore state changes when we are waiting for the modem lines
+        *  to be established.  We know, as a result of code in dgrp_net_ops,
+        *  that we will be called again immediately following the reception
+        *  of the status message with the true modem status flags in it.
+        */
+       if (ch->ch_expect & RR_STATUS)
+               return;
+
+       /*
+        * If CH_HANGUP is set, we gotta keep trying to get all the processes
+        * that have the port open to close the port.
+        * So lets just keep sending a hangup every time we get here.
+        */
+       if ((ch->ch_flag & CH_HANGUP) &&
+           (ch->ch_tun.un_open_count > 0))
+               tty_hangup(ch->ch_tun.un_tty);
+
+       /*
+        *  Compute the effective state of both the physical and virtual
+        *  senses of carrier.
+        */
+
+       if (ch->ch_s_mlast & DM_CD)
+               phys_carrier = 1;
+
+       if ((ch->ch_s_mlast & DM_CD) ||
+           (ch->ch_digi.digi_flags & DIGI_FORCEDCD) ||
+           (ch->ch_flag & CH_CLOCAL))
+               virt_carrier = 1;
+
+       /*
+        *  Test for a VIRTUAL carrier transition to HIGH.
+        *
+        *  The CH_HANGUP condition is intended to prevent any action
+        *  except for close.  As a result, we ignore positive carrier
+        *  transitions during CH_HANGUP.
+        */
+       if (((ch->ch_flag & CH_HANGUP)  == 0) &&
+           ((ch->ch_flag & CH_VIRT_CD) == 0) &&
+           (virt_carrier == 1)) {
+               /*
+                * When carrier rises, wake any threads waiting
+                * for carrier in the open routine.
+                */
+               nd->nd_tx_work = 1;
+
+               if (waitqueue_active(&ch->ch_flag_wait))
+                       wake_up_interruptible(&ch->ch_flag_wait);
+       }
+
+       /*
+        *  Test for a PHYSICAL transition to low, so long as we aren't
+        *  currently ignoring physical transitions (which is what "virtual
+        *  carrier" indicates).
+        *
+        *  The transition of the virtual carrier to low really doesn't
+        *  matter... it really only means "ignore carrier state", not
+        *  "make pretend that carrier is there".
+        */
+       if ((virt_carrier == 0) &&
+           ((ch->ch_flag & CH_PHYS_CD) != 0) &&
+           (phys_carrier == 0)) {
+               /*
+                * When carrier drops:
+                *
+                *   Do a Hard Hangup if that is called for.
+                *
+                *   Drop carrier on all open units.
+                *
+                *   Flush queues, waking up any task waiting in the
+                *   line discipline.
+                *
+                *   Send a hangup to the control terminal.
+                *
+                *   Enable all select calls.
+                */
+
+               nd->nd_tx_work = 1;
+
+               ch->ch_flag &= ~(CH_LOW | CH_EMPTY | CH_DRAIN | CH_INPUT);
+
+               if (waitqueue_active(&ch->ch_flag_wait))
+                       wake_up_interruptible(&ch->ch_flag_wait);
+
+               if (ch->ch_tun.un_open_count > 0)
+                       tty_hangup(ch->ch_tun.un_tty);
+
+               if (ch->ch_pun.un_open_count > 0)
+                       tty_hangup(ch->ch_pun.un_tty);
+       }
+
+       /*
+        *  Make sure that our cached values reflect the current reality.
+        */
+       if (virt_carrier == 1)
+               ch->ch_flag |= CH_VIRT_CD;
+       else
+               ch->ch_flag &= ~CH_VIRT_CD;
+
+       if (phys_carrier == 1)
+               ch->ch_flag |= CH_PHYS_CD;
+       else
+               ch->ch_flag &= ~CH_PHYS_CD;
+
+}
+
+/**
+ * dgrp_chk_perm() -- check permissions for net device
+ * @inode: pointer to inode structure for the net communication device
+ * @op: operation to be tested
+ *
+ * The file permissions and ownerships are tested to determine whether
+ * the operation "op" is permitted on the file pointed to by the inode.
+ * Returns 0 if the operation is permitted, -EACCESS otherwise
+ */
+int dgrp_chk_perm(int mode, int op)
+{
+       if (!current_euid())
+               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;
+}
+
+/* dgrp_chk_perm wrapper for permission call in struct inode_operations */
+int dgrp_inode_permission(struct inode *inode, int op)
+{
+       return dgrp_chk_perm(inode->i_mode, op);
+}
diff --git a/drivers/staging/dgrp/dgrp_common.h b/drivers/staging/dgrp/dgrp_common.h
new file mode 100644 (file)
index 0000000..05ff338
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+#ifndef __DGRP_COMMON_H
+#define __DGRP_COMMON_H
+
+#define DIGI_VERSION "1.9-29"
+
+#include <linux/fs.h>
+#include <linux/timer.h>
+#include "drp.h"
+
+#define DGRP_TTIME 100
+#define DGRP_RTIME 100
+
+/************************************************************************
+ * All global storage allocation.
+ ************************************************************************/
+
+extern int dgrp_rawreadok;  /* Allow raw writing of input */
+extern int dgrp_register_cudevices; /* enable legacy cu devices */
+extern int dgrp_register_prdevices; /* enable transparent print devices */
+extern int dgrp_poll_tick;          /* Poll interval - in ms */
+
+extern struct list_head nd_struct_list;
+
+struct dgrp_poll_data {
+       spinlock_t poll_lock;
+       struct timer_list timer;
+       int poll_tick;
+       ulong poll_round;       /* Timer rouding factor */
+       long node_active_count;
+};
+
+extern struct dgrp_poll_data dgrp_poll_data;
+extern void dgrp_poll_handler(unsigned long arg);
+
+/* from dgrp_mon_ops.c */
+extern void dgrp_register_mon_hook(struct proc_dir_entry *de);
+
+/* from dgrp_tty.c */
+extern int dgrp_tty_init(struct nd_struct *nd);
+extern void dgrp_tty_uninit(struct nd_struct *nd);
+
+/* from dgrp_ports_ops.c */
+extern void dgrp_register_ports_hook(struct proc_dir_entry *de);
+
+/* from dgrp_net_ops.c */
+extern void dgrp_register_net_hook(struct proc_dir_entry *de);
+
+/* from dgrp_dpa_ops.c */
+extern void dgrp_register_dpa_hook(struct proc_dir_entry *de);
+extern void dgrp_dpa_data(struct nd_struct *, int, u8 *, int);
+
+/* from dgrp_sysfs.c */
+extern void dgrp_create_class_sysfs_files(void);
+extern void dgrp_remove_class_sysfs_files(void);
+
+extern void dgrp_create_node_class_sysfs_files(struct nd_struct *nd);
+extern void dgrp_remove_node_class_sysfs_files(struct nd_struct *nd);
+
+extern void dgrp_create_tty_sysfs(struct un_struct *un, struct device *c);
+extern void dgrp_remove_tty_sysfs(struct device *c);
+
+/* from dgrp_specproc.c */
+/*
+ *  The list of DGRP entries with r/w capabilities.  These
+ *  magic numbers are used for identification purposes.
+ */
+enum {
+       DGRP_CONFIG = 1,        /* Configure portservers */
+       DGRP_NETDIR = 2,        /* Directory for "net" devices */
+       DGRP_MONDIR = 3,        /* Directory for "mon" devices */
+       DGRP_PORTSDIR = 4,      /* Directory for "ports" devices */
+       DGRP_INFO = 5,          /* Get info. about the running module */
+       DGRP_NODEINFO = 6,      /* Get info. about the configured nodes */
+       DGRP_DPADIR = 7,        /* Directory for the "dpa" devices */
+};
+
+/*
+ *  Directions for proc handlers
+ */
+enum {
+       INBOUND = 1,            /* Data being written to kernel */
+       OUTBOUND = 2,           /* Data being read from the kernel */
+};
+
+/**
+ * dgrp_proc_entry: structure for dgrp proc dirs
+ * @id: ID number associated with this particular entry.  Should be
+ *    unique across all of DGRP.
+ * @name: text name associated with the /proc entry
+ * @mode: file access permisssions for the /proc entry
+ * @child: pointer to table describing a subdirectory for this entry
+ * @de: pointer to directory entry for this object once registered.  Used
+ *    to grab the handle of the object for unregistration
+ * @excl_sem: semaphore to provide exclusive to struct
+ * @excl_cnt: counter of current accesses
+ *
+ *  Each entry in a DGRP proc directory is described with a
+ *  dgrp_proc_entry structure.  A collection of these
+ *  entries (in an array) represents the members associated
+ *  with a particular /proc directory, and is referred to
+ *  as a table.  All tables are terminated by an entry with
+ *  zeros for every member.
+ */
+struct dgrp_proc_entry {
+       int                  id;          /* Integer identifier */
+       const char        *name;          /* ASCII identifier */
+       mode_t             mode;          /* File access permissions */
+       struct dgrp_proc_entry *child;    /* Child pointer */
+
+       /* file ops to use, pass NULL to use default */
+       struct file_operations *proc_file_ops;
+
+       struct proc_dir_entry *de;        /* proc entry pointer */
+       struct semaphore   excl_sem;      /* Protects exclusive access var */
+       int                excl_cnt;      /* Counts number of curr accesses */
+};
+
+extern void dgrp_unregister_proc(void);
+extern void dgrp_register_proc(void);
+
+/*-----------------------------------------------------------------------*
+ *
+ *  Declarations for common operations:
+ *
+ *      (either used by more than one of net, mon, or tty,
+ *       or in interrupt context (i.e. the poller))
+ *
+ *-----------------------------------------------------------------------*/
+
+void dgrp_carrier(struct ch_struct *ch);
+extern int dgrp_inode_permission(struct inode *inode, int op);
+extern int dgrp_chk_perm(int mode, int op);
+
+
+/*
+ *  ID manipulation macros (where c1 & c2 are characters, i is
+ *  a long integer, and s is a character array of at least three members
+ */
+
+static inline void ID_TO_CHAR(long i, char *s)
+{
+       s[0] = ((i & 0xff00)>>8);
+       s[1] = (i & 0xff);
+       s[2] = 0;
+}
+
+static inline long CHAR_TO_ID(char *s)
+{
+       return ((s[0] & 0xff) << 8) | (s[1] & 0xff);
+}
+
+static inline struct nd_struct *nd_struct_get(long major)
+{
+       struct nd_struct *nd;
+
+       list_for_each_entry(nd, &nd_struct_list, list) {
+               if (major == nd->nd_major)
+                       return nd;
+       }
+
+       return NULL;
+}
+
+static inline int nd_struct_add(struct nd_struct *entry)
+{
+       struct nd_struct *ptr;
+
+       ptr = nd_struct_get(entry->nd_major);
+
+       if (ptr)
+               return -EBUSY;
+
+       list_add_tail(&entry->list, &nd_struct_list);
+
+       return 0;
+}
+
+static inline int nd_struct_del(struct nd_struct *entry)
+{
+       struct nd_struct *nd;
+
+       nd = nd_struct_get(entry->nd_major);
+
+       if (!nd)
+               return -ENODEV;
+
+       list_del(&nd->list);
+       return 0;
+}
+
+#endif /* __DGRP_COMMON_H */
diff --git a/drivers/staging/dgrp/dgrp_dpa_ops.c b/drivers/staging/dgrp/dgrp_dpa_ops.c
new file mode 100644 (file)
index 0000000..49e6709
--- /dev/null
@@ -0,0 +1,556 @@
+/*
+ *
+ * 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_dpa_ops.c
+ *
+ *  Description:
+ *
+ *     Handle the file operations required for the "dpa" devices.
+ *     Includes those functions required to register the "dpa" devices
+ *     in "/proc".
+ *
+ *  Author:
+ *
+ *     James A. Puzzo
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/tty.h>
+#include <linux/poll.h>
+#include <linux/cred.h>
+#include <linux/sched.h>
+#include <linux/ratelimit.h>
+#include <asm/unaligned.h>
+
+#include "dgrp_common.h"
+
+/* File operation declarations */
+static int dgrp_dpa_open(struct inode *, struct file *);
+static int dgrp_dpa_release(struct inode *, struct file *);
+static ssize_t dgrp_dpa_read(struct file *, char __user *, size_t, loff_t *);
+static long dgrp_dpa_ioctl(struct file *file, unsigned int cmd,
+                          unsigned long arg);
+static unsigned int dgrp_dpa_select(struct file *, struct poll_table_struct *);
+
+static const struct file_operations dpa_ops = {
+       .owner   =  THIS_MODULE,
+       .read    =  dgrp_dpa_read,
+       .poll    =  dgrp_dpa_select,
+       .unlocked_ioctl =  dgrp_dpa_ioctl,
+       .open    =  dgrp_dpa_open,
+       .release =  dgrp_dpa_release,
+};
+
+static struct inode_operations dpa_inode_ops = {
+       .permission = dgrp_inode_permission
+};
+
+
+
+struct digi_node {
+       uint    nd_state;               /* Node state: 1 = up, 0 = down. */
+       uint    nd_chan_count;          /* Number of channels found */
+       uint    nd_tx_byte;             /* Tx data count */
+       uint    nd_rx_byte;             /* RX data count */
+       u8      nd_ps_desc[MAX_DESC_LEN]; /* Description from PS */
+};
+
+#define DIGI_GETNODE      (('d'<<8) | 249)     /* get board info */
+
+
+struct digi_chan {
+       uint    ch_port;        /* Port number to get info on */
+       uint    ch_open;        /* 1 if open, 0 if not */
+       uint    ch_txcount;     /* TX data count  */
+       uint    ch_rxcount;     /* RX data count  */
+       uint    ch_s_brate;     /* Realport BRATE */
+       uint    ch_s_estat;     /* Realport ELAST */
+       uint    ch_s_cflag;     /* Realport CFLAG */
+       uint    ch_s_iflag;     /* Realport IFLAG */
+       uint    ch_s_oflag;     /* Realport OFLAG */
+       uint    ch_s_xflag;     /* Realport XFLAG */
+       uint    ch_s_mstat;     /* Realport MLAST */
+};
+
+#define DIGI_GETCHAN      (('d'<<8) | 248)     /* get channel info */
+
+
+struct digi_vpd {
+       int vpd_len;
+       char vpd_data[VPDSIZE];
+};
+
+#define DIGI_GETVPD       (('d'<<8) | 246)     /* get VPD info */
+
+
+struct digi_debug {
+       int onoff;
+       int port;
+};
+
+#define DIGI_SETDEBUG      (('d'<<8) | 247)    /* set debug info */
+
+
+void dgrp_register_dpa_hook(struct proc_dir_entry *de)
+{
+       struct nd_struct *node = de->data;
+
+       de->proc_iops = &dpa_inode_ops;
+       de->proc_fops = &dpa_ops;
+
+       node->nd_dpa_de = de;
+       spin_lock_init(&node->nd_dpa_lock);
+}
+
+/*
+ * dgrp_dpa_open -- open the DPA device for a particular PortServer
+ */
+static int dgrp_dpa_open(struct inode *inode, struct file *file)
+{
+       struct nd_struct *nd;
+       int rtn = 0;
+
+       struct proc_dir_entry *de;
+
+       rtn = try_module_get(THIS_MODULE);
+       if (!rtn)
+               return -ENXIO;
+
+       rtn = 0;
+
+       if (!capable(CAP_SYS_ADMIN)) {
+               rtn = -EPERM;
+               goto done;
+       }
+
+       /*
+        *  Make sure that the "private_data" field hasn't already been used.
+        */
+       if (file->private_data) {
+               rtn = -EINVAL;
+               goto done;
+       }
+
+       /*
+        *  Get the node pointer, and fail if it doesn't exist.
+        */
+       de = PDE(inode);
+       if (!de) {
+               rtn = -ENXIO;
+               goto done;
+       }
+       nd = (struct nd_struct *)de->data;
+       if (!nd) {
+               rtn = -ENXIO;
+               goto done;
+       }
+
+       file->private_data = (void *) nd;
+
+       /*
+        * Allocate the DPA buffer.
+        */
+
+       if (nd->nd_dpa_buf) {
+               rtn = -EBUSY;
+       } else {
+               nd->nd_dpa_buf = kmalloc(DPA_MAX, GFP_KERNEL);
+
+               if (!nd->nd_dpa_buf) {
+                       rtn = -ENOMEM;
+               } else {
+                       nd->nd_dpa_out = 0;
+                       nd->nd_dpa_in = 0;
+                       nd->nd_dpa_lbolt = jiffies;
+               }
+       }
+
+done:
+
+       if (rtn)
+               module_put(THIS_MODULE);
+       return rtn;
+}
+
+/*
+ * dgrp_dpa_release -- close the DPA device for a particular PortServer
+ */
+static int dgrp_dpa_release(struct inode *inode, struct file *file)
+{
+       struct nd_struct *nd;
+       u8 *buf;
+       unsigned long lock_flags;
+
+       /*
+        *  Get the node pointer, and quit if it doesn't exist.
+        */
+       nd = (struct nd_struct *)(file->private_data);
+       if (!nd)
+               goto done;
+
+       /*
+        *  Free the dpa buffer.
+        */
+
+       spin_lock_irqsave(&nd->nd_dpa_lock, lock_flags);
+
+       buf = nd->nd_dpa_buf;
+
+       nd->nd_dpa_buf = NULL;
+       nd->nd_dpa_out = nd->nd_dpa_in;
+
+       /*
+        *  Wakeup any thread waiting for buffer space.
+        */
+
+       if (nd->nd_dpa_flag & DPA_WAIT_SPACE) {
+               nd->nd_dpa_flag &= ~DPA_WAIT_SPACE;
+               wake_up_interruptible(&nd->nd_dpa_wqueue);
+       }
+
+       spin_unlock_irqrestore(&nd->nd_dpa_lock, lock_flags);
+
+       kfree(buf);
+
+done:
+       module_put(THIS_MODULE);
+       file->private_data = NULL;
+       return 0;
+}
+
+/*
+ * dgrp_dpa_read
+ *
+ * Copy data from the monitoring buffer to the user, freeing space
+ * in the monitoring buffer for more messages
+ */
+static ssize_t dgrp_dpa_read(struct file *file, char __user *buf, size_t count,
+                            loff_t *ppos)
+{
+       struct nd_struct *nd;
+       int n;
+       int r;
+       int offset = 0;
+       int res = 0;
+       ssize_t rtn;
+       unsigned long lock_flags;
+
+       /*
+        *  Get the node pointer, and quit if it doesn't exist.
+        */
+       nd = (struct nd_struct *)(file->private_data);
+       if (!nd)
+               return -ENXIO;
+
+       /*
+        *  Wait for some data to appear in the buffer.
+        */
+
+       spin_lock_irqsave(&nd->nd_dpa_lock, lock_flags);
+
+       for (;;) {
+               n = (nd->nd_dpa_in - nd->nd_dpa_out) & DPA_MASK;
+
+               if (n != 0)
+                       break;
+
+               nd->nd_dpa_flag |= DPA_WAIT_DATA;
+
+               spin_unlock_irqrestore(&nd->nd_dpa_lock, lock_flags);
+
+               /*
+                * Go to sleep waiting until the condition becomes true.
+                */
+               rtn = wait_event_interruptible(nd->nd_dpa_wqueue,
+                       ((nd->nd_dpa_flag & DPA_WAIT_DATA) == 0));
+
+               if (rtn)
+                       return rtn;
+
+               spin_lock_irqsave(&nd->nd_dpa_lock, lock_flags);
+       }
+
+       /*
+        *  Read whatever is there.
+        */
+
+       if (n > count)
+               n = count;
+
+       res = n;
+
+       r = DPA_MAX - nd->nd_dpa_out;
+
+       if (r <= n) {
+
+               spin_unlock_irqrestore(&nd->nd_dpa_lock, lock_flags);
+               rtn = copy_to_user((void __user *)buf,
+                                  nd->nd_dpa_buf + nd->nd_dpa_out, r);
+               spin_lock_irqsave(&nd->nd_dpa_lock, lock_flags);
+
+               if (rtn) {
+                       rtn = -EFAULT;
+                       goto done;
+               }
+
+               nd->nd_dpa_out = 0;
+               n -= r;
+               offset = r;
+       }
+
+       spin_unlock_irqrestore(&nd->nd_dpa_lock, lock_flags);
+       rtn = copy_to_user((void __user *)buf + offset,
+                          nd->nd_dpa_buf + nd->nd_dpa_out, n);
+       spin_lock_irqsave(&nd->nd_dpa_lock, lock_flags);
+
+       if (rtn) {
+               rtn = -EFAULT;
+               goto done;
+       }
+
+       nd->nd_dpa_out += n;
+
+       *ppos += res;
+
+       rtn = res;
+
+       /*
+        *  Wakeup any thread waiting for buffer space.
+        */
+
+       n = (nd->nd_dpa_in - nd->nd_dpa_out) & DPA_MASK;
+
+       if (nd->nd_dpa_flag & DPA_WAIT_SPACE &&
+           (DPA_MAX - n) > DPA_HIGH_WATER) {
+               nd->nd_dpa_flag &= ~DPA_WAIT_SPACE;
+               wake_up_interruptible(&nd->nd_dpa_wqueue);
+       }
+
+ done:
+       spin_unlock_irqrestore(&nd->nd_dpa_lock, lock_flags);
+       return rtn;
+}
+
+static unsigned int dgrp_dpa_select(struct file *file,
+                                   struct poll_table_struct *table)
+{
+       unsigned int retval = 0;
+       struct nd_struct *nd = file->private_data;
+
+       if (nd->nd_dpa_out != nd->nd_dpa_in)
+               retval |= POLLIN | POLLRDNORM; /* Conditionally readable */
+
+       retval |= POLLOUT | POLLWRNORM;        /* Always writeable */
+
+       return retval;
+}
+
+static long dgrp_dpa_ioctl(struct file *file, unsigned int cmd,
+                          unsigned long arg)
+{
+
+       struct nd_struct  *nd;
+       struct digi_chan getchan;
+       struct digi_node getnode;
+       struct ch_struct *ch;
+       struct digi_debug setdebug;
+       struct digi_vpd vpd;
+       unsigned int port;
+       void __user *uarg = (void __user *) arg;
+
+       nd = file->private_data;
+
+       switch (cmd) {
+       case DIGI_GETCHAN:
+               if (copy_from_user(&getchan, uarg, sizeof(struct digi_chan)))
+                       return -EFAULT;
+
+               port = getchan.ch_port;
+
+               if (port < 0 || port > nd->nd_chan_count)
+                       return -EINVAL;
+
+               ch = nd->nd_chan + port;
+
+               getchan.ch_open = (ch->ch_open_count > 0) ? 1 : 0;
+               getchan.ch_txcount = ch->ch_txcount;
+               getchan.ch_rxcount = ch->ch_rxcount;
+               getchan.ch_s_brate = ch->ch_s_brate;
+               getchan.ch_s_estat = ch->ch_s_elast;
+               getchan.ch_s_cflag = ch->ch_s_cflag;
+               getchan.ch_s_iflag = ch->ch_s_iflag;
+               getchan.ch_s_oflag = ch->ch_s_oflag;
+               getchan.ch_s_xflag = ch->ch_s_xflag;
+               getchan.ch_s_mstat = ch->ch_s_mlast;
+
+               if (copy_to_user(uarg, &getchan, sizeof(struct digi_chan)))
+                       return -EFAULT;
+               break;
+
+
+       case DIGI_GETNODE:
+               getnode.nd_state = (nd->nd_state & NS_READY) ? 1 : 0;
+               getnode.nd_chan_count = nd->nd_chan_count;
+               getnode.nd_tx_byte = nd->nd_tx_byte;
+               getnode.nd_rx_byte = nd->nd_rx_byte;
+
+               memset(&getnode.nd_ps_desc, 0, MAX_DESC_LEN);
+               strncpy(getnode.nd_ps_desc, nd->nd_ps_desc, MAX_DESC_LEN);
+
+               if (copy_to_user(uarg, &getnode, sizeof(struct digi_node)))
+                       return -EFAULT;
+               break;
+
+
+       case DIGI_SETDEBUG:
+               if (copy_from_user(&setdebug, uarg, sizeof(struct digi_debug)))
+                       return -EFAULT;
+
+               nd->nd_dpa_debug = setdebug.onoff;
+               nd->nd_dpa_port = setdebug.port;
+               break;
+
+
+       case DIGI_GETVPD:
+               if (nd->nd_vpd_len > 0) {
+                       vpd.vpd_len = nd->nd_vpd_len;
+                       memcpy(&vpd.vpd_data, &nd->nd_vpd, nd->nd_vpd_len);
+               } else {
+                       vpd.vpd_len = 0;
+               }
+
+               if (copy_to_user(uarg, &vpd, sizeof(struct digi_vpd)))
+                       return -EFAULT;
+               break;
+       }
+
+       return 0;
+}
+
+/**
+ * dgrp_dpa() -- send data to the device monitor queue
+ * @nd: pointer to a node structure
+ * @buf: buffer of data to copy to the monitoring buffer
+ * @len: number of bytes to transfer to the buffer
+ *
+ * Called by the net device routines to send data to the device
+ * monitor queue.  If the device monitor buffer is too full to
+ * accept the data, it waits until the buffer is ready.
+ */
+static void dgrp_dpa(struct nd_struct *nd, u8 *buf, int nbuf)
+{
+       int n;
+       int r;
+       unsigned long lock_flags;
+
+       /*
+        *  Grab DPA lock.
+        */
+       spin_lock_irqsave(&nd->nd_dpa_lock, lock_flags);
+
+       /*
+        *  Loop while data remains.
+        */
+       while (nbuf > 0 && nd->nd_dpa_buf != NULL) {
+
+               n = (nd->nd_dpa_out - nd->nd_dpa_in - 1) & DPA_MASK;
+
+               /*
+                * Enforce flow control on the DPA device.
+                */
+               if (n < (DPA_MAX - DPA_HIGH_WATER))
+                       nd->nd_dpa_flag |= DPA_WAIT_SPACE;
+
+               /*
+                * This should never happen, as the flow control above
+                * should have stopped things before they got to this point.
+                */
+               if (n == 0) {
+                       spin_unlock_irqrestore(&nd->nd_dpa_lock, lock_flags);
+                       return;
+               }
+
+               /*
+                * Copy as much data as will fit.
+                */
+
+               if (n > nbuf)
+                       n = nbuf;
+
+               r = DPA_MAX - nd->nd_dpa_in;
+
+               if (r <= n) {
+                       memcpy(nd->nd_dpa_buf + nd->nd_dpa_in, buf, r);
+
+                       n -= r;
+
+                       nd->nd_dpa_in = 0;
+
+                       buf += r;
+                       nbuf -= r;
+               }
+
+               memcpy(nd->nd_dpa_buf + nd->nd_dpa_in, buf, n);
+
+               nd->nd_dpa_in += n;
+
+               buf += n;
+               nbuf -= n;
+
+               if (nd->nd_dpa_in >= DPA_MAX)
+                       pr_info_ratelimited("%s - nd->nd_dpa_in (%i) >= DPA_MAX\n",
+                                           __func__, nd->nd_dpa_in);
+
+               /*
+                *  Wakeup any thread waiting for data
+                */
+               if (nd->nd_dpa_flag & DPA_WAIT_DATA) {
+                       nd->nd_dpa_flag &= ~DPA_WAIT_DATA;
+                       wake_up_interruptible(&nd->nd_dpa_wqueue);
+               }
+       }
+
+       /*
+        *  Release the DPA lock.
+        */
+       spin_unlock_irqrestore(&nd->nd_dpa_lock, lock_flags);
+}
+
+/**
+ * dgrp_monitor_data() -- builds a DPA data packet
+ * @nd: pointer to a node structure
+ * @type: type of message to be logged in the DPA buffer
+ * @buf: buffer of data to be logged in the DPA buffer
+ * @size -- number of bytes in the "buf" buffer
+ */
+void dgrp_dpa_data(struct nd_struct *nd, int type, u8 *buf, int size)
+{
+       u8 header[5];
+
+       header[0] = type;
+
+       put_unaligned_be32(size, header + 1);
+
+       dgrp_dpa(nd, header, sizeof(header));
+       dgrp_dpa(nd, buf, size);
+}
diff --git a/drivers/staging/dgrp/dgrp_driver.c b/drivers/staging/dgrp/dgrp_driver.c
new file mode 100644 (file)
index 0000000..6e4a0eb
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ *
+ * Copyright 1999-2003 Digi International (www.digi.com)
+ *     Jeff Randall
+ *     James Puzzo  <jamesp at digi dot com>
+ *     Scott 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.
+ *
+ */
+
+/*
+ *     Driver specific includes
+ */
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/init.h>
+
+/*
+ *  PortServer includes
+ */
+#include "dgrp_common.h"
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Digi International, http://www.digi.com");
+MODULE_DESCRIPTION("RealPort driver for Digi's ethernet-based serial connectivity product line");
+MODULE_VERSION(DIGI_VERSION);
+
+struct list_head nd_struct_list;
+struct dgrp_poll_data dgrp_poll_data;
+
+int dgrp_rawreadok = 1;                /* Bypass flipbuf on input */
+int dgrp_register_cudevices = 1;/* Turn on/off registering legacy cu devices */
+int dgrp_register_prdevices = 1;/* Turn on/off registering transparent print */
+int dgrp_poll_tick = 20;       /* Poll interval - in ms */
+
+module_param_named(rawreadok, dgrp_rawreadok, int, 0644);
+MODULE_PARM_DESC(rawreadok, "Bypass flip buffers on input");
+
+module_param_named(register_cudevices, dgrp_register_cudevices, int, 0644);
+MODULE_PARM_DESC(register_cudevices, "Turn on/off registering legacy cu devices");
+
+module_param_named(register_prdevices, dgrp_register_prdevices, int, 0644);
+MODULE_PARM_DESC(register_prdevices, "Turn on/off registering transparent print devices");
+
+module_param_named(pollrate, dgrp_poll_tick, int, 0644);
+MODULE_PARM_DESC(pollrate, "Poll interval in ms");
+
+/* Driver load/unload functions */
+static int dgrp_init_module(void);
+static void dgrp_cleanup_module(void);
+
+module_init(dgrp_init_module);
+module_exit(dgrp_cleanup_module);
+
+/*
+ * init_module()
+ *
+ * Module load.  This is where it all starts.
+ */
+static int dgrp_init_module(void)
+{
+       INIT_LIST_HEAD(&nd_struct_list);
+
+       spin_lock_init(&dgrp_poll_data.poll_lock);
+       init_timer(&dgrp_poll_data.timer);
+       dgrp_poll_data.poll_tick = dgrp_poll_tick;
+       dgrp_poll_data.timer.function = dgrp_poll_handler;
+       dgrp_poll_data.timer.data = (unsigned long) &dgrp_poll_data;
+
+       dgrp_create_class_sysfs_files();
+
+       dgrp_register_proc();
+
+       return 0;
+}
+
+
+/*
+ *     Module unload.  This is where it all ends.
+ */
+static void dgrp_cleanup_module(void)
+{
+       struct nd_struct *nd, *next;
+
+       /*
+        *      Attempting to free resources in backwards
+        *      order of allocation, in case that helps
+        *      memory pool fragmentation.
+        */
+       dgrp_unregister_proc();
+
+       dgrp_remove_class_sysfs_files();
+
+
+       list_for_each_entry_safe(nd, next, &nd_struct_list, list) {
+               dgrp_tty_uninit(nd);
+               kfree(nd);
+       }
+}
diff --git a/drivers/staging/dgrp/dgrp_mon_ops.c b/drivers/staging/dgrp/dgrp_mon_ops.c
new file mode 100644 (file)
index 0000000..268dcb9
--- /dev/null
@@ -0,0 +1,346 @@
+/*****************************************************************************
+ *
+ * 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_mon_ops.c
+ *
+ *  Description:
+ *
+ *     Handle the file operations required for the "monitor" devices.
+ *     Includes those functions required to register the "mon" devices
+ *     in "/proc".
+ *
+ *  Author:
+ *
+ *     James A. Puzzo
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/tty.h>
+#include <linux/sched.h>
+#include <asm/unaligned.h>
+#include <linux/proc_fs.h>
+
+#include "dgrp_common.h"
+
+/* File operation declarations */
+static int dgrp_mon_open(struct inode *, struct file *);
+static int dgrp_mon_release(struct inode *, struct file *);
+static ssize_t dgrp_mon_read(struct file *, char __user *, size_t, loff_t *);
+static long dgrp_mon_ioctl(struct file *file, unsigned int cmd,
+                          unsigned long arg);
+
+static const struct file_operations mon_ops = {
+       .owner   = THIS_MODULE,
+       .read    = dgrp_mon_read,
+       .unlocked_ioctl = dgrp_mon_ioctl,
+       .open    = dgrp_mon_open,
+       .release = dgrp_mon_release,
+};
+
+static struct inode_operations mon_inode_ops = {
+       .permission = dgrp_inode_permission
+};
+
+void dgrp_register_mon_hook(struct proc_dir_entry *de)
+{
+       struct nd_struct *node = de->data;
+
+       de->proc_iops = &mon_inode_ops;
+       de->proc_fops = &mon_ops;
+       node->nd_mon_de = de;
+       sema_init(&node->nd_mon_semaphore, 1);
+}
+
+/**
+ * dgrp_mon_open() -- open /proc/dgrp/ports device for a PortServer
+ * @inode: struct inode *
+ * @file: struct file *
+ *
+ * Open function to open the /proc/dgrp/ports device for a PortServer.
+ */
+static int dgrp_mon_open(struct inode *inode, struct file *file)
+{
+       struct nd_struct *nd;
+       struct proc_dir_entry *de;
+       struct timeval tv;
+       uint32_t time;
+       u8 *buf;
+       int rtn;
+
+       rtn = try_module_get(THIS_MODULE);
+       if (!rtn)
+               return -ENXIO;
+
+       rtn = 0;
+
+       if (!capable(CAP_SYS_ADMIN)) {
+               rtn = -EPERM;
+               goto done;
+       }
+
+       /*
+        *  Make sure that the "private_data" field hasn't already been used.
+        */
+       if (file->private_data) {
+               rtn = -EINVAL;
+               goto done;
+       }
+
+       /*
+        *  Get the node pointer, and fail if it doesn't exist.
+        */
+       de = PDE(inode);
+       if (!de) {
+               rtn = -ENXIO;
+               goto done;
+       }
+
+       nd = (struct nd_struct *)de->data;
+       if (!nd) {
+               rtn = -ENXIO;
+               goto done;
+       }
+
+       file->private_data = (void *) nd;
+
+       /*
+        * Allocate the monitor buffer.
+        */
+
+       /*
+        *  Grab the MON lock.
+        */
+       down(&nd->nd_mon_semaphore);
+
+       if (nd->nd_mon_buf) {
+               rtn = -EBUSY;
+               goto done_up;
+       }
+
+       nd->nd_mon_buf = kmalloc(MON_MAX, GFP_KERNEL);
+
+       if (!nd->nd_mon_buf) {
+               rtn = -ENOMEM;
+               goto done_up;
+       }
+
+       /*
+        *  Enter an RPDUMP file header into the buffer.
+        */
+
+       buf = nd->nd_mon_buf;
+
+       strcpy(buf, RPDUMP_MAGIC);
+       buf += strlen(buf) + 1;
+
+       do_gettimeofday(&tv);
+
+       /*
+        *  tv.tv_sec might be a 64 bit quantity.  Pare
+        *  it down to 32 bits before attempting to encode
+        *  it.
+        */
+       time = (uint32_t) (tv.tv_sec & 0xffffffff);
+
+       put_unaligned_be32(time, buf);
+       put_unaligned_be16(0, buf + 4);
+       buf += 6;
+
+       if (nd->nd_tx_module) {
+               buf[0] = RPDUMP_CLIENT;
+               put_unaligned_be32(0, buf + 1);
+               put_unaligned_be16(1, buf + 5);
+               buf[7] = 0xf0 + nd->nd_tx_module;
+               buf += 8;
+       }
+
+       if (nd->nd_rx_module) {
+               buf[0] = RPDUMP_SERVER;
+               put_unaligned_be32(0, buf + 1);
+               put_unaligned_be16(1, buf + 5);
+               buf[7] = 0xf0 + nd->nd_rx_module;
+               buf += 8;
+       }
+
+       nd->nd_mon_out = 0;
+       nd->nd_mon_in  = buf - nd->nd_mon_buf;
+       nd->nd_mon_lbolt = jiffies;
+
+done_up:
+       up(&nd->nd_mon_semaphore);
+
+done:
+       if (rtn)
+               module_put(THIS_MODULE);
+       return rtn;
+}
+
+
+/**
+ * dgrp_mon_release() - Close the MON device for a particular PortServer
+ * @inode: struct inode *
+ * @file: struct file *
+ */
+static int dgrp_mon_release(struct inode *inode, struct file *file)
+{
+       struct nd_struct *nd;
+
+       /*
+        *  Get the node pointer, and quit if it doesn't exist.
+        */
+       nd = (struct nd_struct *)(file->private_data);
+       if (!nd)
+               goto done;
+
+       /*
+        *  Free the monitor buffer.
+        */
+
+       down(&nd->nd_mon_semaphore);
+
+       kfree(nd->nd_mon_buf);
+       nd->nd_mon_buf = NULL;
+       nd->nd_mon_out = nd->nd_mon_in;
+
+       /*
+        *  Wakeup any thread waiting for buffer space.
+        */
+
+       if (nd->nd_mon_flag & MON_WAIT_SPACE) {
+               nd->nd_mon_flag &= ~MON_WAIT_SPACE;
+               wake_up_interruptible(&nd->nd_mon_wqueue);
+       }
+
+       up(&nd->nd_mon_semaphore);
+
+       /*
+        *  Make sure there is no thread in the middle of writing a packet.
+        */
+       down(&nd->nd_net_semaphore);
+       up(&nd->nd_net_semaphore);
+
+done:
+       module_put(THIS_MODULE);
+       file->private_data = NULL;
+       return 0;
+}
+
+/**
+ * dgrp_mon_read() -- Copy data from the monitoring buffer to the user
+ */
+static ssize_t dgrp_mon_read(struct file *file, char __user *buf, size_t count,
+                            loff_t *ppos)
+{
+       struct nd_struct *nd;
+       int r;
+       int offset = 0;
+       int res = 0;
+       ssize_t rtn;
+
+       /*
+        *  Get the node pointer, and quit if it doesn't exist.
+        */
+       nd = (struct nd_struct *)(file->private_data);
+       if (!nd)
+               return -ENXIO;
+
+       /*
+        *  Wait for some data to appear in the buffer.
+        */
+
+       down(&nd->nd_mon_semaphore);
+
+       for (;;) {
+               res = (nd->nd_mon_in - nd->nd_mon_out) & MON_MASK;
+
+               if (res)
+                       break;
+
+               nd->nd_mon_flag |= MON_WAIT_DATA;
+
+               up(&nd->nd_mon_semaphore);
+
+               /*
+                * Go to sleep waiting until the condition becomes true.
+                */
+               rtn = wait_event_interruptible(nd->nd_mon_wqueue,
+                                              ((nd->nd_mon_flag & MON_WAIT_DATA) == 0));
+
+               if (rtn)
+                       return rtn;
+
+               down(&nd->nd_mon_semaphore);
+       }
+
+       /*
+        *  Read whatever is there.
+        */
+
+       if (res > count)
+               res = count;
+
+       r = MON_MAX - nd->nd_mon_out;
+
+       if (r <= res) {
+               rtn = copy_to_user((void __user *)buf,
+                                  nd->nd_mon_buf + nd->nd_mon_out, r);
+               if (rtn) {
+                       up(&nd->nd_mon_semaphore);
+                       return -EFAULT;
+               }
+
+               nd->nd_mon_out = 0;
+               res -= r;
+               offset = r;
+       }
+
+       rtn = copy_to_user((void __user *) buf + offset,
+                          nd->nd_mon_buf + nd->nd_mon_out, res);
+       if (rtn) {
+               up(&nd->nd_mon_semaphore);
+               return -EFAULT;
+       }
+
+       nd->nd_mon_out += res;
+
+       *ppos += res;
+
+       up(&nd->nd_mon_semaphore);
+
+       /*
+        *  Wakeup any thread waiting for buffer space.
+        */
+
+       if (nd->nd_mon_flag & MON_WAIT_SPACE) {
+               nd->nd_mon_flag &= ~MON_WAIT_SPACE;
+               wake_up_interruptible(&nd->nd_mon_wqueue);
+       }
+
+       return res;
+}
+
+/*  ioctl is not valid on monitor device */
+static long dgrp_mon_ioctl(struct file *file, unsigned int cmd,
+                          unsigned long arg)
+{
+       return -EINVAL;
+}
diff --git a/drivers/staging/dgrp/dgrp_net_ops.c b/drivers/staging/dgrp/dgrp_net_ops.c
new file mode 100644 (file)
index 0000000..d9d6b67
--- /dev/null
@@ -0,0 +1,3731 @@
+/*
+ *
+ * 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_net_ops.c
+ *
+ *  Description:
+ *
+ *     Handle the file operations required for the "network" devices.
+ *     Includes those functions required to register the "net" devices
+ *     in "/proc".
+ *
+ *  Author:
+ *
+ *     James A. Puzzo
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/spinlock.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/ratelimit.h>
+#include <asm/unaligned.h>
+
+#define MYFLIPLEN      TBUF_MAX
+
+#include "dgrp_common.h"
+
+#define TTY_FLIPBUF_SIZE 512
+#define DEVICE_NAME_SIZE 50
+
+/*
+ *  Generic helper function declarations
+ */
+static void   parity_scan(struct ch_struct *ch, unsigned char *cbuf,
+                               unsigned char *fbuf, int *len);
+
+/*
+ *  File operation declarations
+ */
+static int dgrp_net_open(struct inode *, struct file *);
+static int dgrp_net_release(struct inode *, struct file *);
+static ssize_t dgrp_net_read(struct file *, char __user *, size_t, loff_t *);
+static ssize_t dgrp_net_write(struct file *, const char __user *, size_t,
+                             loff_t *);
+static long dgrp_net_ioctl(struct file *file, unsigned int cmd,
+                          unsigned long arg);
+static unsigned int dgrp_net_select(struct file *file,
+                                   struct poll_table_struct *table);
+
+static const struct file_operations net_ops = {
+       .owner   =  THIS_MODULE,
+       .read    =  dgrp_net_read,
+       .write   =  dgrp_net_write,
+       .poll    =  dgrp_net_select,
+       .unlocked_ioctl =  dgrp_net_ioctl,
+       .open    =  dgrp_net_open,
+       .release =  dgrp_net_release,
+};
+
+static struct inode_operations net_inode_ops = {
+       .permission = dgrp_inode_permission
+};
+
+void dgrp_register_net_hook(struct proc_dir_entry *de)
+{
+       struct nd_struct *node = de->data;
+
+       de->proc_iops = &net_inode_ops;
+       de->proc_fops = &net_ops;
+       node->nd_net_de = de;
+       sema_init(&node->nd_net_semaphore, 1);
+       node->nd_state = NS_CLOSED;
+       dgrp_create_node_class_sysfs_files(node);
+}
+
+
+/**
+ * dgrp_dump() -- prints memory for debugging purposes.
+ * @mem: Memory location which should be printed to the console
+ * @len: Number of bytes to be dumped
+ */
+static void dgrp_dump(u8 *mem, int len)
+{
+       int i;
+
+       pr_debug("dgrp dump length = %d, data = ", len);
+       for (i = 0; i < len; ++i)
+               pr_debug("%.2x ", mem[i]);
+       pr_debug("\n");
+}
+
+/**
+ * dgrp_read_data_block() -- Read a data block
+ * @ch: struct ch_struct *
+ * @flipbuf: u8 *
+ * @flipbuf_size: size of flipbuf
+ */
+static void dgrp_read_data_block(struct ch_struct *ch, u8 *flipbuf,
+                                int flipbuf_size)
+{
+       int t;
+       int n;
+
+       if (flipbuf_size <= 0)
+               return;
+
+       t = RBUF_MAX - ch->ch_rout;
+       n = flipbuf_size;
+
+       if (n >= t) {
+               memcpy(flipbuf, ch->ch_rbuf + ch->ch_rout, t);
+               flipbuf += t;
+               n -= t;
+               ch->ch_rout = 0;
+       }
+
+       memcpy(flipbuf, ch->ch_rbuf + ch->ch_rout, n);
+       flipbuf += n;
+       ch->ch_rout += n;
+}
+
+
+/**
+ * dgrp_input() -- send data to the line disipline
+ * @ch: pointer to channel struct
+ *
+ * Copys the rbuf to the flipbuf and sends to line discipline.
+ * Sends input buffer data to the line discipline.
+ *
+ * There are several modes to consider here:
+ *    rawreadok, tty->real_raw, and IF_PARMRK
+ */
+static void dgrp_input(struct ch_struct *ch)
+{
+       struct nd_struct *nd;
+       struct tty_struct *tty;
+       int remain;
+       int data_len;
+       int len;
+       int flip_len;
+       int tty_count;
+       ulong lock_flags;
+       struct tty_ldisc *ld;
+       u8  *myflipbuf;
+       u8  *myflipflagbuf;
+
+       if (!ch)
+               return;
+
+       nd = ch->ch_nd;
+
+       if (!nd)
+               return;
+
+       spin_lock_irqsave(&nd->nd_lock, lock_flags);
+
+       myflipbuf = nd->nd_inputbuf;
+       myflipflagbuf = nd->nd_inputflagbuf;
+
+       if (!ch->ch_open_count) {
+               ch->ch_rout = ch->ch_rin;
+               goto out;
+       }
+
+       if (ch->ch_tun.un_flag & UN_CLOSING) {
+               ch->ch_rout = ch->ch_rin;
+               goto out;
+       }
+
+       tty = (ch->ch_tun).un_tty;
+
+
+       if (!tty || tty->magic != TTY_MAGIC) {
+               ch->ch_rout = ch->ch_rin;
+               goto out;
+       }
+
+       tty_count = tty->count;
+       if (!tty_count) {
+               ch->ch_rout = ch->ch_rin;
+               goto out;
+       }
+
+       if (tty->closing || test_bit(TTY_CLOSING, &tty->flags)) {
+               ch->ch_rout = ch->ch_rin;
+               goto out;
+       }
+
+       spin_unlock_irqrestore(&nd->nd_lock, lock_flags);
+
+       /* Decide how much data we can send into the tty layer */
+       if (dgrp_rawreadok && tty->real_raw)
+               flip_len = MYFLIPLEN;
+       else
+               flip_len = TTY_FLIPBUF_SIZE;
+
+       /* data_len should be the number of chars that we read in */
+       data_len = (ch->ch_rin - ch->ch_rout) & RBUF_MASK;
+       remain = data_len;
+
+       /* len is the amount of data we are going to transfer here */
+       len = min(data_len, flip_len);
+
+       /* take into consideration length of ldisc */
+       len = min(len, (N_TTY_BUF_SIZE - 1) - tty->read_cnt);
+
+       ld = tty_ldisc_ref(tty);
+
+       /*
+        * If we were unable to get a reference to the ld,
+        * don't flush our buffer, and act like the ld doesn't
+        * have any space to put the data right now.
+        */
+       if (!ld) {
+               len = 0;
+       } else if (!ld->ops->receive_buf) {
+               spin_lock_irqsave(&nd->nd_lock, lock_flags);
+               ch->ch_rout = ch->ch_rin;
+               spin_unlock_irqrestore(&nd->nd_lock, lock_flags);
+               len = 0;
+       }
+
+       /* Check DPA flow control */
+       if ((nd->nd_dpa_debug) &&
+           (nd->nd_dpa_flag & DPA_WAIT_SPACE) &&
+           (nd->nd_dpa_port == MINOR(tty_devnum(ch->ch_tun.un_tty))))
+               len = 0;
+
+       if ((len) && !(ch->ch_flag & CH_RXSTOP)) {
+
+               dgrp_read_data_block(ch, myflipbuf, len);
+
+               /*
+                * In high performance mode, we don't have to update
+                * flag_buf or any of the counts or pointers into flip buf.
+                */
+               if (!dgrp_rawreadok || !tty->real_raw) {
+                       if (I_PARMRK(tty) || I_BRKINT(tty) || I_INPCK(tty))
+                               parity_scan(ch, myflipbuf, myflipflagbuf, &len);
+                       else
+                               memset(myflipflagbuf, TTY_NORMAL, len);
+               }
+
+               if ((nd->nd_dpa_debug) &&
+                   (nd->nd_dpa_port == PORT_NUM(MINOR(tty_devnum(tty)))))
+                       dgrp_dpa_data(nd, 1, myflipbuf, len);
+
+               /*
+                * If we're doing raw reads, jam it right into the
+                * line disc bypassing the flip buffers.
+                */
+               if (dgrp_rawreadok && tty->real_raw)
+                       ld->ops->receive_buf(tty, myflipbuf, NULL, len);
+               else {
+                       len = tty_buffer_request_room(tty, len);
+                       tty_insert_flip_string_flags(tty, myflipbuf,
+                                                    myflipflagbuf, len);
+
+                       /* Tell the tty layer its okay to "eat" the data now */
+                       tty_flip_buffer_push(tty);
+               }
+
+               ch->ch_rxcount += len;
+       }
+
+       if (ld)
+               tty_ldisc_deref(ld);
+
+       /*
+        * Wake up any sleepers (maybe dgrp close) that might be waiting
+        * for a channel flag state change.
+        */
+       wake_up_interruptible(&ch->ch_flag_wait);
+       return;
+
+out:
+       spin_unlock_irqrestore(&nd->nd_lock, lock_flags);
+}
+
+
+/*
+ *  parity_scan
+ *
+ *  Loop to inspect each single character or 0xFF escape.
+ *
+ *  if PARMRK & ~DOSMODE:
+ *     0xFF  0xFF           Normal 0xFF character, escaped
+ *                          to eliminate confusion.
+ *     0xFF  0x00  0x00     Break
+ *     0xFF  0x00  CC       Error character CC.
+ *     CC                   Normal character CC.
+ *
+ *  if PARMRK & DOSMODE:
+ *     0xFF  0x18  0x00     Break
+ *     0xFF  0x08  0x00     Framing Error
+ *     0xFF  0x04  0x00     Parity error
+ *     0xFF  0x0C  0x00     Both Framing and Parity error
+ *
+ *  TODO:  do we need to do the XMODEM, XOFF, XON, XANY processing??
+ *         as per protocol
+ */
+static void parity_scan(struct ch_struct *ch, unsigned char *cbuf,
+                       unsigned char *fbuf, int *len)
+{
+       int l = *len;
+       int count = 0;
+       int DOS = ((ch->ch_iflag & IF_DOSMODE) == 0 ? 0 : 1);
+       unsigned char *cout; /* character buffer */
+       unsigned char *fout; /* flag buffer */
+       unsigned char *in;
+       unsigned char c;
+
+       in = cbuf;
+       cout = cbuf;
+       fout = fbuf;
+
+       while (l--) {
+               c = *in;
+               in++;
+
+               switch (ch->ch_pscan_state) {
+               default:
+                       /* reset to sanity and fall through */
+                       ch->ch_pscan_state = 0 ;
+
+               case 0:
+                       /* No FF seen yet */
+                       if (c == 0xff) /* delete this character from stream */
+                               ch->ch_pscan_state = 1;
+                       else {
+                               *cout++ = c;
+                               *fout++ = TTY_NORMAL;
+                               count += 1;
+                       }
+                       break;
+
+               case 1:
+                       /* first FF seen */
+                       if (c == 0xff) {
+                               /* doubled ff, transform to single ff */
+                               *cout++ = c;
+                               *fout++ = TTY_NORMAL;
+                               count += 1;
+                               ch->ch_pscan_state = 0;
+                       } else {
+                               /* save value examination in next state */
+                               ch->ch_pscan_savechar = c;
+                               ch->ch_pscan_state = 2;
+                       }
+                       break;
+
+               case 2:
+                       /* third character of ff sequence */
+                       *cout++ = c;
+                       if (DOS) {
+                               if (ch->ch_pscan_savechar & 0x10)
+                                       *fout++ = TTY_BREAK;
+                               else if (ch->ch_pscan_savechar & 0x08)
+                                       *fout++ = TTY_FRAME;
+                               else
+                                       /*
+                                        * either marked as a parity error,
+                                        * indeterminate, or not in DOSMODE
+                                        * call it a parity error
+                                        */
+                                       *fout++ = TTY_PARITY;
+                       } else {
+                               /* case FF XX ?? where XX is not 00 */
+                               if (ch->ch_pscan_savechar & 0xff) {
+                                       /* this should not happen */
+                                       pr_info("%s: parity_scan: error unexpected byte\n",
+                                               __func__);
+                                       *fout++ = TTY_PARITY;
+                               }
+                               /* case FF 00 XX where XX is not 00 */
+                               else if (c == 0xff)
+                                       *fout++ = TTY_PARITY;
+                               /* case FF 00 00 */
+                               else
+                                       *fout++ = TTY_BREAK;
+
+                       }
+                       count += 1;
+                       ch->ch_pscan_state = 0;
+               }
+       }
+       *len = count;
+}
+
+
+/**
+ * dgrp_net_idle() -- Idle the network connection
+ * @nd: pointer to node structure to idle
+ */
+static void dgrp_net_idle(struct nd_struct *nd)
+{
+       struct ch_struct *ch;
+       int i;
+
+       nd->nd_tx_work = 1;
+
+       nd->nd_state = NS_IDLE;
+       nd->nd_flag = 0;
+
+       for (i = nd->nd_seq_out; ; i = (i + 1) & SEQ_MASK) {
+               if (!nd->nd_seq_wait[i]) {
+                       nd->nd_seq_wait[i] = 0;
+                       wake_up_interruptible(&nd->nd_seq_wque[i]);
+               }
+
+               if (i == nd->nd_seq_in)
+                       break;
+       }
+
+       nd->nd_seq_out = nd->nd_seq_in;
+
+       nd->nd_unack = 0;
+       nd->nd_remain = 0;
+
+       nd->nd_tx_module = 0x10;
+       nd->nd_rx_module = 0x00;
+
+       for (i = 0, ch = nd->nd_chan; i < CHAN_MAX; i++, ch++) {
+               ch->ch_state = CS_IDLE;
+
+               ch->ch_otype = 0;
+               ch->ch_otype_waiting = 0;
+       }
+}
+
+/*
+ *  Increase the number of channels, waking up any
+ *  threads that might be waiting for the channels
+ *  to appear.
+ */
+static void increase_channel_count(struct nd_struct *nd, int n)
+{
+       struct ch_struct *ch;
+       struct device *classp;
+       char name[DEVICE_NAME_SIZE];
+       int ret;
+       u8 *buf;
+       int i;
+
+       for (i = nd->nd_chan_count; i < n; ++i) {
+               ch = nd->nd_chan + i;
+
+               /* FIXME: return a useful error instead! */
+               buf = kmalloc(TBUF_MAX, GFP_KERNEL);
+               if (!buf)
+                       return;
+
+               if (ch->ch_tbuf)
+                       pr_info_ratelimited("%s - ch_tbuf was not NULL\n",
+                                           __func__);
+
+               ch->ch_tbuf = buf;
+
+               buf = kmalloc(RBUF_MAX, GFP_KERNEL);
+               if (!buf)
+                       return;
+
+               if (ch->ch_rbuf)
+                       pr_info("%s - ch_rbuf was not NULL\n",
+                               __func__);
+               ch->ch_rbuf = buf;
+
+               classp = tty_port_register_device(&ch->port,
+                                                 nd->nd_serial_ttdriver, i,
+                                                 NULL);
+
+               ch->ch_tun.un_sysfs = classp;
+               snprintf(name, DEVICE_NAME_SIZE, "tty_%d", i);
+
+               dgrp_create_tty_sysfs(&ch->ch_tun, classp);
+               ret = sysfs_create_link(&nd->nd_class_dev->kobj,
+                                       &classp->kobj, name);
+
+               /* NOTE: We don't support "cu" devices anymore,
+                * so you will notice we don't register them
+                * here anymore. */
+               if (dgrp_register_prdevices) {
+                       classp = tty_register_device(nd->nd_xprint_ttdriver,
+                                                    i, NULL);
+                       ch->ch_pun.un_sysfs = classp;
+                       snprintf(name, DEVICE_NAME_SIZE, "pr_%d", i);
+
+                       dgrp_create_tty_sysfs(&ch->ch_pun, classp);
+                       ret = sysfs_create_link(&nd->nd_class_dev->kobj,
+                                               &classp->kobj, name);
+               }
+
+               nd->nd_chan_count = i + 1;
+               wake_up_interruptible(&ch->ch_flag_wait);
+       }
+}
+
+/*
+ * Decrease the number of channels, and wake up any threads that might
+ * be waiting on the channels that vanished.
+ */
+static void decrease_channel_count(struct nd_struct *nd, int n)
+{
+       struct ch_struct *ch;
+       char name[DEVICE_NAME_SIZE];
+       int i;
+
+       for (i = nd->nd_chan_count - 1; i >= n; --i) {
+               ch = nd->nd_chan + i;
+
+               /*
+                *  Make any open ports inoperative.
+                */
+               ch->ch_state = CS_IDLE;
+
+               ch->ch_otype = 0;
+               ch->ch_otype_waiting = 0;
+
+               /*
+                *  Only "HANGUP" if we care about carrier
+                *  transitions and we are already open.
+                */
+               if (ch->ch_open_count != 0) {
+                       ch->ch_flag |= CH_HANGUP;
+                       dgrp_carrier(ch);
+               }
+
+               /*
+                * Unlike the CH_HANGUP flag above, use another
+                * flag to indicate to the RealPort state machine
+                * that this port has disappeared.
+                */
+               if (ch->ch_open_count != 0)
+                       ch->ch_flag |= CH_PORT_GONE;
+
+               wake_up_interruptible(&ch->ch_flag_wait);
+
+               nd->nd_chan_count = i;
+
+               kfree(ch->ch_tbuf);
+               ch->ch_tbuf = NULL;
+
+               kfree(ch->ch_rbuf);
+               ch->ch_rbuf = NULL;
+
+               nd->nd_chan_count = i;
+
+               dgrp_remove_tty_sysfs(ch->ch_tun.un_sysfs);
+               snprintf(name, DEVICE_NAME_SIZE, "tty_%d", i);
+               sysfs_remove_link(&nd->nd_class_dev->kobj, name);
+               tty_unregister_device(nd->nd_serial_ttdriver, i);
+
+               /*
+                * NOTE: We don't support "cu" devices anymore, so don't
+                * unregister them here anymore.
+                */
+
+               if (dgrp_register_prdevices) {
+                       dgrp_remove_tty_sysfs(ch->ch_pun.un_sysfs);
+                       snprintf(name, DEVICE_NAME_SIZE, "pr_%d", i);
+                       sysfs_remove_link(&nd->nd_class_dev->kobj, name);
+                       tty_unregister_device(nd->nd_xprint_ttdriver, i);
+               }
+       }
+}
+
+/**
+ * dgrp_chan_count() -- Adjust the node channel count.
+ * @nd: pointer to a node structure
+ * @n: new value for channel count
+ *
+ * Adjusts the node channel count.  If new ports have appeared, it tries
+ * to signal those processes that might have been waiting for ports to
+ * appear.  If ports have disappeared it tries to signal those processes
+ * that might be hung waiting for a response for the now non-existant port.
+ */
+static void dgrp_chan_count(struct nd_struct *nd, int n)
+{
+       if (n == nd->nd_chan_count)
+               return;
+
+       if (n > nd->nd_chan_count)
+               increase_channel_count(nd, n);
+
+       if (n < nd->nd_chan_count)
+               decrease_channel_count(nd, n);
+}
+
+/**
+ * dgrp_monitor() -- send data to the device monitor queue
+ * @nd: pointer to a node structure
+ * @buf: data to copy to the monitoring buffer
+ * @len: number of bytes to transfer to the buffer
+ *
+ * Called by the net device routines to send data to the device
+ * monitor queue.  If the device monitor buffer is too full to
+ * accept the data, it waits until the buffer is ready.
+ */
+static void dgrp_monitor(struct nd_struct *nd, u8 *buf, int len)
+{
+       int n;
+       int r;
+       int rtn;
+
+       /*
+        *  Grab monitor lock.
+        */
+       down(&nd->nd_mon_semaphore);
+
+       /*
+        *  Loop while data remains.
+        */
+       while ((len > 0) && (nd->nd_mon_buf)) {
+               /*
+                *  Determine the amount of available space left in the
+                *  buffer.  If there's none, wait until some appears.
+                */
+
+               n = (nd->nd_mon_out - nd->nd_mon_in - 1) & MON_MASK;
+
+               if (!n) {
+                       nd->nd_mon_flag |= MON_WAIT_SPACE;
+
+                       up(&nd->nd_mon_semaphore);
+
+                       /*
+                        * Go to sleep waiting until the condition becomes true.
+                        */
+                       rtn = wait_event_interruptible(nd->nd_mon_wqueue,
+                                                      ((nd->nd_mon_flag & MON_WAIT_SPACE) == 0));
+
+/* FIXME: really ignore rtn? */
+
+                       /*
+                        *  We can't exit here if we receive a signal, since
+                        *  to do so would trash the debug stream.
+                        */
+
+                       down(&nd->nd_mon_semaphore);
+
+                       continue;
+               }
+
+               /*
+                * Copy as much data as will fit.
+                */
+
+               if (n > len)
+                       n = len;
+
+               r = MON_MAX - nd->nd_mon_in;
+
+               if (r <= n) {
+                       memcpy(nd->nd_mon_buf + nd->nd_mon_in, buf, r);
+
+                       n -= r;
+
+                       nd->nd_mon_in = 0;
+
+                       buf += r;
+                       len -= r;
+               }
+
+               memcpy(nd->nd_mon_buf + nd->nd_mon_in, buf, n);
+
+               nd->nd_mon_in += n;
+
+               buf += n;
+               len -= n;
+
+               if (nd->nd_mon_in >= MON_MAX)
+                       pr_info_ratelimited("%s - nd_mon_in (%i) >= MON_MAX\n",
+                                           __func__, nd->nd_mon_in);
+
+               /*
+                *  Wakeup any thread waiting for data
+                */
+
+               if (nd->nd_mon_flag & MON_WAIT_DATA) {
+                       nd->nd_mon_flag &= ~MON_WAIT_DATA;
+                       wake_up_interruptible(&nd->nd_mon_wqueue);
+               }
+       }
+
+       /*
+        *  Release the monitor lock.
+        */
+       up(&nd->nd_mon_semaphore);
+}
+
+/**
+ * dgrp_encode_time() -- Encodes rpdump time into a 4-byte quantity.
+ * @nd: pointer to a node structure
+ * @buf: destination buffer
+ *
+ * Encodes "rpdump" time into a 4-byte quantity.  Time is measured since
+ * open.
+ */
+static void dgrp_encode_time(struct nd_struct *nd, u8 *buf)
+{
+       ulong t;
+
+       /*
+        *  Convert time in HZ since open to time in milliseconds
+        *  since open.
+        */
+       t = jiffies - nd->nd_mon_lbolt;
+       t = 1000 * (t / HZ) + 1000 * (t % HZ) / HZ;
+
+       put_unaligned_be32((uint)(t & 0xffffffff), buf);
+}
+
+
+
+/**
+ * dgrp_monitor_message() -- Builds a rpdump style message.
+ * @nd: pointer to a node structure
+ * @message: destination buffer
+ */
+static void dgrp_monitor_message(struct nd_struct *nd, char *message)
+{
+       u8 header[7];
+       int n;
+
+       header[0] = RPDUMP_MESSAGE;
+
+       dgrp_encode_time(nd, header + 1);
+
+       n = strlen(message);
+
+       put_unaligned_be16(n, header + 5);
+
+       dgrp_monitor(nd, header, sizeof(header));
+       dgrp_monitor(nd, (u8 *) message, n);
+}
+
+
+
+/**
+ * dgrp_monitor_reset() -- Note a reset in the monitoring buffer.
+ * @nd: pointer to a node structure
+ */
+static void dgrp_monitor_reset(struct nd_struct *nd)
+{
+       u8 header[5];
+
+       header[0] = RPDUMP_RESET;
+
+       dgrp_encode_time(nd, header + 1);
+
+       dgrp_monitor(nd, header, sizeof(header));
+}
+
+/**
+ * dgrp_monitor_data() -- builds a monitor data packet
+ * @nd: pointer to a node structure
+ * @type: type of message to be logged
+ * @buf: data to be logged
+ * @size: number of bytes in the buffer
+ */
+static void dgrp_monitor_data(struct nd_struct *nd, u8 type, u8 *buf, int size)
+{
+       u8 header[7];
+
+       header[0] = type;
+
+       dgrp_encode_time(nd, header + 1);
+
+       put_unaligned_be16(size, header + 5);
+
+       dgrp_monitor(nd, header, sizeof(header));
+       dgrp_monitor(nd, buf, size);
+}
+
+static int alloc_nd_buffers(struct nd_struct *nd)
+{
+
+       nd->nd_iobuf = NULL;
+       nd->nd_writebuf = NULL;
+       nd->nd_inputbuf = NULL;
+       nd->nd_inputflagbuf = NULL;
+
+       /*
+        *  Allocate the network read/write buffer.
+        */
+       nd->nd_iobuf = kzalloc(UIO_MAX + 10, GFP_KERNEL);
+       if (!nd->nd_iobuf)
+               goto out_err;
+
+       /*
+        * Allocate a buffer for doing the copy from user space to
+        * kernel space in the write routines.
+        */
+       nd->nd_writebuf = kzalloc(WRITEBUFLEN, GFP_KERNEL);
+       if (!nd->nd_writebuf)
+               goto out_err;
+
+       /*
+        * Allocate a buffer for doing the copy from kernel space to
+        * tty buffer space in the read routines.
+        */
+       nd->nd_inputbuf = kzalloc(MYFLIPLEN, GFP_KERNEL);
+       if (!nd->nd_inputbuf)
+               goto out_err;
+
+       /*
+        * Allocate a buffer for doing the copy from kernel space to
+        * tty buffer space in the read routines.
+        */
+       nd->nd_inputflagbuf = kzalloc(MYFLIPLEN, GFP_KERNEL);
+       if (!nd->nd_inputflagbuf)
+               goto out_err;
+
+       return 0;
+
+out_err:
+       kfree(nd->nd_iobuf);
+       kfree(nd->nd_writebuf);
+       kfree(nd->nd_inputbuf);
+       kfree(nd->nd_inputflagbuf);
+       return -ENOMEM;
+}
+
+/*
+ * dgrp_net_open() -- Open the NET device for a particular PortServer
+ */
+static int dgrp_net_open(struct inode *inode, struct file *file)
+{
+       struct nd_struct *nd;
+       struct proc_dir_entry *de;
+       ulong  lock_flags;
+       int rtn;
+
+       rtn = try_module_get(THIS_MODULE);
+       if (!rtn)
+               return -EAGAIN;
+
+       if (!capable(CAP_SYS_ADMIN)) {
+               rtn = -EPERM;
+               goto done;
+       }
+
+       /*
+        *  Make sure that the "private_data" field hasn't already been used.
+        */
+       if (file->private_data) {
+               rtn = -EINVAL;
+               goto done;
+       }
+
+       /*
+        *  Get the node pointer, and fail if it doesn't exist.
+        */
+       de = PDE(inode);
+       if (!de) {
+               rtn = -ENXIO;
+               goto done;
+       }
+
+       nd = (struct nd_struct *) de->data;
+       if (!nd) {
+               rtn = -ENXIO;
+               goto done;
+       }
+
+       file->private_data = (void *) nd;
+
+       /*
+        *  Grab the NET lock.
+        */
+       down(&nd->nd_net_semaphore);
+
+       if (nd->nd_state != NS_CLOSED) {
+               rtn = -EBUSY;
+               goto unlock;
+       }
+
+       /*
+        *  Initialize the link speed parameters.
+        */
+
+       nd->nd_link.lk_fast_rate = UIO_MAX;
+       nd->nd_link.lk_slow_rate = UIO_MAX;
+
+       nd->nd_link.lk_fast_delay = 1000;
+       nd->nd_link.lk_slow_delay = 1000;
+
+       nd->nd_link.lk_header_size = 46;
+
+
+       rtn = alloc_nd_buffers(nd);
+       if (rtn)
+               goto unlock;
+
+       /*
+        *  The port is now open, so move it to the IDLE state
+        */
+       dgrp_net_idle(nd);
+
+       nd->nd_tx_time = jiffies;
+
+       /*
+        *  If the polling routing is not running, start it running here
+        */
+       spin_lock_irqsave(&dgrp_poll_data.poll_lock, lock_flags);
+
+       if (!dgrp_poll_data.node_active_count) {
+               dgrp_poll_data.node_active_count = 2;
+               dgrp_poll_data.timer.expires = jiffies +
+                       dgrp_poll_tick * HZ / 1000;
+               add_timer(&dgrp_poll_data.timer);
+       }
+
+       spin_unlock_irqrestore(&dgrp_poll_data.poll_lock, lock_flags);
+
+       dgrp_monitor_message(nd, "Net Open");
+
+unlock:
+       /*
+        *  Release the NET lock.
+        */
+       up(&nd->nd_net_semaphore);
+
+done:
+       if (rtn)
+               module_put(THIS_MODULE);
+
+       return rtn;
+}
+
+/* dgrp_net_release() -- close the NET device for a particular PortServer */
+static int dgrp_net_release(struct inode *inode, struct file *file)
+{
+       struct nd_struct *nd;
+       ulong  lock_flags;
+
+       nd = (struct nd_struct *)(file->private_data);
+       if (!nd)
+               goto done;
+
+/* TODO : historical locking placeholder */
+/*
+ *  In the HPUX version of the RealPort driver (which served as a basis
+ *  for this driver) this locking code was used.  Saved if ever we need
+ *  to review the locking under Linux.
+ */
+/*     spinlock(&nd->nd_lock); */
+
+
+       /*
+        *  Grab the NET lock.
+        */
+       down(&nd->nd_net_semaphore);
+
+       /*
+        *  Before "closing" the internal connection, make sure all
+        *  ports are "idle".
+        */
+       dgrp_net_idle(nd);
+
+       nd->nd_state = NS_CLOSED;
+       nd->nd_flag = 0;
+
+       /*
+        *  TODO ... must the wait queue be reset on close?
+        *  should any pending waiters be reset?
+        *  Let's decide to assert that the waitq is empty... and see
+        *  how soon we break.
+        */
+       if (waitqueue_active(&nd->nd_tx_waitq))
+               pr_info("%s - expected waitqueue_active to be false\n",
+                       __func__);
+
+       nd->nd_send = 0;
+
+       kfree(nd->nd_iobuf);
+       nd->nd_iobuf = NULL;
+
+/* TODO : historical locking placeholder */
+/*
+ *  In the HPUX version of the RealPort driver (which served as a basis
+ *  for this driver) this locking code was used.  Saved if ever we need
+ *  to review the locking under Linux.
+ */
+/*     spinunlock( &nd->nd_lock ); */
+
+
+       kfree(nd->nd_writebuf);
+       nd->nd_writebuf = NULL;
+
+       kfree(nd->nd_inputbuf);
+       nd->nd_inputbuf = NULL;
+
+       kfree(nd->nd_inputflagbuf);
+       nd->nd_inputflagbuf = NULL;
+
+/* TODO : historical locking placeholder */
+/*
+ *  In the HPUX version of the RealPort driver (which served as a basis
+ *  for this driver) this locking code was used.  Saved if ever we need
+ *  to review the locking under Linux.
+ */
+/*     spinlock(&nd->nd_lock); */
+
+       /*
+        *  Set the active port count to zero.
+        */
+       dgrp_chan_count(nd, 0);
+
+/* TODO : historical locking placeholder */
+/*
+ *  In the HPUX version of the RealPort driver (which served as a basis
+ *  for this driver) this locking code was used.  Saved if ever we need
+ *  to review the locking under Linux.
+ */
+/*     spinunlock(&nd->nd_lock); */
+
+       /*
+        *  Release the NET lock.
+        */
+       up(&nd->nd_net_semaphore);
+
+       /*
+        *  Cause the poller to stop scheduling itself if this is
+        *  the last active node.
+        */
+       spin_lock_irqsave(&dgrp_poll_data.poll_lock, lock_flags);
+
+       if (dgrp_poll_data.node_active_count == 2) {
+               del_timer(&dgrp_poll_data.timer);
+               dgrp_poll_data.node_active_count = 0;
+       }
+
+       spin_unlock_irqrestore(&dgrp_poll_data.poll_lock, lock_flags);
+
+done:
+       down(&nd->nd_net_semaphore);
+
+       dgrp_monitor_message(nd, "Net Close");
+
+       up(&nd->nd_net_semaphore);
+
+       module_put(THIS_MODULE);
+       file->private_data = NULL;
+       return 0;
+}
+
+/* used in dgrp_send to setup command header */
+static inline u8 *set_cmd_header(u8 *b, u8 port, u8 cmd)
+{
+       *b++ = 0xb0 + (port & 0x0f);
+       *b++ = cmd;
+       return b;
+}
+
+/**
+ * dgrp_send() -- build a packet for transmission to the server
+ * @nd: pointer to a node structure
+ * @tmax: maximum bytes to transmit
+ *
+ * returns number of bytes sent
+ */
+static int dgrp_send(struct nd_struct *nd, long tmax)
+{
+       struct ch_struct *ch = nd->nd_chan;
+       u8 *b;
+       u8 *buf;
+       u8 *mbuf;
+       u8 port;
+       int mod;
+       long send;
+       int maxport;
+       long lastport = -1;
+       ushort rwin;
+       long in;
+       ushort n;
+       long t;
+       long ttotal;
+       long tchan;
+       long tsend;
+       ushort tsafe;
+       long work;
+       long send_sync;
+       long wanted_sync_port = -1;
+       ushort tdata[CHAN_MAX];
+       long used_buffer;
+
+       mbuf = nd->nd_iobuf + UIO_BASE;
+       buf = b = mbuf;
+
+       send_sync = nd->nd_link.lk_slow_rate < UIO_MAX;
+
+       ttotal = 0;
+       tchan = 0;
+
+       memset(tdata, 0, sizeof(tdata));
+
+
+       /*
+        * If there are any outstanding requests to be serviced,
+        * service them here.
+        */
+       if (nd->nd_send & NR_PASSWORD) {
+
+               /*
+                *  Send Password response.
+                */
+
+               b[0] = 0xfc;
+               b[1] = 0x20;
+               put_unaligned_be16(strlen(nd->password), b + 2);
+               b += 4;
+               b += strlen(nd->password);
+               nd->nd_send &= ~(NR_PASSWORD);
+       }
+
+
+       /*
+        *  Loop over all modules to generate commands, and determine
+        *  the amount of data queued for transmit.
+        */
+
+       for (mod = 0, port = 0; port < nd->nd_chan_count; mod++) {
+               /*
+                *  If this is not the current module, enter a module select
+                *  code in the buffer.
+                */
+
+               if (mod != nd->nd_tx_module)
+                       mbuf = ++b;
+
+               /*
+                *  Loop to process one module.
+                */
+
+               maxport = port + 16;
+
+               if (maxport > nd->nd_chan_count)
+                       maxport = nd->nd_chan_count;
+
+               for (; port < maxport; port++, ch++) {
+                       /*
+                        *  Switch based on channel state.
+                        */
+
+                       switch (ch->ch_state) {
+                       /*
+                        *  Send requests when the port is closed, and there
+                        *  are no Open, Close or Cancel requests expected.
+                        */
+
+                       case CS_IDLE:
+                               /*
+                                * Wait until any open error code
+                                * has been delivered to all
+                                * associated ports.
+                                */
+
+                               if (ch->ch_open_error) {
+                                       if (ch->ch_wait_count[ch->ch_otype]) {
+                                               work = 1;
+                                               break;
+                                       }
+
+                                       ch->ch_open_error = 0;
+                               }
+
+                               /*
+                                *  Wait until the channel HANGUP flag is reset
+                                *  before sending the first open.  We can only
+                                *  get to this state after a server disconnect.
+                                */
+
+                               if ((ch->ch_flag & CH_HANGUP) != 0)
+                                       break;
+
+                               /*
+                                *  If recovering from a TCP disconnect, or if
+                                *  there is an immediate open pending, send an
+                                *  Immediate Open request.
+                                */
+                               if ((ch->ch_flag & CH_PORT_GONE) ||
+                                   ch->ch_wait_count[OTYPE_IMMEDIATE] != 0) {
+                                       b = set_cmd_header(b, port, 10);
+                                       *b++ = 0;
+
+                                       ch->ch_state = CS_WAIT_OPEN;
+                                       ch->ch_otype = OTYPE_IMMEDIATE;
+                                       break;
+                               }
+
+       /*
+        *  If there is no Persistent or Incoming Open on the wait
+        *  list in the server, and a thread is waiting for a
+        *  Persistent or Incoming Open, send a Persistent or Incoming
+        *  Open Request.
+        */
+                               if (ch->ch_otype_waiting == 0) {
+                                       if (ch->ch_wait_count[OTYPE_PERSISTENT] != 0) {
+                                               b = set_cmd_header(b, port, 10);
+                                               *b++ = 1;
+
+                                               ch->ch_state = CS_WAIT_OPEN;
+                                               ch->ch_otype = OTYPE_PERSISTENT;
+                                       } else if (ch->ch_wait_count[OTYPE_INCOMING] != 0) {
+                                               b = set_cmd_header(b, port, 10);
+                                               *b++ = 2;
+
+                                               ch->ch_state = CS_WAIT_OPEN;
+                                               ch->ch_otype = OTYPE_INCOMING;
+                                       }
+                                       break;
+                               }
+
+                               /*
+                                *  If a Persistent or Incoming Open is pending in
+                                *  the server, but there is no longer an open
+                                *  thread waiting for it, cancel the request.
+                                */
+
+                               if (ch->ch_wait_count[ch->ch_otype_waiting] == 0) {
+                                       b = set_cmd_header(b, port, 10);
+                                       *b++ = 4;
+
+                                       ch->ch_state = CS_WAIT_CANCEL;
+                                       ch->ch_otype = ch->ch_otype_waiting;
+                               }
+                               break;
+
+                               /*
+                                *  Send port parameter queries.
+                                */
+                       case CS_SEND_QUERY:
+                               /*
+                                *  Clear out all FEP state that might remain
+                                *  from the last connection.
+                                */
+
+                               ch->ch_flag |= CH_PARAM;
+
+                               ch->ch_flag &= ~CH_RX_FLUSH;
+
+                               ch->ch_expect = 0;
+
+                               ch->ch_s_tin   = 0;
+                               ch->ch_s_tpos  = 0;
+                               ch->ch_s_tsize = 0;
+                               ch->ch_s_treq  = 0;
+                               ch->ch_s_elast = 0;
+
+                               ch->ch_s_rin   = 0;
+                               ch->ch_s_rwin  = 0;
+                               ch->ch_s_rsize = 0;
+
+                               ch->ch_s_tmax  = 0;
+                               ch->ch_s_ttime = 0;
+                               ch->ch_s_rmax  = 0;
+                               ch->ch_s_rtime = 0;
+                               ch->ch_s_rlow  = 0;
+                               ch->ch_s_rhigh = 0;
+
+                               ch->ch_s_brate = 0;
+                               ch->ch_s_iflag = 0;
+                               ch->ch_s_cflag = 0;
+                               ch->ch_s_oflag = 0;
+                               ch->ch_s_xflag = 0;
+
+                               ch->ch_s_mout  = 0;
+                               ch->ch_s_mflow = 0;
+                               ch->ch_s_mctrl = 0;
+                               ch->ch_s_xon   = 0;
+                               ch->ch_s_xoff  = 0;
+                               ch->ch_s_lnext = 0;
+                               ch->ch_s_xxon  = 0;
+                               ch->ch_s_xxoff = 0;
+
+                               /* Send Sequence Request */
+                               b = set_cmd_header(b, port, 14);
+
+                               /* Configure Event Conditions Packet */
+                               b = set_cmd_header(b, port, 42);
+                               put_unaligned_be16(0x02c0, b);
+                               b += 2;
+                               *b++ = (DM_DTR | DM_RTS | DM_CTS |
+                                       DM_DSR | DM_RI | DM_CD);
+
+                               /* Send Status Request */
+                               b = set_cmd_header(b, port, 16);
+
+                               /* Send Buffer Request  */
+                               b = set_cmd_header(b, port, 20);
+
+                               /* Send Port Capability Request */
+                               b = set_cmd_header(b, port, 22);
+
+                               ch->ch_expect = (RR_SEQUENCE |
+                                                RR_STATUS  |
+                                                RR_BUFFER |
+                                                RR_CAPABILITY);
+
+                               ch->ch_state = CS_WAIT_QUERY;
+
+                               /* Raise modem signals */
+                               b = set_cmd_header(b, port, 44);
+
+                               if (ch->ch_flag & CH_PORT_GONE)
+                                       ch->ch_s_mout = ch->ch_mout;
+                               else
+                                       ch->ch_s_mout = ch->ch_mout = DM_DTR | DM_RTS;
+
+                               *b++ = ch->ch_mout;
+                               *b++ = ch->ch_s_mflow = 0;
+                               *b++ = ch->ch_s_mctrl = ch->ch_mctrl = 0;
+
+                               if (ch->ch_flag & CH_PORT_GONE)
+                                       ch->ch_flag &= ~CH_PORT_GONE;
+
+                               break;
+
+                       /*
+                        *  Handle normal open and ready mode.
+                        */
+
+                       case CS_READY:
+
+                               /*
+                                *  If the port is not open, and there are no
+                                *  no longer any ports requesting an open,
+                                *  then close the port.
+                                */
+
+                               if (ch->ch_open_count == 0 &&
+                                   ch->ch_wait_count[ch->ch_otype] == 0) {
+                                       goto send_close;
+                               }
+
+       /*
+        *  Process waiting input.
+        *
+        *  If there is no one to read it, discard the data.
+        *
+        *  Otherwise if we are not in fastcook mode, or if there is a
+        *  fastcook thread waiting for data, send the data to the
+        *  line discipline.
+        */
+                               if (ch->ch_rin != ch->ch_rout) {
+                                       if (ch->ch_tun.un_open_count == 0 ||
+                                            (ch->ch_tun.un_flag & UN_CLOSING) ||
+                                           (ch->ch_cflag & CF_CREAD) == 0) {
+                                               ch->ch_rout = ch->ch_rin;
+                                       } else if ((ch->ch_flag & CH_FAST_READ) == 0 ||
+                                                       ch->ch_inwait != 0) {
+                                               dgrp_input(ch);
+
+                                               if (ch->ch_rin != ch->ch_rout)
+                                                       work = 1;
+                                       }
+                               }
+
+                               /*
+                                *  Handle receive flush, and changes to
+                                *  server port parameters.
+                                */
+
+                               if (ch->ch_flag & (CH_RX_FLUSH | CH_PARAM)) {
+                               /*
+                                *  If we are in receive flush mode,
+                                *  and enough data has gone by, reset
+                                *  receive flush mode.
+                                */
+                                       if (ch->ch_flag & CH_RX_FLUSH) {
+                                               if (((ch->ch_flush_seq - nd->nd_seq_out) & SEQ_MASK) >
+                                                   ((nd->nd_seq_in - nd->nd_seq_out) & SEQ_MASK))
+                                                       ch->ch_flag &= ~CH_RX_FLUSH;
+                                               else
+                                                       work = 1;
+                                       }
+
+                                       /*
+                                        *  Send TMAX, TTIME.
+                                        */
+
+                                       if (ch->ch_s_tmax  != ch->ch_tmax ||
+                                           ch->ch_s_ttime != ch->ch_ttime) {
+                                               b = set_cmd_header(b, port, 48);
+
+                                               ch->ch_s_tmax = ch->ch_tmax;
+                                               ch->ch_s_ttime = ch->ch_ttime;
+
+                                               put_unaligned_be16(ch->ch_s_tmax,
+                                                                  b);
+                                               b += 2;
+
+                                               put_unaligned_be16(ch->ch_s_ttime,
+                                                                  b);
+                                               b += 2;
+                                       }
+
+                                       /*
+                                        *  Send RLOW, RHIGH.
+                                        */
+
+                                       if (ch->ch_s_rlow  != ch->ch_rlow ||
+                                           ch->ch_s_rhigh != ch->ch_rhigh) {
+                                               b = set_cmd_header(b, port, 45);
+
+                                               ch->ch_s_rlow  = ch->ch_rlow;
+                                               ch->ch_s_rhigh = ch->ch_rhigh;
+
+                                               put_unaligned_be16(ch->ch_s_rlow,
+                                                                  b);
+                                               b += 2;
+
+                                               put_unaligned_be16(ch->ch_s_rhigh,
+                                                                  b);
+                                               b += 2;
+                                       }
+
+                                       /*
+                                        *  Send BRATE, CFLAG, IFLAG,
+                                        *  OFLAG, XFLAG.
+                                        */
+
+                                       if (ch->ch_s_brate != ch->ch_brate ||
+                                           ch->ch_s_cflag != ch->ch_cflag ||
+                                           ch->ch_s_iflag != ch->ch_iflag ||
+                                           ch->ch_s_oflag != ch->ch_oflag ||
+                                           ch->ch_s_xflag != ch->ch_xflag) {
+                                               b = set_cmd_header(b, port, 40);
+
+                                               ch->ch_s_brate = ch->ch_brate;
+                                               ch->ch_s_cflag = ch->ch_cflag;
+                                               ch->ch_s_iflag = ch->ch_iflag;
+                                               ch->ch_s_oflag = ch->ch_oflag;
+                                               ch->ch_s_xflag = ch->ch_xflag;
+
+                                               put_unaligned_be16(ch->ch_s_brate,
+                                                                  b);
+                                               b += 2;
+
+                                               put_unaligned_be16(ch->ch_s_cflag,
+                                                                  b);
+                                               b += 2;
+
+                                               put_unaligned_be16(ch->ch_s_iflag,
+                                                                  b);
+                                               b += 2;
+
+                                               put_unaligned_be16(ch->ch_s_oflag,
+                                                                  b);
+                                               b += 2;
+
+                                               put_unaligned_be16(ch->ch_s_xflag,
+                                                                  b);
+                                               b += 2;
+                                       }
+
+                                       /*
+                                        *  Send MOUT, MFLOW, MCTRL.
+                                        */
+
+                                       if (ch->ch_s_mout  != ch->ch_mout  ||
+                                           ch->ch_s_mflow != ch->ch_mflow ||
+                                           ch->ch_s_mctrl != ch->ch_mctrl) {
+                                               b = set_cmd_header(b, port, 44);
+
+                                               *b++ = ch->ch_s_mout  = ch->ch_mout;
+                                               *b++ = ch->ch_s_mflow = ch->ch_mflow;
+                                               *b++ = ch->ch_s_mctrl = ch->ch_mctrl;
+                                       }
+
+                                       /*
+                                        *  Send Flow control characters.
+                                        */
+
+                                       if (ch->ch_s_xon   != ch->ch_xon   ||
+                                           ch->ch_s_xoff  != ch->ch_xoff  ||
+                                           ch->ch_s_lnext != ch->ch_lnext ||
+                                           ch->ch_s_xxon  != ch->ch_xxon  ||
+                                           ch->ch_s_xxoff != ch->ch_xxoff) {
+                                               b = set_cmd_header(b, port, 46);
+
+                                               *b++ = ch->ch_s_xon   = ch->ch_xon;
+                                               *b++ = ch->ch_s_xoff  = ch->ch_xoff;
+                                               *b++ = ch->ch_s_lnext = ch->ch_lnext;
+                                               *b++ = ch->ch_s_xxon  = ch->ch_xxon;
+                                               *b++ = ch->ch_s_xxoff = ch->ch_xxoff;
+                                       }
+
+                                       /*
+                                        *  Send RMAX, RTIME.
+                                        */
+
+                                       if (ch->ch_s_rmax != ch->ch_rmax ||
+                                           ch->ch_s_rtime != ch->ch_rtime) {
+                                               b = set_cmd_header(b, port, 47);
+
+                                               ch->ch_s_rmax  = ch->ch_rmax;
+                                               ch->ch_s_rtime = ch->ch_rtime;
+
+                                               put_unaligned_be16(ch->ch_s_rmax,
+                                                                  b);
+                                               b += 2;
+
+                                               put_unaligned_be16(ch->ch_s_rtime,
+                                                                  b);
+                                               b += 2;
+                                       }
+
+                                       ch->ch_flag &= ~CH_PARAM;
+                                       wake_up_interruptible(&ch->ch_flag_wait);
+                               }
+
+
+                               /*
+                                *  Handle action commands.
+                                */
+
+                               if (ch->ch_send != 0) {
+                                       /* int send = ch->ch_send & ~ch->ch_expect; */
+                                       send = ch->ch_send & ~ch->ch_expect;
+
+                                       /* Send character immediate */
+                                       if ((send & RR_TX_ICHAR) != 0) {
+                                               b = set_cmd_header(b, port, 60);
+
+                                               *b++ = ch->ch_xon;
+                                               ch->ch_expect |= RR_TX_ICHAR;
+                                       }
+
+                                       /* BREAK request */
+                                       if ((send & RR_TX_BREAK) != 0) {
+                                               if (ch->ch_break_time != 0) {
+                                                       b = set_cmd_header(b, port, 61);
+                                                       put_unaligned_be16(ch->ch_break_time,
+                                                                          b);
+                                                       b += 2;
+
+                                                       ch->ch_expect |= RR_TX_BREAK;
+                                                       ch->ch_break_time = 0;
+                                               } else {
+                                                       ch->ch_send &= ~RR_TX_BREAK;
+                                                       ch->ch_flag &= ~CH_TX_BREAK;
+                                                       wake_up_interruptible(&ch->ch_flag_wait);
+                                               }
+                                       }
+
+                                       /*
+                                        *  Flush input/output buffers.
+                                        */
+
+                                       if ((send & (RR_RX_FLUSH | RR_TX_FLUSH)) != 0) {
+                                               b = set_cmd_header(b, port, 62);
+
+                                               *b++ = ((send & RR_TX_FLUSH) == 0 ? 1 :
+                                                       (send & RR_RX_FLUSH) == 0 ? 2 : 3);
+
+                                               if (send & RR_RX_FLUSH) {
+                                                       ch->ch_flush_seq = nd->nd_seq_in;
+                                                       ch->ch_flag |= CH_RX_FLUSH;
+                                                       work = 1;
+                                                       send_sync = 1;
+                                                       wanted_sync_port = port;
+                                               }
+
+                                               ch->ch_send &= ~(RR_RX_FLUSH | RR_TX_FLUSH);
+                                       }
+
+                                       /*  Pause input/output */
+                                       if ((send & (RR_RX_STOP | RR_TX_STOP)) != 0) {
+                                               b = set_cmd_header(b, port, 63);
+                                               *b = 0;
+
+                                               if ((send & RR_TX_STOP) != 0)
+                                                       *b |= EV_OPU;
+
+                                               if ((send & RR_RX_STOP) != 0)
+                                                       *b |= EV_IPU;
+
+                                               b++;
+
+                                               ch->ch_send &= ~(RR_RX_STOP | RR_TX_STOP);
+                                       }
+
+                                       /* Start input/output */
+                                       if ((send & (RR_RX_START | RR_TX_START)) != 0) {
+                                               b = set_cmd_header(b, port, 64);
+                                               *b = 0;
+
+                                               if ((send & RR_TX_START) != 0)
+                                                       *b |= EV_OPU | EV_OPS | EV_OPX;
+
+                                               if ((send & RR_RX_START) != 0)
+                                                       *b |= EV_IPU | EV_IPS;
+
+                                               b++;
+
+                                               ch->ch_send &= ~(RR_RX_START | RR_TX_START);
+                                       }
+                               }
+
+
+                               /*
+                                *  Send a window sequence to acknowledge received data.
+                                */
+
+                               rwin = (ch->ch_s_rin +
+                                       ((ch->ch_rout - ch->ch_rin - 1) & RBUF_MASK));
+
+                               n = (rwin - ch->ch_s_rwin) & 0xffff;
+
+                               if (n >= RBUF_MAX / 4) {
+                                       b[0] = 0xa0 + (port & 0xf);
+                                       ch->ch_s_rwin = rwin;
+                                       put_unaligned_be16(rwin, b + 1);
+                                       b += 3;
+                               }
+
+                               /*
+                                *  If the terminal is waiting on LOW
+                                *  water or EMPTY, and the condition
+                                *  is now satisfied, call the line
+                                *  discipline to put more data in the
+                                *  buffer.
+                                */
+
+                               n = (ch->ch_tin - ch->ch_tout) & TBUF_MASK;
+
+                               if ((ch->ch_tun.un_flag & (UN_EMPTY|UN_LOW)) != 0) {
+                                       if ((ch->ch_tun.un_flag & UN_LOW) != 0 ?
+                                           (n <= TBUF_LOW) :
+                                           (n == 0 && ch->ch_s_tpos == ch->ch_s_tin)) {
+                                               ch->ch_tun.un_flag &= ~(UN_EMPTY|UN_LOW);
+
+                                               if (waitqueue_active(&((ch->ch_tun.un_tty)->write_wait)))
+                                                       wake_up_interruptible(&((ch->ch_tun.un_tty)->write_wait));
+                                               tty_wakeup(ch->ch_tun.un_tty);
+                                               n = (ch->ch_tin - ch->ch_tout) & TBUF_MASK;
+                                       }
+                               }
+
+                               /*
+                                * If the printer is waiting on LOW
+                                * water, TIME, EMPTY or PWAIT, and is
+                                * now ready to put more data in the
+                                * buffer, call the line discipline to
+                                * do the job.
+                                */
+
+                               if (ch->ch_pun.un_open_count &&
+                                   (ch->ch_pun.un_flag &
+                                   (UN_EMPTY|UN_TIME|UN_LOW|UN_PWAIT)) != 0) {
+
+                                       if ((ch->ch_pun.un_flag & UN_LOW) != 0 ?
+                                           (n <= TBUF_LOW) :
+                                           (ch->ch_pun.un_flag & UN_TIME) != 0 ?
+                                           ((jiffies - ch->ch_waketime) >= 0) :
+                                           (n == 0 && ch->ch_s_tpos == ch->ch_s_tin) &&
+                                           ((ch->ch_pun.un_flag & UN_EMPTY) != 0 ||
+                                           ((ch->ch_tun.un_open_count &&
+                                             ch->ch_tun.un_tty->ops->chars_in_buffer) ?
+                                            (ch->ch_tun.un_tty->ops->chars_in_buffer)(ch->ch_tun.un_tty) == 0
+                                            : 1
+                                           )
+                                           )) {
+                                               ch->ch_pun.un_flag &= ~(UN_EMPTY | UN_TIME | UN_LOW | UN_PWAIT);
+
+                                               if (waitqueue_active(&((ch->ch_pun.un_tty)->write_wait)))
+                                                       wake_up_interruptible(&((ch->ch_pun.un_tty)->write_wait));
+                                               tty_wakeup(ch->ch_pun.un_tty);
+                                               n = (ch->ch_tin - ch->ch_tout) & TBUF_MASK;
+
+                                       } else if ((ch->ch_pun.un_flag & UN_TIME) != 0) {
+                                               work = 1;
+                                       }
+                               }
+
+
+                               /*
+                                *  Determine the max number of bytes
+                                *  this port can send, including
+                                *  packet header overhead.
+                                */
+
+                               t = ((ch->ch_s_tsize + ch->ch_s_tpos - ch->ch_s_tin) & 0xffff);
+
+                               if (n > t)
+                                       n = t;
+
+                               if (n != 0) {
+                                       n += (n <= 8 ? 1 : n <= 255 ? 2 : 3);
+
+                                       tdata[tchan++] = n;
+                                       ttotal += n;
+                               }
+                               break;
+
+                       /*
+                        *  Close the port.
+                        */
+
+send_close:
+                       case CS_SEND_CLOSE:
+                               b = set_cmd_header(b, port, 10);
+                               if (ch->ch_otype == OTYPE_IMMEDIATE)
+                                       *b++ = 3;
+                               else
+                                       *b++ = 4;
+
+                               ch->ch_state = CS_WAIT_CLOSE;
+                               break;
+
+                       /*
+                        *  Wait for a previous server request.
+                        */
+
+                       case CS_WAIT_OPEN:
+                       case CS_WAIT_CANCEL:
+                       case CS_WAIT_FAIL:
+                       case CS_WAIT_QUERY:
+                       case CS_WAIT_CLOSE:
+                               break;
+
+                       default:
+                               pr_info("%s - unexpected channel state (%i)\n",
+                                       __func__, ch->ch_state);
+                       }
+               }
+
+               /*
+                *  If a module select code is needed, drop one in.  If space
+                *  was reserved for one, but none is needed, recover the space.
+                */
+
+               if (mod != nd->nd_tx_module) {
+                       if (b != mbuf) {
+                               mbuf[-1] = 0xf0 | mod;
+                               nd->nd_tx_module = mod;
+                       } else {
+                               b--;
+                       }
+               }
+       }
+
+       /*
+        *  Adjust "tmax" so that under worst case conditions we do
+        *  not overflow either the daemon buffer or the internal
+        *  buffer in the loop that follows.   Leave a safe area
+        *  of 64 bytes so we start getting asserts before we start
+        *  losing data or clobbering memory.
+        */
+
+       n = UIO_MAX - UIO_BASE;
+
+       if (tmax > n)
+               tmax = n;
+
+       tmax -= 64;
+
+       tsafe = tmax;
+
+       /*
+        *  Allocate space for 5 Module Selects, 1 Sequence Request,
+        *  and 1 Set TREQ for each active channel.
+        */
+
+       tmax -= 5 + 3 + 4 * nd->nd_chan_count;
+
+       /*
+        *  Further reduce "tmax" to the available transmit credit.
+        *  Note that this is a soft constraint;  The transmit credit
+        *  can go negative for a time and then recover.
+        */
+
+       n = nd->nd_tx_deposit - nd->nd_tx_charge - nd->nd_link.lk_header_size;
+
+       if (tmax > n)
+               tmax = n;
+
+       /*
+        *  Finally reduce tmax by the number of bytes already in
+        *  the buffer.
+        */
+
+       tmax -= b - buf;
+
+       /*
+        *  Suspend data transmit unless every ready channel can send
+        *  at least 1 character.
+        */
+       if (tmax < 2 * nd->nd_chan_count) {
+               tsend = 1;
+
+       } else if (tchan > 1 && ttotal > tmax) {
+
+               /*
+                *  If transmit is limited by the credit budget, find the
+                *  largest number of characters we can send without driving
+                *  the credit negative.
+                */
+
+               long tm = tmax;
+               int tc = tchan;
+               int try;
+
+               tsend = tm / tc;
+
+               for (try = 0; try < 3; try++) {
+                       int i;
+                       int c = 0;
+
+                       for (i = 0; i < tc; i++) {
+                               if (tsend < tdata[i])
+                                       tdata[c++] = tdata[i];
+                               else
+                                       tm -= tdata[i];
+                       }
+
+                       if (c == tc)
+                               break;
+
+                       tsend = tm / c;
+
+                       if (c == 1)
+                               break;
+
+                       tc = c;
+               }
+
+               tsend = tm / nd->nd_chan_count;
+
+               if (tsend < 2)
+                       tsend = 1;
+
+       } else {
+               /*
+                *  If no budgetary constraints, or only one channel ready
+                *  to send, set the character limit to the remaining
+                *  buffer size.
+                */
+
+               tsend = tmax;
+       }
+
+       tsend -= (tsend <= 9) ? 1 : (tsend <= 257) ? 2 : 3;
+
+       /*
+        *  Loop over all channels, sending queued data.
+        */
+
+       port = 0;
+       ch = nd->nd_chan;
+       used_buffer = tmax;
+
+       for (mod = 0; port < nd->nd_chan_count; mod++) {
+               /*
+                *  If this is not the current module, enter a module select
+                *  code in the buffer.
+                */
+
+               if (mod != nd->nd_tx_module)
+                       mbuf = ++b;
+
+               /*
+                *  Loop to process one module.
+                */
+
+               maxport = port + 16;
+
+               if (maxport > nd->nd_chan_count)
+                       maxport = nd->nd_chan_count;
+
+               for (; port < maxport; port++, ch++) {
+                       if (ch->ch_state != CS_READY)
+                               continue;
+
+                       lastport = port;
+
+                       n = (ch->ch_tin - ch->ch_tout) & TBUF_MASK;
+
+                       /*
+                        *  If there is data that can be sent, send it.
+                        */
+
+                       if (n != 0 && used_buffer > 0) {
+                               t = (ch->ch_s_tsize + ch->ch_s_tpos - ch->ch_s_tin) & 0xffff;
+
+                               if (n > t)
+                                       n = t;
+
+                               if (n > tsend) {
+                                       work = 1;
+                                       n = tsend;
+                               }
+
+                               if (n > used_buffer) {
+                                       work = 1;
+                                       n = used_buffer;
+                               }
+
+                               if (n <= 0)
+                                       continue;
+
+                               /*
+                                *  Create the correct size transmit header,
+                                *  depending on the amount of data to transmit.
+                                */
+
+                               if (n <= 8) {
+
+                                       b[0] = ((n - 1) << 4) + (port & 0xf);
+                                       b += 1;
+
+                               } else if (n <= 255) {
+
+                                       b[0] = 0x80 + (port & 0xf);
+                                       b[1] = n;
+                                       b += 2;
+
+                               } else {
+
+                                       b[0] = 0x90 + (port & 0xf);
+                                       put_unaligned_be16(n, b + 1);
+                                       b += 3;
+                               }
+
+                               ch->ch_s_tin = (ch->ch_s_tin + n) & 0xffff;
+
+                               /*
+                                *  Copy transmit data to the packet.
+                                */
+
+                               t = TBUF_MAX - ch->ch_tout;
+
+                               if (n >= t) {
+                                       memcpy(b, ch->ch_tbuf + ch->ch_tout, t);
+                                       b += t;
+                                       n -= t;
+                                       used_buffer -= t;
+                                       ch->ch_tout = 0;
+                               }
+
+                               memcpy(b, ch->ch_tbuf + ch->ch_tout, n);
+                               b += n;
+                               used_buffer -= n;
+                               ch->ch_tout += n;
+                               n = (ch->ch_tin - ch->ch_tout) & TBUF_MASK;
+                       }
+
+                       /*
+                        *  Wake any terminal unit process waiting in the
+                        *  dgrp_write routine for low water.
+                        */
+
+                       if (n > TBUF_LOW)
+                               continue;
+
+                       if ((ch->ch_flag & CH_LOW) != 0) {
+                               ch->ch_flag &= ~CH_LOW;
+                               wake_up_interruptible(&ch->ch_flag_wait);
+                       }
+
+                       /* selwakeup tty_sel */
+                       if (ch->ch_tun.un_open_count) {
+                               struct tty_struct *tty = (ch->ch_tun.un_tty);
+
+                               if (waitqueue_active(&tty->write_wait))
+                                       wake_up_interruptible(&tty->write_wait);
+
+                               tty_wakeup(tty);
+                       }
+
+                       if (ch->ch_pun.un_open_count) {
+                               struct tty_struct *tty = (ch->ch_pun.un_tty);
+
+                               if (waitqueue_active(&tty->write_wait))
+                                       wake_up_interruptible(&tty->write_wait);
+
+                               tty_wakeup(tty);
+                       }
+
+                       /*
+                        *  Do EMPTY processing.
+                        */
+
+                       if (n != 0)
+                               continue;
+
+                       if ((ch->ch_flag & (CH_EMPTY | CH_DRAIN)) != 0 ||
+                           (ch->ch_pun.un_flag & UN_EMPTY) != 0) {
+                               /*
+                                *  If there is still data in the server, ask the server
+                                *  to notify us when its all gone.
+                                */
+
+                               if (ch->ch_s_treq != ch->ch_s_tin) {
+                                       b = set_cmd_header(b, port, 43);
+
+                                       ch->ch_s_treq = ch->ch_s_tin;
+                                       put_unaligned_be16(ch->ch_s_treq,
+                                                          b);
+                                       b += 2;
+                               }
+
+                               /*
+                                *  If there is a thread waiting for buffer empty,
+                                *  and we are truly empty, wake the thread.
+                                */
+
+                               else if ((ch->ch_flag & CH_EMPTY) != 0 &&
+                                       (ch->ch_send & RR_TX_BREAK) == 0) {
+                                       ch->ch_flag &= ~CH_EMPTY;
+
+                                       wake_up_interruptible(&ch->ch_flag_wait);
+                               }
+                       }
+               }
+
+               /*
+                *  If a module select code is needed, drop one in.  If space
+                *  was reserved for one, but none is needed, recover the space.
+                */
+
+               if (mod != nd->nd_tx_module) {
+                       if (b != mbuf) {
+                               mbuf[-1] = 0xf0 | mod;
+                               nd->nd_tx_module = mod;
+                       } else {
+                               b--;
+                       }
+               }
+       }
+
+       /*
+        *  Send a synchronization sequence associated with the last open
+        *  channel that sent data, and remember the time when the data was
+        *  sent.
+        */
+
+       in = nd->nd_seq_in;
+
+       if ((send_sync || nd->nd_seq_wait[in] != 0) && lastport >= 0) {
+               u8 *bb = b;
+
+               /*
+                * Attempt the use the port that really wanted the sync.
+                * This gets around a race condition where the "lastport" is in
+                * the middle of the close() routine, and by the time we
+                * send this command, it will have already acked the close, and
+                * thus not send the sync response.
+                */
+               if (wanted_sync_port >= 0)
+                       lastport = wanted_sync_port;
+               /*
+                * Set a flag just in case the port is in the middle of a close,
+                * it will not be permitted to actually close until we get an
+                * sync response, and clear the flag there.
+                */
+               ch = nd->nd_chan + lastport;
+               ch->ch_flag |= CH_WAITING_SYNC;
+
+               mod = lastport >> 4;
+
+               if (mod != nd->nd_tx_module) {
+                       bb[0] = 0xf0 + mod;
+                       bb += 1;
+
+                       nd->nd_tx_module = mod;
+               }
+
+               bb = set_cmd_header(bb, lastport, 12);
+               *bb++ = in;
+
+               nd->nd_seq_size[in] = bb - buf;
+               nd->nd_seq_time[in] = jiffies;
+
+               if (++in >= SEQ_MAX)
+                       in = 0;
+
+               if (in != nd->nd_seq_out) {
+                       b = bb;
+                       nd->nd_seq_in = in;
+                       nd->nd_unack += b - buf;
+               }
+       }
+
+       /*
+        *  If there are no open ports, a sync cannot be sent.
+        *  There is nothing left to wait for anyway, so wake any
+        *  thread waiting for an acknowledgement.
+        */
+
+       else if (nd->nd_seq_wait[in] != 0) {
+               nd->nd_seq_wait[in] = 0;
+
+               wake_up_interruptible(&nd->nd_seq_wque[in]);
+       }
+
+       /*
+        *  If there is no traffic for an interval of IDLE_MAX, then
+        *  send a single byte packet.
+        */
+
+       if (b != buf) {
+               nd->nd_tx_time = jiffies;
+       } else if ((ulong)(jiffies - nd->nd_tx_time) >= IDLE_MAX) {
+               *b++ = 0xf0 | nd->nd_tx_module;
+               nd->nd_tx_time = jiffies;
+       }
+
+       n = b - buf;
+
+       if (n >= tsafe)
+               pr_info("%s - n(%i) >= tsafe(%i)\n",
+                       __func__, n, tsafe);
+
+       if (tsend < 0)
+               dgrp_dump(buf, n);
+
+       nd->nd_tx_work = work;
+
+       return n;
+}
+
+/*
+ * dgrp_net_read()
+ * Data to be sent TO the PortServer from the "async." half of the driver.
+ */
+static ssize_t dgrp_net_read(struct file *file, char __user *buf, size_t count,
+                            loff_t *ppos)
+{
+       struct nd_struct *nd;
+       long n;
+       u8 *local_buf;
+       u8 *b;
+       ssize_t rtn;
+
+       /*
+        *  Get the node pointer, and quit if it doesn't exist.
+        */
+       nd = (struct nd_struct *)(file->private_data);
+       if (!nd)
+               return -ENXIO;
+
+       if (count < UIO_MIN)
+               return -EINVAL;
+
+       /*
+        *  Only one read/write operation may be in progress at
+        *  any given time.
+        */
+
+       /*
+        *  Grab the NET lock.
+        */
+       down(&nd->nd_net_semaphore);
+
+       nd->nd_read_count++;
+
+       nd->nd_tx_ready = 0;
+
+       /*
+        *  Determine the effective size of the buffer.
+        */
+
+       if (nd->nd_remain > UIO_BASE)
+               pr_info_ratelimited("%s - nd_remain(%i) > UIO_BASE\n",
+                                   __func__, nd->nd_remain);
+
+       b = local_buf = nd->nd_iobuf + UIO_BASE;
+
+       /*
+        *  Generate data according to the node state.
+        */
+
+       switch (nd->nd_state) {
+       /*
+        *  Initialize the connection.
+        */
+
+       case NS_IDLE:
+               if (nd->nd_mon_buf)
+                       dgrp_monitor_reset(nd);
+
+               /*
+                *  Request a Product ID Packet.
+                */
+
+               b[0] = 0xfb;
+               b[1] = 0x01;
+               b += 2;
+
+               nd->nd_expect |= NR_IDENT;
+
+               /*
+                *  Request a Server Capability ID Response.
+                */
+
+               b[0] = 0xfb;
+               b[1] = 0x02;
+               b += 2;
+
+               nd->nd_expect |= NR_CAPABILITY;
+
+               /*
+                *  Request a Server VPD Response.
+                */
+
+               b[0] = 0xfb;
+               b[1] = 0x18;
+               b += 2;
+
+               nd->nd_expect |= NR_VPD;
+
+               nd->nd_state = NS_WAIT_QUERY;
+               break;
+
+       /*
+        *  We do serious communication with the server only in
+        *  the READY state.
+        */
+
+       case NS_READY:
+               b = dgrp_send(nd, count) + local_buf;
+               break;
+
+       /*
+        *  Send off an error after receiving a bogus message
+        *  from the server.
+        */
+
+       case NS_SEND_ERROR:
+               n = strlen(nd->nd_error);
+
+               b[0] = 0xff;
+               b[1] = n;
+               memcpy(b + 2, nd->nd_error, n);
+               b += 2 + n;
+
+               dgrp_net_idle(nd);
+               /*
+                *  Set the active port count to zero.
+                */
+               dgrp_chan_count(nd, 0);
+               break;
+
+       default:
+               break;
+       }
+
+       n = b - local_buf;
+
+       if (n != 0) {
+               nd->nd_send_count++;
+
+               nd->nd_tx_byte   += n + nd->nd_link.lk_header_size;
+               nd->nd_tx_charge += n + nd->nd_link.lk_header_size;
+       }
+
+       rtn = copy_to_user((void __user *)buf, local_buf, n);
+       if (rtn) {
+               rtn = -EFAULT;
+               goto done;
+       }
+
+       *ppos += n;
+
+       rtn = n;
+
+       if (nd->nd_mon_buf)
+               dgrp_monitor_data(nd, RPDUMP_CLIENT, local_buf, n);
+
+       /*
+        *  Release the NET lock.
+        */
+done:
+       up(&nd->nd_net_semaphore);
+
+       return rtn;
+}
+
+/**
+ * dgrp_receive() -- decode data packets received from the remote PortServer.
+ * @nd: pointer to a node structure
+ */
+static void dgrp_receive(struct nd_struct *nd)
+{
+       struct ch_struct *ch;
+       u8 *buf;
+       u8 *b;
+       u8 *dbuf;
+       char *error;
+       long port;
+       long dlen;
+       long plen;
+       long remain;
+       long n;
+       long mlast;
+       long elast;
+       long mstat;
+       long estat;
+
+       char ID[3];
+
+       nd->nd_tx_time = jiffies;
+
+       ID_TO_CHAR(nd->nd_ID, ID);
+
+       b = buf = nd->nd_iobuf;
+       remain = nd->nd_remain;
+
+       /*
+        *  Loop to process Realport protocol packets.
+        */
+
+       while (remain > 0) {
+               int n0 = b[0] >> 4;
+               int n1 = b[0] & 0x0f;
+
+               if (n0 <= 12) {
+                       port = (nd->nd_rx_module << 4) + n1;
+
+                       if (port >= nd->nd_chan_count) {
+                               error = "Improper Port Number";
+                               goto prot_error;
+                       }
+
+                       ch = nd->nd_chan + port;
+               } else {
+                       port = -1;
+                       ch = NULL;
+               }
+
+               /*
+                *  Process by major packet type.
+                */
+
+               switch (n0) {
+
+               /*
+                *  Process 1-byte header data packet.
+                */
+
+               case 0:
+               case 1:
+               case 2:
+               case 3:
+               case 4:
+               case 5:
+               case 6:
+               case 7:
+                       dlen = n0 + 1;
+                       plen = dlen + 1;
+
+                       dbuf = b + 1;
+                       goto data;
+
+               /*
+                *  Process 2-byte header data packet.
+                */
+
+               case 8:
+                       if (remain < 3)
+                               goto done;
+
+                       dlen = b[1];
+                       plen = dlen + 2;
+
+                       dbuf = b + 2;
+                       goto data;
+
+               /*
+                *  Process 3-byte header data packet.
+                */
+
+               case 9:
+                       if (remain < 4)
+                               goto done;
+
+                       dlen = get_unaligned_be16(b + 1);
+                       plen = dlen + 3;
+
+                       dbuf = b + 3;
+
+               /*
+                *  Common packet handling code.
+                */
+
+data:
+                       nd->nd_tx_work = 1;
+
+                       /*
+                        *  Otherwise data should appear only when we are
+                        *  in the CS_READY state.
+                        */
+
+                       if (ch->ch_state < CS_READY) {
+                               error = "Data received before RWIN established";
+                               goto prot_error;
+                       }
+
+                       /*
+                        *  Assure that the data received is within the
+                        *  allowable window.
+                        */
+
+                       n = (ch->ch_s_rwin - ch->ch_s_rin) & 0xffff;
+
+                       if (dlen > n) {
+                               error = "Receive data overrun";
+                               goto prot_error;
+                       }
+
+                       /*
+                        *  If we received 3 or less characters,
+                        *  assume it is a human typing, and set RTIME
+                        *  to 10 milliseconds.
+                        *
+                        *  If we receive 10 or more characters,
+                        *  assume its not a human typing, and set RTIME
+                        *  to 100 milliseconds.
+                        */
+
+                       if (ch->ch_edelay != DGRP_RTIME) {
+                               if (ch->ch_rtime != ch->ch_edelay) {
+                                       ch->ch_rtime = ch->ch_edelay;
+                                       ch->ch_flag |= CH_PARAM;
+                               }
+                       } else if (dlen <= 3) {
+                               if (ch->ch_rtime != 10) {
+                                       ch->ch_rtime = 10;
+                                       ch->ch_flag |= CH_PARAM;
+                               }
+                       } else {
+                               if (ch->ch_rtime != DGRP_RTIME) {
+                                       ch->ch_rtime = DGRP_RTIME;
+                                       ch->ch_flag |= CH_PARAM;
+                               }
+                       }
+
+                       /*
+                        *  If a portion of the packet is outside the
+                        *  buffer, shorten the effective length of the
+                        *  data packet to be the amount of data received.
+                        */
+
+                       if (remain < plen)
+                               dlen -= plen - remain;
+
+                       /*
+                        *  Detect if receive flush is now complete.
+                        */
+
+                       if ((ch->ch_flag & CH_RX_FLUSH) != 0 &&
+                           ((ch->ch_flush_seq - nd->nd_seq_out) & SEQ_MASK) >=
+                           ((nd->nd_seq_in    - nd->nd_seq_out) & SEQ_MASK)) {
+                               ch->ch_flag &= ~CH_RX_FLUSH;
+                       }
+
+                       /*
+                        *  If we are ready to receive, move the data into
+                        *  the receive buffer.
+                        */
+
+                       ch->ch_s_rin = (ch->ch_s_rin + dlen) & 0xffff;
+
+                       if (ch->ch_state == CS_READY &&
+                           (ch->ch_tun.un_open_count != 0) &&
+                           (ch->ch_tun.un_flag & UN_CLOSING) == 0 &&
+                           (ch->ch_cflag & CF_CREAD) != 0 &&
+                           (ch->ch_flag & (CH_BAUD0 | CH_RX_FLUSH)) == 0 &&
+                           (ch->ch_send & RR_RX_FLUSH) == 0) {
+
+                               if (ch->ch_rin + dlen >= RBUF_MAX) {
+                                       n = RBUF_MAX - ch->ch_rin;
+
+                                       memcpy(ch->ch_rbuf + ch->ch_rin, dbuf, n);
+
+                                       ch->ch_rin = 0;
+                                       dbuf += n;
+                                       dlen -= n;
+                               }
+
+                               memcpy(ch->ch_rbuf + ch->ch_rin, dbuf, dlen);
+
+                               ch->ch_rin += dlen;
+
+
+                               /*
+                                *  If we are not in fastcook mode, or
+                                *  if there is a fastcook thread
+                                *  waiting for data, send the data to
+                                *  the line discipline.
+                                */
+
+                               if ((ch->ch_flag & CH_FAST_READ) == 0 ||
+                                   ch->ch_inwait != 0) {
+                                       dgrp_input(ch);
+                               }
+
+                               /*
+                                *  If there is a read thread waiting
+                                *  in select, and we are in fastcook
+                                *  mode, wake him up.
+                                */
+
+                               if (waitqueue_active(&ch->ch_tun.un_tty->read_wait) &&
+                                   (ch->ch_flag & CH_FAST_READ) != 0)
+                                       wake_up_interruptible(&ch->ch_tun.un_tty->read_wait);
+
+                               /*
+                                * Wake any thread waiting in the
+                                * fastcook loop.
+                                */
+
+                               if ((ch->ch_flag & CH_INPUT) != 0) {
+                                       ch->ch_flag &= ~CH_INPUT;
+
+                                       wake_up_interruptible(&ch->ch_flag_wait);
+                               }
+                       }
+
+                       /*
+                        *  Fabricate and insert a data packet header to
+                        *  preceed the remaining data when it comes in.
+                        */
+
+                       if (remain < plen) {
+                               dlen = plen - remain;
+                               b = buf;
+
+                               b[0] = 0x90 + n1;
+                               put_unaligned_be16(dlen, b + 1);
+
+                               remain = 3;
+                               goto done;
+                       }
+                       break;
+
+               /*
+                *  Handle Window Sequence packets.
+                */
+
+               case 10:
+                       plen = 3;
+                       if (remain < plen)
+                               goto done;
+
+                       nd->nd_tx_work = 1;
+
+                       {
+                               ushort tpos   = get_unaligned_be16(b + 1);
+
+                               ushort ack    = (tpos          - ch->ch_s_tpos) & 0xffff;
+                               ushort unack  = (ch->ch_s_tin  - ch->ch_s_tpos) & 0xffff;
+                               ushort notify = (ch->ch_s_treq - ch->ch_s_tpos) & 0xffff;
+
+                               if (ch->ch_state < CS_READY || ack > unack) {
+                                       error = "Improper Window Sequence";
+                                       goto prot_error;
+                               }
+
+                               ch->ch_s_tpos = tpos;
+
+                               if (notify <= ack)
+                                       ch->ch_s_treq = tpos;
+                       }
+                       break;
+
+               /*
+                *  Handle Command response packets.
+                */
+
+               case 11:
+
+                       /*
+                        * RealPort engine fix - 03/11/2004
+                        *
+                        * This check did not used to be here.
+                        *
+                        * We were using b[1] without verifying that the data
+                        * is actually there and valid. On a split packet, it
+                        * might not be yet.
+                        *
+                        * NOTE:  I have never actually seen the failure happen
+                        *        under Linux,  but since I have seen it occur
+                        *        under both Solaris and HP-UX,  the assumption
+                        *        is that it *could* happen here as well...
+                        */
+                       if (remain < 2)
+                               goto done;
+
+
+                       switch (b[1]) {
+
+                       /*
+                        *  Handle Open Response.
+                        */
+
+                       case 11:
+                               plen = 6;
+                               if (remain < plen)
+                                       goto done;
+
+                               nd->nd_tx_work = 1;
+
+                               {
+                                       int req = b[2];
+                                       int resp = b[3];
+                                       port = get_unaligned_be16(b + 4);
+
+                                       if (port >= nd->nd_chan_count) {
+                                               error = "Open channel number out of range";
+                                               goto prot_error;
+                                       }
+
+                                       ch = nd->nd_chan + port;
+
+                                       /*
+                                        *  How we handle an open response depends primarily
+                                        *  on our current channel state.
+                                        */
+
+                                       switch (ch->ch_state) {
+                                       case CS_IDLE:
+
+                                               /*
+                                                *  Handle a delayed open.
+                                                */
+
+                                               if (ch->ch_otype_waiting != 0 &&
+                                                   req == ch->ch_otype_waiting &&
+                                                   resp == 0) {
+                                                       ch->ch_otype = req;
+                                                       ch->ch_otype_waiting = 0;
+                                                       ch->ch_state = CS_SEND_QUERY;
+                                                       break;
+                                               }
+                                               goto open_error;
+
+                                       case CS_WAIT_OPEN:
+
+                                               /*
+                                                *  Handle the open response.
+                                                */
+
+                                               if (req == ch->ch_otype) {
+                                                       switch (resp) {
+
+                                                       /*
+                                                        *  On successful response, open the
+                                                        *  port and proceed normally.
+                                                        */
+
+                                                       case 0:
+                                                               ch->ch_state = CS_SEND_QUERY;
+                                                               break;
+
+                                                       /*
+                                                        *  On a busy response to a persistent open,
+                                                        *  remember that the open is pending.
+                                                        */
+
+                                                       case 1:
+                                                       case 2:
+                                                               if (req != OTYPE_IMMEDIATE) {
+                                                                       ch->ch_otype_waiting = req;
+                                                                       ch->ch_state = CS_IDLE;
+                                                                       break;
+                                                               }
+
+                                                       /*
+                                                        *  Otherwise the server open failed.  If
+                                                        *  the Unix port is open, hang it up.
+                                                        */
+
+                                                       default:
+                                                               if (ch->ch_open_count != 0) {
+                                                                       ch->ch_flag |= CH_HANGUP;
+                                                                       dgrp_carrier(ch);
+                                                                       ch->ch_state = CS_IDLE;
+                                                                       break;
+                                                               }
+
+                                                               ch->ch_open_error = resp;
+                                                               ch->ch_state = CS_IDLE;
+
+                                                               wake_up_interruptible(&ch->ch_flag_wait);
+                                                       }
+                                                       break;
+                                               }
+
+                                               /*
+                                                *  Handle delayed response arrival preceeding
+                                                *  the open response we are waiting for.
+                                                */
+
+                                               if (ch->ch_otype_waiting != 0 &&
+                                                   req == ch->ch_otype_waiting &&
+                                                   resp == 0) {
+                                                       ch->ch_otype = ch->ch_otype_waiting;
+                                                       ch->ch_otype_waiting = 0;
+                                                       ch->ch_state = CS_WAIT_FAIL;
+                                                       break;
+                                               }
+                                               goto open_error;
+
+
+                                       case CS_WAIT_FAIL:
+
+                                               /*
+                                                *  Handle response to immediate open arriving
+                                                *  after a delayed open success.
+                                                */
+
+                                               if (req == OTYPE_IMMEDIATE) {
+                                                       ch->ch_state = CS_SEND_QUERY;
+                                                       break;
+                                               }
+                                               goto open_error;
+
+
+                                       case CS_WAIT_CANCEL:
+                                               /*
+                                                *  Handle delayed open response arriving before
+                                                *  the cancel response.
+                                                */
+
+                                               if (req == ch->ch_otype_waiting &&
+                                                   resp == 0) {
+                                                       ch->ch_otype_waiting = 0;
+                                                       break;
+                                               }
+
+                                               /*
+                                                *  Handle cancel response.
+                                                */
+
+                                               if (req == 4 && resp == 0) {
+                                                       ch->ch_otype_waiting = 0;
+                                                       ch->ch_state = CS_IDLE;
+                                                       break;
+                                               }
+                                               goto open_error;
+
+
+                                       case CS_WAIT_CLOSE:
+                                               /*
+                                                *  Handle a successful response to a port
+                                                *  close.
+                                                */
+
+                                               if (req >= 3) {
+                                                       ch->ch_state = CS_IDLE;
+                                                       break;
+                                               }
+                                               goto open_error;
+
+open_error:
+                                       default:
+                                               {
+                                                       error = "Improper Open Response";
+                                                       goto prot_error;
+                                               }
+                                       }
+                               }
+                               break;
+
+                       /*
+                        *  Handle Synchronize Response.
+                        */
+
+                       case 13:
+                               plen = 3;
+                               if (remain < plen)
+                                       goto done;
+                               {
+                                       int seq = b[2];
+                                       int s;
+
+                                       /*
+                                        * If channel was waiting for this sync response,
+                                        * unset the flag, and wake up anyone waiting
+                                        * on the event.
+                                        */
+                                       if (ch->ch_flag & CH_WAITING_SYNC) {
+                                               ch->ch_flag &= ~(CH_WAITING_SYNC);
+                                               wake_up_interruptible(&ch->ch_flag_wait);
+                                       }
+
+                                       if (((seq - nd->nd_seq_out) & SEQ_MASK) >=
+                                           ((nd->nd_seq_in - nd->nd_seq_out) & SEQ_MASK)) {
+                                               break;
+                                       }
+
+                                       for (s = nd->nd_seq_out;; s = (s + 1) & SEQ_MASK) {
+                                               if (nd->nd_seq_wait[s] != 0) {
+                                                       nd->nd_seq_wait[s] = 0;
+
+                                                       wake_up_interruptible(&nd->nd_seq_wque[s]);
+                                               }
+
+                                               nd->nd_unack -= nd->nd_seq_size[s];
+
+                                               if (s == seq)
+                                                       break;
+                                       }
+
+                                       nd->nd_seq_out = (seq + 1) & SEQ_MASK;
+                               }
+                               break;
+
+                       /*
+                        *  Handle Sequence Response.
+                        */
+
+                       case 15:
+                               plen = 6;
+                               if (remain < plen)
+                                       goto done;
+
+                               {
+                               /* Record that we have received the Sequence
+                                * Response, but we aren't interested in the
+                                * sequence numbers.  We were using RIN like it
+                                * was ROUT and that was causing problems,
+                                * fixed 7-13-2001 David Fries. See comment in
+                                * drp.h for ch_s_rin variable.
+                                       int rin = get_unaligned_be16(b + 2);
+                                       int tpos = get_unaligned_be16(b + 4);
+                               */
+
+                                       ch->ch_send   &= ~RR_SEQUENCE;
+                                       ch->ch_expect &= ~RR_SEQUENCE;
+                               }
+                               goto check_query;
+
+                       /*
+                        *  Handle Status Response.
+                        */
+
+                       case 17:
+                               plen = 5;
+                               if (remain < plen)
+                                       goto done;
+
+                               {
+                                       ch->ch_s_elast = get_unaligned_be16(b + 2);
+                                       ch->ch_s_mlast = b[4];
+
+                                       ch->ch_expect &= ~RR_STATUS;
+                                       ch->ch_send   &= ~RR_STATUS;
+
+                                       /*
+                                        *  CH_PHYS_CD is cleared because something _could_ be
+                                        *  waiting for the initial sense of carrier... and if
+                                        *  carrier is high immediately, we want to be sure to
+                                        *  wake them as soon as possible.
+                                        */
+                                       ch->ch_flag &= ~CH_PHYS_CD;
+
+                                       dgrp_carrier(ch);
+                               }
+                               goto check_query;
+
+                       /*
+                        *  Handle Line Error Response.
+                        */
+
+                       case 19:
+                               plen = 14;
+                               if (remain < plen)
+                                       goto done;
+
+                               break;
+
+                       /*
+                        *  Handle Buffer Response.
+                        */
+
+                       case 21:
+                               plen = 6;
+                               if (remain < plen)
+                                       goto done;
+
+                               {
+                                       ch->ch_s_rsize = get_unaligned_be16(b + 2);
+                                       ch->ch_s_tsize = get_unaligned_be16(b + 4);
+
+                                       ch->ch_send   &= ~RR_BUFFER;
+                                       ch->ch_expect &= ~RR_BUFFER;
+                               }
+                               goto check_query;
+
+                       /*
+                        *  Handle Port Capability Response.
+                        */
+
+                       case 23:
+                               plen = 32;
+                               if (remain < plen)
+                                       goto done;
+
+                               {
+                                       ch->ch_send   &= ~RR_CAPABILITY;
+                                       ch->ch_expect &= ~RR_CAPABILITY;
+                               }
+
+                       /*
+                        *  When all queries are complete, set those parameters
+                        *  derived from the query results, then transition
+                        *  to the READY state.
+                        */
+
+check_query:
+                               if (ch->ch_state == CS_WAIT_QUERY &&
+                                   (ch->ch_expect & (RR_SEQUENCE |
+                                                       RR_STATUS |
+                                                       RR_BUFFER |
+                                                       RR_CAPABILITY)) == 0) {
+                                       ch->ch_tmax  = ch->ch_s_tsize / 4;
+
+                                       if (ch->ch_edelay == DGRP_TTIME)
+                                               ch->ch_ttime = DGRP_TTIME;
+                                       else
+                                               ch->ch_ttime = ch->ch_edelay;
+
+                                       ch->ch_rmax = ch->ch_s_rsize / 4;
+
+                                       if (ch->ch_edelay == DGRP_RTIME)
+                                               ch->ch_rtime = DGRP_RTIME;
+                                       else
+                                               ch->ch_rtime = ch->ch_edelay;
+
+                                       ch->ch_rlow  = 2 * ch->ch_s_rsize / 8;
+                                       ch->ch_rhigh = 6 * ch->ch_s_rsize / 8;
+
+                                       ch->ch_state = CS_READY;
+
+                                       nd->nd_tx_work = 1;
+                                       wake_up_interruptible(&ch->ch_flag_wait);
+
+                               }
+                               break;
+
+                       default:
+                               goto decode_error;
+                       }
+                       break;
+
+               /*
+                *  Handle Events.
+                */
+
+               case 12:
+                       plen = 4;
+                       if (remain < plen)
+                               goto done;
+
+                       mlast = ch->ch_s_mlast;
+                       elast = ch->ch_s_elast;
+
+                       mstat = ch->ch_s_mlast = b[1];
+                       estat = ch->ch_s_elast = get_unaligned_be16(b + 2);
+
+                       /*
+                        *  Handle modem changes.
+                        */
+
+                       if (((mstat ^ mlast) & DM_CD) != 0)
+                               dgrp_carrier(ch);
+
+
+                       /*
+                        *  Handle received break.
+                        */
+
+                       if ((estat & ~elast & EV_RXB) != 0 &&
+                           (ch->ch_tun.un_open_count != 0) &&
+                           I_BRKINT(ch->ch_tun.un_tty) &&
+                           !(I_IGNBRK(ch->ch_tun.un_tty))) {
+
+                               tty_buffer_request_room(ch->ch_tun.un_tty, 1);
+                               tty_insert_flip_char(ch->ch_tun.un_tty, 0, TTY_BREAK);
+                               tty_flip_buffer_push(ch->ch_tun.un_tty);
+
+                       }
+
+                       /*
+                        *  On transmit break complete, if more break traffic
+                        *  is waiting then send it.  Otherwise wake any threads
+                        *  waiting for transmitter empty.
+                        */
+
+                       if ((~estat & elast & EV_TXB) != 0 &&
+                           (ch->ch_expect & RR_TX_BREAK) != 0) {
+
+                               nd->nd_tx_work = 1;
+
+                               ch->ch_expect &= ~RR_TX_BREAK;
+
+                               if (ch->ch_break_time != 0) {
+                                       ch->ch_send |= RR_TX_BREAK;
+                               } else {
+                                       ch->ch_send &= ~RR_TX_BREAK;
+                                       ch->ch_flag &= ~CH_TX_BREAK;
+                                       wake_up_interruptible(&ch->ch_flag_wait);
+                               }
+                       }
+                       break;
+
+               case 13:
+               case 14:
+                       error = "Unrecognized command";
+                       goto prot_error;
+
+               /*
+                *  Decode Special Codes.
+                */
+
+               case 15:
+                       switch (n1) {
+                       /*
+                        *  One byte module select.
+                        */
+
+                       case 0:
+                       case 1:
+                       case 2:
+                       case 3:
+                       case 4:
+                       case 5:
+                       case 6:
+                       case 7:
+                               plen = 1;
+                               nd->nd_rx_module = n1;
+                               break;
+
+                       /*
+                        *  Two byte module select.
+                        */
+
+                       case 8:
+                               plen = 2;
+                               if (remain < plen)
+                                       goto done;
+
+                               nd->nd_rx_module = b[1];
+                               break;
+
+                       /*
+                        *  ID Request packet.
+                        */
+
+                       case 11:
+                               if (remain < 4)
+                                       goto done;
+
+                               plen = get_unaligned_be16(b + 2);
+
+                               if (plen < 12 || plen > 1000) {
+                                       error = "Response Packet length error";
+                                       goto prot_error;
+                               }
+
+                               nd->nd_tx_work = 1;
+
+                               switch (b[1]) {
+                               /*
+                                *  Echo packet.
+                                */
+
+                               case 0:
+                                       nd->nd_send |= NR_ECHO;
+                                       break;
+
+                               /*
+                                *  ID Response packet.
+                                */
+
+                               case 1:
+                                       nd->nd_send |= NR_IDENT;
+                                       break;
+
+                               /*
+                                *  ID Response packet.
+                                */
+
+                               case 32:
+                                       nd->nd_send |= NR_PASSWORD;
+                                       break;
+
+                               }
+                               break;
+
+                       /*
+                        *  Various node-level response packets.
+                        */
+
+                       case 12:
+                               if (remain < 4)
+                                       goto done;
+
+                               plen = get_unaligned_be16(b + 2);
+
+                               if (plen < 4 || plen > 1000) {
+                                       error = "Response Packet length error";
+                                       goto prot_error;
+                               }
+
+                               nd->nd_tx_work = 1;
+
+                               switch (b[1]) {
+                               /*
+                                *  Echo packet.
+                                */
+
+                               case 0:
+                                       nd->nd_expect &= ~NR_ECHO;
+                                       break;
+
+                               /*
+                                *  Product Response Packet.
+                                */
+
+                               case 1:
+                                       {
+                                               int desclen;
+
+                                               nd->nd_hw_ver = (b[8] << 8) | b[9];
+                                               nd->nd_sw_ver = (b[10] << 8) | b[11];
+                                               nd->nd_hw_id = b[6];
+                                               desclen = ((plen - 12) > MAX_DESC_LEN) ? MAX_DESC_LEN :
+                                                       plen - 12;
+                                               strncpy(nd->nd_ps_desc, b + 12, desclen);
+                                               nd->nd_ps_desc[desclen] = 0;
+                                       }
+
+                                       nd->nd_expect &= ~NR_IDENT;
+                                       break;
+
+                               /*
+                                *  Capability Response Packet.
+                                */
+
+                               case 2:
+                                       {
+                                               int nn = get_unaligned_be16(b + 4);
+
+                                               if (nn > CHAN_MAX)
+                                                       nn = CHAN_MAX;
+
+                                               dgrp_chan_count(nd, nn);
+                                       }
+
+                                       nd->nd_expect &= ~NR_CAPABILITY;
+                                       break;
+
+                               /*
+                                *  VPD Response Packet.
+                                */
+
+                               case 15:
+                                       /*
+                                        * NOTE: case 15 is here ONLY because the EtherLite
+                                        * is broken, and sends a response to 24 back as 15.
+                                        * To resolve this, the EtherLite firmware is now
+                                        * fixed to send back 24 correctly, but, for backwards
+                                        * compatibility, we now have reserved 15 for the
+                                        * bad EtherLite response to 24 as well.
+                                        */
+
+                                       /* Fallthru! */
+
+                               case 24:
+
+                                       /*
+                                        * If the product doesn't support VPD,
+                                        * it will send back a null IDRESP,
+                                        * which is a length of 4 bytes.
+                                        */
+                                       if (plen > 4) {
+                                               memcpy(nd->nd_vpd, b + 4, min(plen - 4, (long) VPDSIZE));
+                                               nd->nd_vpd_len = min(plen - 4, (long) VPDSIZE);
+                                       }
+
+                                       nd->nd_expect &= ~NR_VPD;
+                                       break;
+
+                               default:
+                                       goto decode_error;
+                               }
+
+                               if (nd->nd_expect == 0 &&
+                                   nd->nd_state == NS_WAIT_QUERY) {
+                                       nd->nd_state = NS_READY;
+                               }
+                               break;
+
+                       /*
+                        *  Debug packet.
+                        */
+
+                       case 14:
+                               if (remain < 4)
+                                       goto done;
+
+                               plen = get_unaligned_be16(b + 2) + 4;
+
+                               if (plen > 1000) {
+                                       error = "Debug Packet too large";
+                                       goto prot_error;
+                               }
+
+                               if (remain < plen)
+                                       goto done;
+                               break;
+
+                       /*
+                        *  Handle reset packet.
+                        */
+
+                       case 15:
+                               if (remain < 2)
+                                       goto done;
+
+                               plen = 2 + b[1];
+
+                               if (remain < plen)
+                                       goto done;
+
+                               nd->nd_tx_work = 1;
+
+                               n = b[plen];
+                               b[plen] = 0;
+
+                               b[plen] = n;
+
+                               error = "Client Reset Acknowledge";
+                               goto prot_error;
+
+                       default:
+                               goto decode_error;
+                       }
+                       break;
+
+               default:
+                       goto decode_error;
+               }
+
+               b += plen;
+               remain -= plen;
+       }
+
+       /*
+        *  When the buffer is exhausted, copy any data left at the
+        *  top of the buffer back down to the bottom for the next
+        *  read request.
+        */
+
+done:
+       if (remain > 0 && b != buf)
+               memcpy(buf, b, remain);
+
+       nd->nd_remain = remain;
+       return;
+
+/*
+ *  Handle a decode error.
+ */
+
+decode_error:
+       error = "Protocol decode error";
+
+/*
+ *  Handle a general protocol error.
+ */
+
+prot_error:
+       nd->nd_remain = 0;
+       nd->nd_state = NS_SEND_ERROR;
+       nd->nd_error = error;
+}
+
+/*
+ * dgrp_net_write() -- write data to the network device.
+ *
+ * A zero byte write indicates that the connection to the RealPort
+ * device has been broken.
+ *
+ * A non-zero write indicates data from the RealPort device.
+ */
+static ssize_t dgrp_net_write(struct file *file, const char __user *buf,
+                             size_t count, loff_t *ppos)
+{
+       struct nd_struct *nd;
+       ssize_t rtn = 0;
+       long n;
+       long total = 0;
+
+       /*
+        *  Get the node pointer, and quit if it doesn't exist.
+        */
+       nd = (struct nd_struct *)(file->private_data);
+       if (!nd)
+               return -ENXIO;
+
+       /*
+        *  Grab the NET lock.
+        */
+       down(&nd->nd_net_semaphore);
+
+       nd->nd_write_count++;
+
+       /*
+        *  Handle disconnect.
+        */
+
+       if (count == 0) {
+               dgrp_net_idle(nd);
+               /*
+                *  Set the active port count to zero.
+                */
+               dgrp_chan_count(nd, 0);
+               goto unlock;
+       }
+
+       /*
+        *  Loop to process entire receive packet.
+        */
+
+       while (count > 0) {
+               n = UIO_MAX - nd->nd_remain;
+
+               if (n > count)
+                       n = count;
+
+               nd->nd_rx_byte += n + nd->nd_link.lk_header_size;
+
+               rtn = copy_from_user(nd->nd_iobuf + nd->nd_remain,
+                                    (void __user *) buf + total, n);
+               if (rtn) {
+                       rtn = -EFAULT;
+                       goto unlock;
+               }
+
+               *ppos += n;
+
+               total += n;
+
+               count -= n;
+
+               if (nd->nd_mon_buf)
+                       dgrp_monitor_data(nd, RPDUMP_SERVER,
+                                         nd->nd_iobuf + nd->nd_remain, n);
+
+               nd->nd_remain += n;
+
+               dgrp_receive(nd);
+       }
+
+       rtn = total;
+
+unlock:
+       /*
+        *  Release the NET lock.
+        */
+       up(&nd->nd_net_semaphore);
+
+       return rtn;
+}
+
+
+/*
+ * dgrp_net_select()
+ *  Determine whether a device is ready to be read or written to, and
+ *  sleep if not.
+ */
+static unsigned int dgrp_net_select(struct file *file,
+                                   struct poll_table_struct *table)
+{
+       unsigned int retval = 0;
+       struct nd_struct *nd = file->private_data;
+
+       poll_wait(file, &nd->nd_tx_waitq, table);
+
+       if (nd->nd_tx_ready)
+               retval |= POLLIN | POLLRDNORM; /* Conditionally readable */
+
+       retval |= POLLOUT | POLLWRNORM;        /* Always writeable */
+
+       return retval;
+}
+
+/*
+ * dgrp_net_ioctl
+ *
+ * Implement those functions which allow the network daemon to control
+ * the network parameters in the driver.  The ioctls include ones to
+ * get and set the link speed parameters for the PortServer.
+ */
+static long dgrp_net_ioctl(struct file *file, unsigned int cmd,
+                          unsigned long arg)
+{
+       struct nd_struct  *nd;
+       int    rtn = 0;
+       long   size = _IOC_SIZE(cmd);
+       struct link_struct link;
+
+       nd = file->private_data;
+
+       if (_IOC_DIR(cmd) & _IOC_READ)
+               rtn = access_ok(VERIFY_WRITE, (void __user *) arg, size);
+       else if (_IOC_DIR(cmd) & _IOC_WRITE)
+               rtn = access_ok(VERIFY_READ,  (void __user *) arg, size);
+
+       if (!rtn)
+               return rtn;
+
+       switch (cmd) {
+       case DIGI_SETLINK:
+               if (size != sizeof(struct link_struct))
+                       return -EINVAL;
+
+               if (copy_from_user((void *)(&link), (void __user *) arg, size))
+                       return -EFAULT;
+
+               if (link.lk_fast_rate < 9600)
+                       link.lk_fast_rate = 9600;
+
+               if (link.lk_slow_rate < 2400)
+                       link.lk_slow_rate = 2400;
+
+               if (link.lk_fast_rate > 10000000)
+                       link.lk_fast_rate = 10000000;
+
+               if (link.lk_slow_rate > link.lk_fast_rate)
+                       link.lk_slow_rate = link.lk_fast_rate;
+
+               if (link.lk_fast_delay > 2000)
+                       link.lk_fast_delay = 2000;
+
+               if (link.lk_slow_delay > 10000)
+                       link.lk_slow_delay = 10000;
+
+               if (link.lk_fast_delay < 60)
+                       link.lk_fast_delay = 60;
+
+               if (link.lk_slow_delay < link.lk_fast_delay)
+                       link.lk_slow_delay = link.lk_fast_delay;
+
+               if (link.lk_header_size < 2)
+                       link.lk_header_size = 2;
+
+               if (link.lk_header_size > 128)
+                       link.lk_header_size = 128;
+
+               link.lk_fast_rate /= 8 * 1000 / dgrp_poll_tick;
+               link.lk_slow_rate /= 8 * 1000 / dgrp_poll_tick;
+
+               link.lk_fast_delay /= dgrp_poll_tick;
+               link.lk_slow_delay /= dgrp_poll_tick;
+
+               nd->nd_link = link;
+
+               break;
+
+       case DIGI_GETLINK:
+               if (size != sizeof(struct link_struct))
+                       return -EINVAL;
+
+               if (copy_to_user((void __user *)arg, (void *)(&nd->nd_link),
+                                size))
+                       return -EFAULT;
+
+               break;
+
+       default:
+               return -EINVAL;
+
+       }
+
+       return 0;
+}
+
+/**
+ * dgrp_poll_handler() -- handler for poll timer
+ *
+ * As each timer expires, it determines (a) whether the "transmit"
+ * waiter needs to be woken up, and (b) whether the poller needs to
+ * be rescheduled.
+ */
+void dgrp_poll_handler(unsigned long arg)
+{
+       struct dgrp_poll_data *poll_data;
+       struct nd_struct *nd;
+       struct link_struct *lk;
+       ulong time;
+       ulong poll_time;
+       ulong freq;
+       ulong lock_flags;
+
+       poll_data = (struct dgrp_poll_data *) arg;
+       freq = 1000 / poll_data->poll_tick;
+       poll_data->poll_round += 17;
+
+       if (poll_data->poll_round >= freq)
+               poll_data->poll_round -= freq;
+
+       /*
+        * Loop to process all open nodes.
+        *
+        * For each node, determine the rate at which it should
+        * be transmitting data.  Then if the node should wake up
+        * and transmit data now, enable the net receive select
+        * to get the transmit going.
+        */
+
+       list_for_each_entry(nd, &nd_struct_list, list) {
+
+               lk = &nd->nd_link;
+
+               /*
+                * Decrement statistics.  These are only for use with
+                * KME, so don't worry that the operations are done
+                * unlocked, and so the results are occassionally wrong.
+                */
+
+               nd->nd_read_count -= (nd->nd_read_count +
+                                     poll_data->poll_round) / freq;
+               nd->nd_write_count -= (nd->nd_write_count +
+                                      poll_data->poll_round) / freq;
+               nd->nd_send_count -= (nd->nd_send_count +
+                                     poll_data->poll_round) / freq;
+               nd->nd_tx_byte -= (nd->nd_tx_byte +
+                                  poll_data->poll_round) / freq;
+               nd->nd_rx_byte -= (nd->nd_rx_byte +
+                                  poll_data->poll_round) / freq;
+
+               /*
+                * Wake the daemon to transmit data only when there is
+                * enough byte credit to send data.
+                *
+                * The results are approximate because the operations
+                * are performed unlocked, and we are inspecting
+                * data asynchronously updated elsewhere.  The whole
+                * thing is just approximation anyway, so that should
+                * be okay.
+                */
+
+               if (lk->lk_slow_rate >= UIO_MAX) {
+
+                       nd->nd_delay = 0;
+                       nd->nd_rate = UIO_MAX;
+
+                       nd->nd_tx_deposit = nd->nd_tx_charge + 3 * UIO_MAX;
+                       nd->nd_tx_credit  = 3 * UIO_MAX;
+
+               } else {
+
+                       long rate;
+                       long delay;
+                       long deposit;
+                       long charge;
+                       long size;
+                       long excess;
+
+                       long seq_in = nd->nd_seq_in;
+                       long seq_out = nd->nd_seq_out;
+
+                       /*
+                        * If there are no outstanding packets, run at the
+                        * fastest rate.
+                        */
+
+                       if (seq_in == seq_out) {
+                               delay = 0;
+                               rate = lk->lk_fast_rate;
+                       }
+
+                       /*
+                        * Otherwise compute the transmit rate based on the
+                        * delay since the oldest packet.
+                        */
+
+                       else {
+                               /*
+                                * The actual delay is computed as the
+                                * time since the oldest unacknowledged
+                                * packet was sent, minus the time it
+                                * took to send that packet to the server.
+                                */
+
+                               delay = ((jiffies - nd->nd_seq_time[seq_out])
+                                       - (nd->nd_seq_size[seq_out] /
+                                       lk->lk_fast_rate));
+
+                               /*
+                                * If the delay is less than the "fast"
+                                * delay, transmit full speed.  If greater
+                                * than the "slow" delay, transmit at the
+                                * "slow" speed.   In between, interpolate
+                                * between the fast and slow speeds.
+                                */
+
+                               rate =
+                                 (delay <= lk->lk_fast_delay ?
+                                   lk->lk_fast_rate :
+                                   delay >= lk->lk_slow_delay ?
+                                     lk->lk_slow_rate :
+                                     (lk->lk_slow_rate +
+                                      (lk->lk_slow_delay - delay) *
+                                      (lk->lk_fast_rate - lk->lk_slow_rate) /
+                                      (lk->lk_slow_delay - lk->lk_fast_delay)
+                                     )
+                                 );
+                       }
+
+                       nd->nd_delay = delay;
+                       nd->nd_rate = rate;
+
+                       /*
+                        * Increase the transmit credit by depositing the
+                        * current transmit rate.
+                        */
+
+                       deposit = nd->nd_tx_deposit;
+                       charge  = nd->nd_tx_charge;
+
+                       deposit += rate;
+
+                       /*
+                        * If the available transmit credit becomes too large,
+                        * reduce the deposit to correct the value.
+                        *
+                        * Too large is the max of:
+                        *              6 times the header size
+                        *              3 times the current transmit rate.
+                        */
+
+                       size = 2 * nd->nd_link.lk_header_size;
+
+                       if (size < rate)
+                               size = rate;
+
+                       size *= 3;
+
+                       excess = deposit - charge - size;
+
+                       if (excess > 0)
+                               deposit -= excess;
+
+                       nd->nd_tx_deposit = deposit;
+                       nd->nd_tx_credit  = deposit - charge;
+
+                       /*
+                        * Wake the transmit task only if the transmit credit
+                        * is at least 3 times the transmit header size.
+                        */
+
+                       size = 3 * lk->lk_header_size;
+
+                       if (nd->nd_tx_credit < size)
+                               continue;
+               }
+
+
+               /*
+                * Enable the READ select to wake the daemon if there
+                * is useful work for the drp_read routine to perform.
+                */
+
+               if (waitqueue_active(&nd->nd_tx_waitq) &&
+                   (nd->nd_tx_work != 0 ||
+                   (ulong)(jiffies - nd->nd_tx_time) >= IDLE_MAX)) {
+                       nd->nd_tx_ready = 1;
+
+                       wake_up_interruptible(&nd->nd_tx_waitq);
+
+                       /* not needed */
+                       /* nd->nd_flag &= ~ND_SELECT; */
+               }
+       }
+
+
+       /*
+        * Schedule ourself back at the nominal wakeup interval.
+        */
+       spin_lock_irqsave(&poll_data->poll_lock, lock_flags);
+
+       poll_data->node_active_count--;
+       if (poll_data->node_active_count > 0) {
+               poll_data->node_active_count++;
+               poll_time = poll_data->timer.expires +
+                       poll_data->poll_tick * HZ / 1000;
+
+               time = poll_time - jiffies;
+
+               if (time >= 2 * poll_data->poll_tick)
+                       poll_time = jiffies + dgrp_poll_tick * HZ / 1000;
+
+               poll_data->timer.expires = poll_time;
+               add_timer(&poll_data->timer);
+       }
+
+       spin_unlock_irqrestore(&poll_data->poll_lock, lock_flags);
+}
diff --git a/drivers/staging/dgrp/dgrp_ports_ops.c b/drivers/staging/dgrp/dgrp_ports_ops.c
new file mode 100644 (file)
index 0000000..cd1fc20
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+ *
+ * Copyright 1999-2000 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.
+ *
+ * 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.
+ *
+ */
+
+/*
+ *
+ *  Filename:
+ *
+ *     dgrp_ports_ops.c
+ *
+ *  Description:
+ *
+ *     Handle the file operations required for the /proc/dgrp/ports/...
+ *     devices.  Basically gathers tty status for the node and returns it.
+ *
+ *  Author:
+ *
+ *     James A. Puzzo
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/tty.h>
+#include <linux/sched.h>
+#include <linux/seq_file.h>
+
+#include "dgrp_common.h"
+
+/* File operation declarations */
+static int dgrp_ports_open(struct inode *, struct file *);
+
+static const struct file_operations ports_ops = {
+       .owner   = THIS_MODULE,
+       .open    = dgrp_ports_open,
+       .read    = seq_read,
+       .llseek  = seq_lseek,
+       .release = seq_release
+};
+
+static struct inode_operations ports_inode_ops = {
+       .permission = dgrp_inode_permission
+};
+
+
+void dgrp_register_ports_hook(struct proc_dir_entry *de)
+{
+       struct nd_struct *node = de->data;
+
+       de->proc_iops = &ports_inode_ops;
+       de->proc_fops = &ports_ops;
+       node->nd_ports_de = de;
+}
+
+static void *dgrp_ports_seq_start(struct seq_file *seq, loff_t *pos)
+{
+       if (*pos == 0)
+               seq_puts(seq, "#num tty_open pr_open tot_wait MSTAT  IFLAG  OFLAG  CFLAG  BPS    DIGIFLAGS\n");
+
+       return pos;
+}
+
+static void *dgrp_ports_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+       struct nd_struct *nd = seq->private;
+
+       if (*pos >= nd->nd_chan_count)
+               return NULL;
+
+       *pos += 1;
+
+       return pos;
+}
+
+static void dgrp_ports_seq_stop(struct seq_file *seq, void *v)
+{
+}
+
+static int dgrp_ports_seq_show(struct seq_file *seq, void *v)
+{
+       loff_t *pos = v;
+       struct nd_struct *nd;
+       struct ch_struct *ch;
+       struct un_struct *tun, *pun;
+       unsigned int totcnt;
+
+       nd = seq->private;
+       if (!nd)
+               return 0;
+
+       if (*pos >= nd->nd_chan_count)
+               return 0;
+
+       ch = &nd->nd_chan[*pos];
+       tun = &ch->ch_tun;
+       pun = &ch->ch_pun;
+
+       /*
+        * If port is not open and no one is waiting to
+        * open it, the modem signal values can't be
+        * trusted, and will be zeroed.
+        */
+       totcnt = tun->un_open_count +
+               pun->un_open_count +
+               ch->ch_wait_count[0] +
+               ch->ch_wait_count[1] +
+               ch->ch_wait_count[2];
+
+       seq_printf(seq, "%02d      %02d      %02d      %02d     0x%04X 0x%04X 0x%04X 0x%04X %-6d 0x%04X\n",
+                  (int) *pos,
+                  tun->un_open_count,
+                  pun->un_open_count,
+                  ch->ch_wait_count[0] +
+                  ch->ch_wait_count[1] +
+                  ch->ch_wait_count[2],
+                  (totcnt ? ch->ch_s_mlast : 0),
+                  ch->ch_s_iflag,
+                  ch->ch_s_oflag,
+                  ch->ch_s_cflag,
+                  (ch->ch_s_brate ? (1843200 / ch->ch_s_brate) : 0),
+                  ch->ch_digi.digi_flags);
+
+       return 0;
+}
+
+static const struct seq_operations ports_seq_ops = {
+       .start = dgrp_ports_seq_start,
+       .next  = dgrp_ports_seq_next,
+       .stop  = dgrp_ports_seq_stop,
+       .show  = dgrp_ports_seq_show,
+};
+
+/**
+ * dgrp_ports_open -- open the /proc/dgrp/ports/... device
+ * @inode: struct inode *
+ * @file: struct file *
+ *
+ * Open function to open the /proc/dgrp/ports device for a PortServer.
+ * This is the open function for struct file_operations
+ */
+static int dgrp_ports_open(struct inode *inode, struct file *file)
+{
+       struct seq_file *seq;
+       int rtn;
+
+       rtn = seq_open(file, &ports_seq_ops);
+       if (!rtn) {
+               seq = file->private_data;
+               seq->private = PDE(inode)->data;
+       }
+
+       return rtn;
+}
diff --git a/drivers/staging/dgrp/dgrp_specproc.c b/drivers/staging/dgrp/dgrp_specproc.c
new file mode 100644 (file)
index 0000000..259d23a
--- /dev/null
@@ -0,0 +1,821 @@
+/*
+ *
+ * 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 "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;
+}
diff --git a/drivers/staging/dgrp/dgrp_sysfs.c b/drivers/staging/dgrp/dgrp_sysfs.c
new file mode 100644 (file)
index 0000000..e5a3c88
--- /dev/null
@@ -0,0 +1,555 @@
+/*
+ * Copyright 2004 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.
+ *
+ */
+
+#include "dgrp_common.h"
+
+#include <linux/kernel.h>
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/ctype.h>
+#include <linux/string.h>
+#include <linux/serial_reg.h>
+#include <linux/pci.h>
+#include <linux/kdev_t.h>
+
+
+#define PORTSERVER_DIVIDEND 1843200
+#define SERIAL_TYPE_NORMAL      1
+#define SERIAL_TYPE_CALLOUT     2
+#define SERIAL_TYPE_XPRINT      3
+
+
+static struct class *dgrp_class;
+static struct device *dgrp_class_nodes_dev;
+static struct device *dgrp_class_global_settings_dev;
+
+
+static ssize_t dgrp_class_version_show(struct class *class,
+                                      struct class_attribute *attr, char *buf)
+{
+       return snprintf(buf, PAGE_SIZE, "%s\n", DIGI_VERSION);
+}
+static CLASS_ATTR(driver_version, 0400, dgrp_class_version_show, NULL);
+
+
+static ssize_t dgrp_class_register_with_sysfs_show(struct device *c,
+                                                  struct device_attribute *attr,
+                                                  char *buf)
+{
+       return snprintf(buf, PAGE_SIZE, "1\n");
+}
+static DEVICE_ATTR(register_with_sysfs, 0400,
+                  dgrp_class_register_with_sysfs_show, NULL);
+
+
+static ssize_t dgrp_class_rawreadok_show(struct device *c,
+                                        struct device_attribute *attr,
+                                        char *buf)
+{
+       return snprintf(buf, PAGE_SIZE, "%d\n", dgrp_rawreadok);
+}
+static ssize_t dgrp_class_rawreadok_store(struct device *c,
+                                         struct device_attribute *attr,
+                                         const char *buf, size_t count)
+{
+       sscanf(buf, "0x%x\n", &dgrp_rawreadok);
+       return count;
+}
+static DEVICE_ATTR(rawreadok, 0600, dgrp_class_rawreadok_show,
+                  dgrp_class_rawreadok_store);
+
+
+static ssize_t dgrp_class_pollrate_show(struct device *c,
+                                       struct device_attribute *attr,
+                                       char *buf)
+{
+       return snprintf(buf, PAGE_SIZE, "%d\n", dgrp_poll_tick);
+}
+
+static ssize_t dgrp_class_pollrate_store(struct device *c,
+                                        struct device_attribute *attr,
+                                        const char *buf, size_t count)
+{
+       sscanf(buf, "0x%x\n", &dgrp_poll_tick);
+       return count;
+}
+static DEVICE_ATTR(pollrate, 0600, dgrp_class_pollrate_show,
+                  dgrp_class_pollrate_store);
+
+static struct attribute *dgrp_sysfs_global_settings_entries[] = {
+       &dev_attr_pollrate.attr,
+       &dev_attr_rawreadok.attr,
+       &dev_attr_register_with_sysfs.attr,
+       NULL
+};
+
+
+static struct attribute_group dgrp_global_settings_attribute_group = {
+       .name = NULL,
+       .attrs = dgrp_sysfs_global_settings_entries,
+};
+
+
+
+void dgrp_create_class_sysfs_files(void)
+{
+       int ret = 0;
+       int max_majors = 1U << (32 - MINORBITS);
+
+       dgrp_class = class_create(THIS_MODULE, "digi_realport");
+       ret = class_create_file(dgrp_class, &class_attr_driver_version);
+
+       dgrp_class_global_settings_dev = device_create(dgrp_class, NULL,
+               MKDEV(0, max_majors + 1), NULL, "driver_settings");
+
+       ret = sysfs_create_group(&dgrp_class_global_settings_dev->kobj,
+               &dgrp_global_settings_attribute_group);
+       if (ret) {
+               pr_alert("%s: failed to create sysfs global settings device attributes.\n",
+                       __func__);
+               sysfs_remove_group(&dgrp_class_global_settings_dev->kobj,
+                       &dgrp_global_settings_attribute_group);
+               return;
+       }
+
+       dgrp_class_nodes_dev = device_create(dgrp_class, NULL,
+               MKDEV(0, max_majors + 2), NULL, "nodes");
+
+}
+
+
+void dgrp_remove_class_sysfs_files(void)
+{
+       struct nd_struct *nd;
+       int max_majors = 1U << (32 - MINORBITS);
+
+       list_for_each_entry(nd, &nd_struct_list, list)
+               dgrp_remove_node_class_sysfs_files(nd);
+
+       sysfs_remove_group(&dgrp_class_global_settings_dev->kobj,
+               &dgrp_global_settings_attribute_group);
+
+       class_remove_file(dgrp_class, &class_attr_driver_version);
+
+       device_destroy(dgrp_class, MKDEV(0, max_majors + 1));
+       device_destroy(dgrp_class, MKDEV(0, max_majors + 2));
+       class_destroy(dgrp_class);
+}
+
+static ssize_t dgrp_node_state_show(struct device *c,
+                                   struct device_attribute *attr, char *buf)
+{
+       struct nd_struct *nd;
+
+       if (!c)
+               return 0;
+       nd = (struct nd_struct *) dev_get_drvdata(c);
+       if (!nd)
+               return 0;
+
+       return snprintf(buf, PAGE_SIZE, "%s\n", ND_STATE_STR(nd->nd_state));
+}
+
+static DEVICE_ATTR(state, 0600, dgrp_node_state_show, NULL);
+
+static ssize_t dgrp_node_description_show(struct device *c,
+                                         struct device_attribute *attr,
+                                         char *buf)
+{
+       struct nd_struct *nd;
+
+       if (!c)
+               return 0;
+       nd = (struct nd_struct *) dev_get_drvdata(c);
+       if (!nd)
+               return 0;
+
+       if (nd->nd_state == NS_READY && nd->nd_ps_desc)
+               return snprintf(buf, PAGE_SIZE, "%s\n", nd->nd_ps_desc);
+       return 0;
+}
+static DEVICE_ATTR(description_info, 0600, dgrp_node_description_show, NULL);
+
+static ssize_t dgrp_node_hw_version_show(struct device *c,
+                                        struct device_attribute *attr,
+                                        char *buf)
+{
+       struct nd_struct *nd;
+
+       if (!c)
+               return 0;
+       nd = (struct nd_struct *) dev_get_drvdata(c);
+       if (!nd)
+               return 0;
+
+       if (nd->nd_state == NS_READY)
+               return snprintf(buf, PAGE_SIZE, "%d.%d\n",
+                               (nd->nd_hw_ver >> 8) & 0xff,
+                               nd->nd_hw_ver & 0xff);
+
+       return 0;
+}
+static DEVICE_ATTR(hw_version_info, 0600, dgrp_node_hw_version_show, NULL);
+
+static ssize_t dgrp_node_hw_id_show(struct device *c,
+                                   struct device_attribute *attr, char *buf)
+{
+       struct nd_struct *nd;
+
+       if (!c)
+               return 0;
+       nd = (struct nd_struct *) dev_get_drvdata(c);
+       if (!nd)
+               return 0;
+
+
+       if (nd->nd_state == NS_READY)
+               return snprintf(buf, PAGE_SIZE, "%d\n", nd->nd_hw_id);
+       return 0;
+}
+static DEVICE_ATTR(hw_id_info, 0600, dgrp_node_hw_id_show, NULL);
+
+static ssize_t dgrp_node_sw_version_show(struct device *c,
+                                        struct device_attribute *attr,
+                                        char *buf)
+{
+       struct nd_struct *nd;
+
+       if (!c)
+               return 0;
+
+       nd = (struct nd_struct *) dev_get_drvdata(c);
+       if (!nd)
+               return 0;
+
+       if (nd->nd_state == NS_READY)
+               return snprintf(buf, PAGE_SIZE, "%d.%d\n",
+                               (nd->nd_sw_ver >> 8) & 0xff,
+                               nd->nd_sw_ver & 0xff);
+
+       return 0;
+}
+static DEVICE_ATTR(sw_version_info, 0600, dgrp_node_sw_version_show, NULL);
+
+
+static struct attribute *dgrp_sysfs_node_entries[] = {
+       &dev_attr_state.attr,
+       &dev_attr_description_info.attr,
+       &dev_attr_hw_version_info.attr,
+       &dev_attr_hw_id_info.attr,
+       &dev_attr_sw_version_info.attr,
+       NULL
+};
+
+
+static struct attribute_group dgrp_node_attribute_group = {
+       .name = NULL,
+       .attrs = dgrp_sysfs_node_entries,
+};
+
+
+void dgrp_create_node_class_sysfs_files(struct nd_struct *nd)
+{
+       int ret;
+       char name[10];
+
+       if (nd->nd_ID)
+               ID_TO_CHAR(nd->nd_ID, name);
+       else
+               sprintf(name, "node%ld", nd->nd_major);
+
+       nd->nd_class_dev = device_create(dgrp_class, dgrp_class_nodes_dev,
+               MKDEV(0, nd->nd_major), NULL, name);
+
+       ret = sysfs_create_group(&nd->nd_class_dev->kobj,
+                                &dgrp_node_attribute_group);
+
+       if (ret) {
+               pr_alert("%s: failed to create sysfs node device attributes.\n",
+                       __func__);
+               sysfs_remove_group(&nd->nd_class_dev->kobj,
+                                  &dgrp_node_attribute_group);
+               return;
+       }
+
+       dev_set_drvdata(nd->nd_class_dev, nd);
+
+}
+
+
+void dgrp_remove_node_class_sysfs_files(struct nd_struct *nd)
+{
+       if (nd->nd_class_dev) {
+               sysfs_remove_group(&nd->nd_class_dev->kobj,
+                                  &dgrp_node_attribute_group);
+
+               device_destroy(dgrp_class, MKDEV(0, nd->nd_major));
+               nd->nd_class_dev = NULL;
+       }
+}
+
+
+
+static ssize_t dgrp_tty_state_show(struct device *d,
+                                  struct device_attribute *attr, char *buf)
+{
+       struct un_struct *un;
+
+       if (!d)
+               return 0;
+       un = (struct un_struct *) dev_get_drvdata(d);
+       if (!un)
+               return 0;
+
+       return snprintf(buf, PAGE_SIZE, "%s\n",
+                       un->un_open_count ? "Open" : "Closed");
+}
+static DEVICE_ATTR(state_info, 0600, dgrp_tty_state_show, NULL);
+
+static ssize_t dgrp_tty_baud_show(struct device *d,
+                                 struct device_attribute *attr, char *buf)
+{
+       struct ch_struct *ch;
+       struct un_struct *un;
+
+       if (!d)
+               return 0;
+       un = (struct un_struct *) dev_get_drvdata(d);
+       if (!un)
+               return 0;
+       ch = un->un_ch;
+       if (!ch)
+               return 0;
+       return snprintf(buf, PAGE_SIZE, "%d\n",
+               un->un_open_count ? (PORTSERVER_DIVIDEND / ch->ch_s_brate) : 0);
+}
+static DEVICE_ATTR(baud_info, 0400, dgrp_tty_baud_show, NULL);
+
+
+static ssize_t dgrp_tty_msignals_show(struct device *d,
+                                     struct device_attribute *attr, char *buf)
+{
+       struct ch_struct *ch;
+       struct un_struct *un;
+
+       if (!d)
+               return 0;
+       un = (struct un_struct *) dev_get_drvdata(d);
+       if (!un)
+               return 0;
+       ch = un->un_ch;
+       if (!ch)
+               return 0;
+
+       if (ch->ch_open_count) {
+               return snprintf(buf, PAGE_SIZE, "%s %s %s %s %s %s\n",
+                       (ch->ch_s_mlast & DM_RTS) ? "RTS" : "",
+                       (ch->ch_s_mlast & DM_CTS) ? "CTS" : "",
+                       (ch->ch_s_mlast & DM_DTR) ? "DTR" : "",
+                       (ch->ch_s_mlast & DM_DSR) ? "DSR" : "",
+                       (ch->ch_s_mlast & DM_CD) ? "DCD" : "",
+                       (ch->ch_s_mlast & DM_RI)  ? "RI"  : "");
+       }
+       return 0;
+}
+static DEVICE_ATTR(msignals_info, 0400, dgrp_tty_msignals_show, NULL);
+
+
+static ssize_t dgrp_tty_iflag_show(struct device *d,
+                                  struct device_attribute *attr, char *buf)
+{
+       struct ch_struct *ch;
+       struct un_struct *un;
+
+       if (!d)
+               return 0;
+       un = (struct un_struct *) dev_get_drvdata(d);
+       if (!un)
+               return 0;
+       ch = un->un_ch;
+       if (!ch)
+               return 0;
+       return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_s_iflag);
+}
+static DEVICE_ATTR(iflag_info, 0600, dgrp_tty_iflag_show, NULL);
+
+
+static ssize_t dgrp_tty_cflag_show(struct device *d,
+                                  struct device_attribute *attr, char *buf)
+{
+       struct ch_struct *ch;
+       struct un_struct *un;
+
+       if (!d)
+               return 0;
+       un = (struct un_struct *) dev_get_drvdata(d);
+       if (!un)
+               return 0;
+       ch = un->un_ch;
+       if (!ch)
+               return 0;
+       return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_s_cflag);
+}
+static DEVICE_ATTR(cflag_info, 0600, dgrp_tty_cflag_show, NULL);
+
+
+static ssize_t dgrp_tty_oflag_show(struct device *d,
+                                  struct device_attribute *attr, char *buf)
+{
+       struct ch_struct *ch;
+       struct un_struct *un;
+
+       if (!d)
+               return 0;
+       un = (struct un_struct *) dev_get_drvdata(d);
+       if (!un)
+               return 0;
+       ch = un->un_ch;
+       if (!ch)
+               return 0;
+       return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_s_oflag);
+}
+static DEVICE_ATTR(oflag_info, 0600, dgrp_tty_oflag_show, NULL);
+
+
+static ssize_t dgrp_tty_digi_flag_show(struct device *d,
+                                      struct device_attribute *attr, char *buf)
+{
+       struct ch_struct *ch;
+       struct un_struct *un;
+
+       if (!d)
+               return 0;
+       un = (struct un_struct *) dev_get_drvdata(d);
+       if (!un)
+               return 0;
+       ch = un->un_ch;
+       if (!ch)
+               return 0;
+       return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_digi.digi_flags);
+}
+static DEVICE_ATTR(digi_flag_info, 0600, dgrp_tty_digi_flag_show, NULL);
+
+
+static ssize_t dgrp_tty_rxcount_show(struct device *d,
+                                    struct device_attribute *attr, char *buf)
+{
+       struct ch_struct *ch;
+       struct un_struct *un;
+
+       if (!d)
+               return 0;
+       un = (struct un_struct *) dev_get_drvdata(d);
+       if (!un)
+               return 0;
+       ch = un->un_ch;
+       if (!ch)
+               return 0;
+       return snprintf(buf, PAGE_SIZE, "%d\n", ch->ch_rxcount);
+}
+static DEVICE_ATTR(rxcount_info, 0600, dgrp_tty_rxcount_show, NULL);
+
+
+static ssize_t dgrp_tty_txcount_show(struct device *d,
+                                    struct device_attribute *attr, char *buf)
+{
+       struct ch_struct *ch;
+       struct un_struct *un;
+
+       if (!d)
+               return 0;
+       un = (struct un_struct *) dev_get_drvdata(d);
+       if (!un)
+               return 0;
+       ch = un->un_ch;
+       if (!ch)
+               return 0;
+       return snprintf(buf, PAGE_SIZE, "%d\n", ch->ch_txcount);
+}
+static DEVICE_ATTR(txcount_info, 0600, dgrp_tty_txcount_show, NULL);
+
+
+static ssize_t dgrp_tty_name_show(struct device *d,
+                                 struct device_attribute *attr, char *buf)
+{
+       struct nd_struct *nd;
+       struct ch_struct *ch;
+       struct un_struct *un;
+       char name[10];
+
+       if (!d)
+               return 0;
+       un = (struct un_struct *) dev_get_drvdata(d);
+       if (!un)
+               return 0;
+       ch = un->un_ch;
+       if (!ch)
+               return 0;
+       nd = ch->ch_nd;
+       if (!nd)
+               return 0;
+
+       ID_TO_CHAR(nd->nd_ID, name);
+
+       return snprintf(buf, PAGE_SIZE, "%s%s%02d\n",
+               un->un_type == SERIAL_TYPE_XPRINT ? "pr" : "tty",
+               name, ch->ch_portnum);
+}
+static DEVICE_ATTR(custom_name, 0600, dgrp_tty_name_show, NULL);
+
+
+static struct attribute *dgrp_sysfs_tty_entries[] = {
+       &dev_attr_state_info.attr,
+       &dev_attr_baud_info.attr,
+       &dev_attr_msignals_info.attr,
+       &dev_attr_iflag_info.attr,
+       &dev_attr_cflag_info.attr,
+       &dev_attr_oflag_info.attr,
+       &dev_attr_digi_flag_info.attr,
+       &dev_attr_rxcount_info.attr,
+       &dev_attr_txcount_info.attr,
+       &dev_attr_custom_name.attr,
+       NULL
+};
+
+
+static struct attribute_group dgrp_tty_attribute_group = {
+       .name = NULL,
+       .attrs = dgrp_sysfs_tty_entries,
+};
+
+
+void dgrp_create_tty_sysfs(struct un_struct *un, struct device *c)
+{
+       int ret;
+
+       ret = sysfs_create_group(&c->kobj, &dgrp_tty_attribute_group);
+       if (ret) {
+               pr_alert("%s: failed to create sysfs tty device attributes.\n",
+                       __func__);
+               sysfs_remove_group(&c->kobj, &dgrp_tty_attribute_group);
+               return;
+       }
+
+       dev_set_drvdata(c, un);
+
+}
+
+
+void dgrp_remove_tty_sysfs(struct device *c)
+{
+       sysfs_remove_group(&c->kobj, &dgrp_tty_attribute_group);
+}
diff --git a/drivers/staging/dgrp/dgrp_tty.c b/drivers/staging/dgrp/dgrp_tty.c
new file mode 100644 (file)
index 0000000..7d7de87
--- /dev/null
@@ -0,0 +1,3331 @@
+/*
+ *
+ * Copyright 1999 Digi International (www.digi.com)
+ *     Gene Olson    <Gene_Olson at digi dot com>
+ *     James Puzzo   <jamesp at digi dot com>
+ *     Jeff Randall
+ *     Scott Kilau   <scottk 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_tty.c
+ *
+ *  Description:
+ *
+ *     This file implements the tty driver functionality for the
+ *     RealPort driver software.
+ *
+ *  Author:
+ *
+ *     James A. Puzzo
+ *     Ann-Marie Westgate
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/sched.h>
+
+#include "dgrp_common.h"
+
+#ifndef _POSIX_VDISABLE
+#define   _POSIX_VDISABLE ('\0')
+#endif
+
+/*
+ *     forward declarations
+ */
+
+static void drp_param(struct ch_struct *);
+static void dgrp_tty_close(struct tty_struct *, struct file *);
+
+/* ioctl helper functions */
+static int set_modem_info(struct ch_struct *, unsigned int, unsigned int *);
+static int get_modem_info(struct ch_struct *, unsigned int *);
+static void dgrp_set_custom_speed(struct ch_struct *, int);
+static int dgrp_tty_digigetedelay(struct tty_struct *, int *);
+static int dgrp_tty_digisetedelay(struct tty_struct *, int *);
+static int dgrp_send_break(struct ch_struct *, int);
+
+static ushort  tty_to_ch_flags(struct tty_struct *, char);
+static tcflag_t ch_to_tty_flags(unsigned short, char);
+
+static void dgrp_tty_input_start(struct tty_struct *);
+static void dgrp_tty_input_stop(struct tty_struct *);
+
+static void drp_wmove(struct ch_struct *, int, void*, int);
+
+static int dgrp_tty_open(struct tty_struct *, struct file *);
+static void dgrp_tty_close(struct tty_struct *, struct file *);
+static int dgrp_tty_write(struct tty_struct *, const unsigned char *, int);
+static int dgrp_tty_write_room(struct tty_struct *);
+static void dgrp_tty_flush_buffer(struct tty_struct *);
+static int dgrp_tty_chars_in_buffer(struct tty_struct *);
+static int dgrp_tty_ioctl(struct tty_struct *, unsigned int, unsigned long);
+static void dgrp_tty_set_termios(struct tty_struct *, struct ktermios *);
+static void dgrp_tty_stop(struct tty_struct *);
+static void dgrp_tty_start(struct tty_struct *);
+static void dgrp_tty_throttle(struct tty_struct *);
+static void dgrp_tty_unthrottle(struct tty_struct *);
+static void dgrp_tty_hangup(struct tty_struct *);
+static int dgrp_tty_put_char(struct tty_struct *, unsigned char);
+static int dgrp_tty_tiocmget(struct tty_struct *);
+static int dgrp_tty_tiocmset(struct tty_struct *, unsigned int, unsigned int);
+static int dgrp_tty_send_break(struct tty_struct *, int);
+static void dgrp_tty_send_xchar(struct tty_struct *, char);
+
+/*
+ *     tty defines
+ */
+#define        SERIAL_TYPE_NORMAL      1
+#define        SERIAL_TYPE_CALLOUT     2
+#define        SERIAL_TYPE_XPRINT      3
+
+
+/*
+ *     tty globals/statics
+ */
+
+
+#define PORTSERVER_DIVIDEND    1843200
+
+/*
+ *  Default transparent print information.
+ */
+static struct digi_struct digi_init = {
+       .digi_flags   = DIGI_COOK,      /* Flags                        */
+       .digi_maxcps  = 100,            /* Max CPS                      */
+       .digi_maxchar = 50,             /* Max chars in print queue     */
+       .digi_bufsize = 100,            /* Printer buffer size          */
+       .digi_onlen   = 4,              /* size of printer on string    */
+       .digi_offlen  = 4,              /* size of printer off string   */
+       .digi_onstr   = "\033[5i",      /* ANSI printer on string       */
+       .digi_offstr  = "\033[4i",      /* ANSI printer off string      */
+       .digi_term    = "ansi"          /* default terminal type        */
+};
+
+/*
+ *     Define a local default termios struct. All ports will be created
+ *     with this termios initially.
+ *
+ *     This defines a raw port at 9600 baud, 8 data bits, no parity,
+ *     1 stop bit.
+ */
+static struct ktermios DefaultTermios = {
+       .c_iflag = (ICRNL | IXON),
+       .c_oflag = (OPOST | ONLCR),
+       .c_cflag = (B9600 | CS8 | CREAD | HUPCL | CLOCAL),
+       .c_lflag = (ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL
+                   | ECHOKE | IEXTEN),
+       .c_cc    = INIT_C_CC,
+       .c_line  = 0,
+};
+
+/* Define our tty operations struct */
+static const struct tty_operations dgrp_tty_ops = {
+       .open            = dgrp_tty_open,
+       .close           = dgrp_tty_close,
+       .write           = dgrp_tty_write,
+       .write_room      = dgrp_tty_write_room,
+       .flush_buffer    = dgrp_tty_flush_buffer,
+       .chars_in_buffer = dgrp_tty_chars_in_buffer,
+       .flush_chars     = NULL,
+       .ioctl           = dgrp_tty_ioctl,
+       .set_termios     = dgrp_tty_set_termios,
+       .stop            = dgrp_tty_stop,
+       .start           = dgrp_tty_start,
+       .throttle        = dgrp_tty_throttle,
+       .unthrottle      = dgrp_tty_unthrottle,
+       .hangup          = dgrp_tty_hangup,
+       .put_char        = dgrp_tty_put_char,
+       .tiocmget        = dgrp_tty_tiocmget,
+       .tiocmset        = dgrp_tty_tiocmset,
+       .break_ctl       = dgrp_tty_send_break,
+       .send_xchar      = dgrp_tty_send_xchar
+};
+
+
+static int calc_baud_rate(struct un_struct *un)
+{
+       int i;
+       int brate;
+
+       struct baud_rates {
+               unsigned int rate;
+               unsigned int cflag;
+       };
+
+       static struct baud_rates baud_rates[] = {
+               { 921600, B921600 },
+               { 460800, B460800 },
+               { 230400, B230400 },
+               { 115200, B115200 },
+               {  57600, B57600  },
+               {  38400, B38400  },
+               {  19200, B19200  },
+               {   9600, B9600   },
+               {   4800, B4800   },
+               {   2400, B2400   },
+               {   1200, B1200   },
+               {    600, B600    },
+               {    300, B300    },
+               {    200, B200    },
+               {    150, B150    },
+               {    134, B134    },
+               {    110, B110    },
+               {     75, B75     },
+               {     50, B50     },
+               {      0, B9600  }
+       };
+
+       brate = C_BAUD(un->un_tty);
+
+       for (i = 0; baud_rates[i].rate; i++) {
+               if (baud_rates[i].cflag == brate)
+                       break;
+       }
+
+       return baud_rates[i].rate;
+}
+
+static int calc_fastbaud_rate(struct un_struct *un, struct ktermios *uts)
+{
+       int i;
+       int brate;
+
+       ulong bauds[2][16] = {
+               { /* fastbaud*/
+                       0,      57600,   76800, 115200,
+                       131657, 153600, 230400, 460800,
+                       921600, 1200,   1800,   2400,
+                       4800,   9600,   19200,  38400 },
+               { /* fastbaud & CBAUDEX */
+                       0,      57600,  115200, 230400,
+                       460800, 150,    200,    921600,
+                       600,    1200,   1800,   2400,
+                       4800,   9600,   19200,  38400 }
+       };
+
+       brate = C_BAUD(un->un_tty) & 0xff;
+
+       i = (uts->c_cflag & CBAUDEX) ? 1 : 0;
+
+
+       if ((i >= 0) && (i < 2) && (brate >= 0) && (brate < 16))
+               brate = bauds[i][brate];
+       else
+               brate = 0;
+
+       return brate;
+}
+
+/**
+ * drp_param() -- send parameter values to be sent to the node
+ * @ch: channel structure of port to modify
+ *
+ * Interprets the tty and modem changes made by an application
+ * program (by examining the termios structures) and sets up
+ * parameter values to be sent to the node.
+ */
+static void drp_param(struct ch_struct *ch)
+{
+       struct nd_struct *nd;
+       struct un_struct *un;
+       int   brate;
+       int   mflow;
+       int   xflag;
+       int   iflag;
+       struct ktermios *tts, *pts, *uts;
+
+       nd = ch->ch_nd;
+
+       /*
+        *  If the terminal device is open, use it to set up all tty
+        *  modes and functions.  Otherwise use the printer device.
+        */
+
+       if (ch->ch_tun.un_open_count) {
+
+               un = &ch->ch_tun;
+               tts = &ch->ch_tun.un_tty->termios;
+
+               /*
+                *  If both devices are open, copy critical line
+                *  parameters from the tty device to the printer,
+                *  so that if the tty is closed, the printer will
+                *  continue without disruption.
+                */
+
+               if (ch->ch_pun.un_open_count) {
+
+                       pts = &ch->ch_pun.un_tty->termios;
+
+                       pts->c_cflag ^=
+                               (pts->c_cflag ^ tts->c_cflag) &
+                               (CBAUD  | CSIZE | CSTOPB | CREAD | PARENB |
+                                PARODD | HUPCL | CLOCAL);
+
+                       pts->c_iflag ^=
+                               (pts->c_iflag ^ tts->c_iflag) &
+                               (IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK |
+                                ISTRIP | IXON   | IXANY  | IXOFF);
+
+                       pts->c_cc[VSTART] = tts->c_cc[VSTART];
+                       pts->c_cc[VSTOP] = tts->c_cc[VSTOP];
+               }
+       } else if (ch->ch_pun.un_open_count == 0) {
+               pr_warn("%s - ch_pun.un_open_count shouldn't be 0\n",
+                      __func__);
+               return;
+       } else {
+               un = &ch->ch_pun;
+       }
+
+       uts = &un->un_tty->termios;
+
+       /*
+        * Determine if FAST writes can be performed.
+        */
+
+       if ((ch->ch_digi.digi_flags & DIGI_COOK) != 0 &&
+           (ch->ch_tun.un_open_count != 0)  &&
+           !((un->un_tty)->ldisc->ops->flags & LDISC_FLAG_DEFINED) &&
+           !(L_XCASE(un->un_tty))) {
+               ch->ch_flag |= CH_FAST_WRITE;
+       } else {
+               ch->ch_flag &= ~CH_FAST_WRITE;
+       }
+
+       /*
+        *  If FAST writes can be performed, and OPOST is on in the
+        *  terminal device, do OPOST handling in the server.
+        */
+
+       if ((ch->ch_flag & CH_FAST_WRITE) &&
+             O_OPOST(un->un_tty) != 0) {
+               int oflag = tty_to_ch_flags(un->un_tty, 'o');
+
+               /* add to ch_ocook any processing flags set in the termio */
+               ch->ch_ocook |= oflag & (OF_OLCUC |
+                                        OF_ONLCR |
+                                        OF_OCRNL |
+                                        OF_ONLRET |
+                                        OF_TABDLY);
+
+               /*
+                * the hpux driver clears any flags set in ch_ocook
+                * from the termios oflag.  It is STILL reported though
+                * by a TCGETA
+                */
+
+               oflag = ch_to_tty_flags(ch->ch_ocook, 'o');
+               uts->c_oflag &= ~oflag;
+
+       } else {
+               /* clear the ch->ch_ocook flag */
+               int oflag = ch_to_tty_flags(ch->ch_ocook, 'o');
+               uts->c_oflag |= oflag;
+               ch->ch_ocook = 0;
+       }
+
+       ch->ch_oflag = ch->ch_ocook;
+
+
+       ch->ch_flag &= ~CH_FAST_READ;
+
+       /*
+        *  Generate channel flags
+        */
+
+       if (C_BAUD(un->un_tty) == B0) {
+               if (!(ch->ch_flag & CH_BAUD0)) {
+                       /* TODO : the HPUX driver flushes line */
+                       /* TODO : discipline, I assume I don't have to */
+
+                       ch->ch_tout = ch->ch_tin;
+                       ch->ch_rout = ch->ch_rin;
+
+                       ch->ch_break_time = 0;
+
+                       ch->ch_send |= RR_TX_FLUSH | RR_RX_FLUSH;
+
+                       ch->ch_mout &= ~(DM_DTR | DM_RTS);
+
+                       ch->ch_flag |= CH_BAUD0;
+               }
+       } else if (ch->ch_custom_speed) {
+               ch->ch_brate = PORTSERVER_DIVIDEND / ch->ch_custom_speed ;
+
+               if (ch->ch_flag & CH_BAUD0) {
+                       ch->ch_mout |= DM_DTR | DM_RTS;
+
+                       ch->ch_flag &= ~CH_BAUD0;
+               }
+       } else {
+               /*
+                * Baud rate mapping.
+                *
+                * If FASTBAUD isn't on, we can scan the new baud rate list
+                * as required.
+                *
+                * However, if FASTBAUD is on, we must go to the old
+                * baud rate mapping that existed many many moons ago,
+                * for compatibility reasons.
+                */
+
+               if (!(ch->ch_digi.digi_flags & DIGI_FAST))
+                       brate = calc_baud_rate(un);
+               else
+                       brate = calc_fastbaud_rate(un, uts);
+
+               if (brate == 0)
+                       brate = 9600;
+
+               ch->ch_brate = PORTSERVER_DIVIDEND / brate;
+
+               if (ch->ch_flag & CH_BAUD0) {
+                       ch->ch_mout |= DM_DTR | DM_RTS;
+
+                       ch->ch_flag &= ~CH_BAUD0;
+               }
+       }
+
+       /*
+        *  Generate channel cflags from the termio.
+        */
+
+       ch->ch_cflag = tty_to_ch_flags(un->un_tty, 'c');
+
+       /*
+        *  Generate channel iflags from the termio.
+        */
+
+       iflag = (int) tty_to_ch_flags(un->un_tty, 'i');
+
+       if (START_CHAR(un->un_tty) == _POSIX_VDISABLE ||
+           STOP_CHAR(un->un_tty) == _POSIX_VDISABLE) {
+               iflag &= ~(IF_IXON | IF_IXANY | IF_IXOFF);
+       }
+
+       ch->ch_iflag = iflag;
+
+       /*
+        *  Generate flow control characters
+        */
+
+       /*
+        * From the POSIX.1 spec (7.1.2.6): "If {_POSIX_VDISABLE}
+        * is defined for the terminal device file, and the value
+        * of one of the changable special control characters (see
+        * 7.1.1.9) is {_POSIX_VDISABLE}, that function shall be
+        * disabled, that is, no input data shall be recognized as
+        * the disabled special character."
+        *
+        * OK, so we don't ever assign S/DXB XON or XOFF to _POSIX_VDISABLE.
+        */
+
+       if (uts->c_cc[VSTART] != _POSIX_VDISABLE)
+               ch->ch_xon = uts->c_cc[VSTART];
+       if (uts->c_cc[VSTOP] != _POSIX_VDISABLE)
+               ch->ch_xoff = uts->c_cc[VSTOP];
+
+       ch->ch_lnext = (uts->c_cc[VLNEXT] == _POSIX_VDISABLE ? 0 :
+                       uts->c_cc[VLNEXT]);
+
+       /*
+        * Also, if either c_cc[START] or c_cc[STOP] is set to
+        * _POSIX_VDISABLE, we can't really do software flow
+        * control--in either direction--so we turn it off as
+        * far as S/DXB is concerned.  In essence, if you disable
+        * one, you disable the other too.
+        */
+       if ((uts->c_cc[VSTART] == _POSIX_VDISABLE) ||
+           (uts->c_cc[VSTOP] == _POSIX_VDISABLE))
+               ch->ch_iflag &= ~(IF_IXOFF | IF_IXON);
+
+       /*
+        *  Update xflags.
+        */
+
+       xflag = 0;
+
+       if (ch->ch_digi.digi_flags & DIGI_AIXON)
+               xflag = XF_XIXON;
+
+       if ((ch->ch_xxon == _POSIX_VDISABLE) ||
+           (ch->ch_xxoff == _POSIX_VDISABLE))
+               xflag &= ~XF_XIXON;
+
+       ch->ch_xflag = xflag;
+
+
+       /*
+        *  Figure effective DCD value.
+        */
+
+       if (C_CLOCAL(un->un_tty))
+               ch->ch_flag |= CH_CLOCAL;
+       else
+               ch->ch_flag &= ~CH_CLOCAL;
+
+       /*
+        *  Check modem signals
+        */
+
+       dgrp_carrier(ch);
+
+       /*
+        *  Get hardware handshake value.
+        */
+
+       mflow = 0;
+
+       if (C_CRTSCTS(un->un_tty))
+               mflow |= (DM_RTS | DM_CTS);
+
+       if (ch->ch_digi.digi_flags & RTSPACE)
+               mflow |= DM_RTS;
+
+       if (ch->ch_digi.digi_flags & DTRPACE)
+               mflow |= DM_DTR;
+
+       if (ch->ch_digi.digi_flags & CTSPACE)
+               mflow |= DM_CTS;
+
+       if (ch->ch_digi.digi_flags & DSRPACE)
+               mflow |= DM_DSR;
+
+       if (ch->ch_digi.digi_flags & DCDPACE)
+               mflow |= DM_CD;
+
+       if (ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE)
+               mflow |= DM_RTS_TOGGLE;
+
+       ch->ch_mflow = mflow;
+
+       /*
+        *  Send the changes to the server.
+        */
+
+       ch->ch_flag |= CH_PARAM;
+       (ch->ch_nd)->nd_tx_work = 1;
+
+       if (waitqueue_active(&ch->ch_flag_wait))
+               wake_up_interruptible(&ch->ch_flag_wait);
+}
+
+/*
+ * This function is just used as a callback for timeouts
+ * waiting on the ch_sleep flag.
+ */
+static void wake_up_drp_sleep_timer(unsigned long ptr)
+{
+       struct ch_struct *ch = (struct ch_struct *) ptr;
+       if (ch)
+               wake_up(&ch->ch_sleep);
+}
+
+
+/*
+ * Set up our own sleep that can't be cancelled
+ * until our timeout occurs.
+ */
+static void drp_my_sleep(struct ch_struct *ch)
+{
+       struct timer_list drp_wakeup_timer;
+       DECLARE_WAITQUEUE(wait, current);
+
+       /*
+        * First make sure we're ready to receive the wakeup.
+        */
+
+       add_wait_queue(&ch->ch_sleep, &wait);
+       current->state = TASK_UNINTERRUPTIBLE;
+
+       /*
+        * Since we are uninterruptible, set a timer to
+        * unset the uninterruptable state in 1 second.
+        */
+
+       init_timer(&drp_wakeup_timer);
+       drp_wakeup_timer.function = wake_up_drp_sleep_timer;
+       drp_wakeup_timer.data = (unsigned long) ch;
+       drp_wakeup_timer.expires = jiffies + (1 * HZ);
+       add_timer(&drp_wakeup_timer);
+
+       schedule();
+
+       del_timer(&drp_wakeup_timer);
+
+       remove_wait_queue(&ch->ch_sleep, &wait);
+}
+
+/*
+ * dgrp_tty_open()
+ *
+ * returns:
+ *    -EBUSY    - this is a callout device and the normal device is active
+ *              - there is an error in opening the tty
+ *    -ENODEV   - the channel does not exist
+ *    -EAGAIN   - we are in the middle of hanging up or closing
+ *              - IMMEDIATE_OPEN fails
+ *    -ENXIO or -EAGAIN
+ *              - if the port is outside physical range
+ *    -EINTR    - the open is interrupted
+ *
+ */
+static int dgrp_tty_open(struct tty_struct *tty, struct file *file)
+{
+       int    retval = 0;
+       struct nd_struct  *nd;
+       struct ch_struct *ch;
+       struct un_struct  *un;
+       int    port;
+       int    delay_error;
+       int    otype;
+       int    unf;
+       int    wait_carrier;
+       int    category;
+       int    counts_were_incremented = 0;
+       ulong lock_flags;
+       DECLARE_WAITQUEUE(wait, current);
+
+       /*
+        * Do some initial checks to see if the node and port exist
+        */
+
+       nd = nd_struct_get(MAJOR(tty_devnum(tty)));
+       port = PORT_NUM(MINOR(tty_devnum(tty)));
+       category = OPEN_CATEGORY(MINOR(tty_devnum(tty)));
+
+       if (!nd)
+               return -ENODEV;
+
+       if (port >= CHAN_MAX)
+               return -ENODEV;
+
+       /*
+        *  The channel exists.
+        */
+
+       ch = nd->nd_chan + port;
+
+       un = IS_PRINT(MINOR(tty_devnum(tty))) ? &ch->ch_pun : &ch->ch_tun;
+       un->un_tty = tty;
+       tty->driver_data = un;
+
+       /*
+        * If we are in the middle of hanging up,
+        * then return an error
+        */
+       if (tty_hung_up_p(file)) {
+               retval = ((un->un_flag & UN_HUP_NOTIFY) ?
+                          -EAGAIN : -ERESTARTSYS);
+               goto done;
+       }
+
+       /*
+        * If the port is in the middle of closing, then block
+        * until it is done, then try again.
+        */
+       retval = wait_event_interruptible(un->un_close_wait,
+                       ((un->un_flag & UN_CLOSING) == 0));
+
+       if (retval)
+               goto done;
+
+       /*
+        * If the port is in the middle of a reopen after a network disconnect,
+        * wait until it is done, then try again.
+        */
+       retval = wait_event_interruptible(ch->ch_flag_wait,
+                       ((ch->ch_flag & CH_PORT_GONE) == 0));
+
+       if (retval)
+               goto done;
+
+       /*
+        * If this is a callout device, then just make sure the normal
+        * device isn't being used.
+        */
+
+       if (tty->driver->subtype == SERIAL_TYPE_CALLOUT) {
+               if (un->un_flag & UN_NORMAL_ACTIVE) {
+                       retval = -EBUSY;
+                       goto done;
+               } else {
+                       un->un_flag |= UN_CALLOUT_ACTIVE;
+               }
+       }
+
+       /*
+        *  Loop waiting until the open can be successfully completed.
+        */
+
+       spin_lock_irqsave(&nd->nd_lock, lock_flags);
+
+       nd->nd_tx_work = 1;
+
+       for (;;) {
+               wait_carrier = 0;
+
+               /*
+                * Determine the open type from the flags provided.
+                */
+
+               /*
+                * If the port is not enabled, then exit
+                */
+               if (test_bit(TTY_IO_ERROR, &tty->flags)) {
+                       /* there was an error in opening the tty */
+                       if (un->un_flag & UN_CALLOUT_ACTIVE)
+                               retval = -EBUSY;
+                       else
+                               un->un_flag |= UN_NORMAL_ACTIVE;
+                       goto unlock;
+               }
+
+               if (file->f_flags & O_NONBLOCK) {
+
+                       /*
+                        * if the O_NONBLOCK is set, errors on read and write
+                        * must return -EAGAIN immediately and NOT sleep
+                        * on the waitqs.
+                        */
+                       otype = OTYPE_IMMEDIATE;
+                       delay_error = -EAGAIN;
+
+               } else if (!OPEN_WAIT_AVAIL(category) ||
+                         (file->f_flags & O_NDELAY) != 0) {
+                       otype = OTYPE_IMMEDIATE;
+                       delay_error = -EBUSY;
+
+               } else if (!OPEN_WAIT_CARRIER(category) ||
+                         ((ch->ch_digi.digi_flags & DIGI_FORCEDCD) != 0) ||
+                         C_CLOCAL(tty)) {
+                       otype = OTYPE_PERSISTENT;
+                       delay_error = 0;
+
+               } else {
+                       otype = OTYPE_INCOMING;
+                       delay_error = 0;
+               }
+
+               /*
+                * Handle port currently outside physical port range.
+                */
+
+               if (port >= nd->nd_chan_count) {
+                       if (otype == OTYPE_IMMEDIATE) {
+                               retval = (nd->nd_state == NS_READY) ?
+                                               -ENXIO : -EAGAIN;
+                               goto unlock;
+                       }
+               }
+
+               /*
+                *  Handle port not currently open.
+                */
+
+               else if (ch->ch_open_count == 0) {
+                       /*
+                        * Return an error when an Incoming Open
+                        * response indicates the port is busy.
+                        */
+
+                       if (ch->ch_open_error != 0 && otype == ch->ch_otype) {
+                               retval = (ch->ch_open_error <= 2) ?
+                                         delay_error : -ENXIO ;
+                               goto unlock;
+                       }
+
+                       /*
+                        * Fail any new Immediate open if we do not have
+                        * a normal connection to the server.
+                        */
+
+                       if (nd->nd_state != NS_READY &&
+                           otype == OTYPE_IMMEDIATE) {
+                               retval = -EAGAIN;
+                               goto unlock;
+                       }
+
+                       /*
+                        * If a Realport open of the correct type has
+                        * succeeded, complete the open.
+                        */
+
+                       if (ch->ch_state == CS_READY && ch->ch_otype == otype)
+                               break;
+               }
+
+               /*
+                * Handle port already open and active as a device
+                * of same category.
+                */
+
+               else if ((ch->ch_category == category) ||
+                         IS_PRINT(MINOR(tty_devnum(tty)))) {
+                       /*
+                        * Fail if opening the device now would
+                        * violate exclusive use.
+                        */
+                       unf = ch->ch_tun.un_flag | ch->ch_pun.un_flag;
+
+                       if ((file->f_flags & O_EXCL) || (unf & UN_EXCL)) {
+                               retval = -EBUSY;
+                               goto unlock;
+                       }
+
+                       /*
+                        * If the open device is in the hangup state, all
+                        * system calls fail except close().
+                        */
+
+                       /* TODO : check on hangup_p calls */
+
+                       if (ch->ch_flag & CH_HANGUP) {
+                               retval = -ENXIO;
+                               goto unlock;
+                       }
+
+                       /*
+                        * If the port is ready, and carrier is ignored
+                        * or present, then complete the open.
+                        */
+
+                       if (ch->ch_state == CS_READY &&
+                           (otype != OTYPE_INCOMING ||
+                           ch->ch_flag & CH_VIRT_CD))
+                               break;
+
+                       wait_carrier = 1;
+               }
+
+               /*
+                *  Handle port active with a different category device.
+                */
+
+               else {
+                       if (otype == OTYPE_IMMEDIATE) {
+                               retval = delay_error;
+                               goto unlock;
+                       }
+               }
+
+               /*
+                * Wait until conditions change, then take another
+                * try at the open.
+                */
+
+               ch->ch_wait_count[otype]++;
+
+               if (wait_carrier)
+                       ch->ch_wait_carrier++;
+
+               /*
+                * Prepare the task to accept the wakeup, then
+                * release our locks and release control.
+                */
+
+               add_wait_queue(&ch->ch_flag_wait, &wait);
+               current->state = TASK_INTERRUPTIBLE;
+
+               spin_unlock_irqrestore(&nd->nd_lock, lock_flags);
+
+               /*
+                * Give up control, we'll come back if we're
+                * interrupted or are woken up.
+                */
+               schedule();
+               remove_wait_queue(&ch->ch_flag_wait, &wait);
+
+               spin_lock_irqsave(&nd->nd_lock, lock_flags);
+
+               current->state = TASK_RUNNING;
+
+               ch->ch_wait_count[otype]--;
+
+               if (wait_carrier)
+                       ch->ch_wait_carrier--;
+
+               nd->nd_tx_work = 1;
+
+               if (signal_pending(current)) {
+                       retval = -EINTR;
+                       goto unlock;
+               }
+       } /* end for(;;) */
+
+       /*
+        *  The open has succeeded.  No turning back.
+        */
+       counts_were_incremented = 1;
+       un->un_open_count++;
+       ch->ch_open_count++;
+
+       /*
+        * Initialize the channel, if it's not already open.
+        */
+
+       if (ch->ch_open_count == 1) {
+               ch->ch_flag = 0;
+               ch->ch_inwait = 0;
+               ch->ch_category = category;
+               ch->ch_pscan_state = 0;
+
+               /* TODO : find out what PS-1 bug Gene was referring to */
+               /* TODO : in the following comment. */
+
+               ch->ch_send = RR_TX_START | RR_RX_START;  /* PS-1 bug */
+
+               if (C_CLOCAL(tty) ||
+                   ch->ch_s_mlast & DM_CD ||
+                   ch->ch_digi.digi_flags & DIGI_FORCEDCD)
+                       ch->ch_flag |= CH_VIRT_CD;
+               else if (OPEN_FORCES_CARRIER(category))
+                       ch->ch_flag |= CH_VIRT_CD;
+
+       }
+
+       /*
+        *  Initialize the unit, if it is not already open.
+        */
+
+       if (un->un_open_count == 1) {
+               /*
+                *  Since all terminal options are always sticky in Linux,
+                *  we don't need the UN_STICKY flag to be handled specially.
+                */
+               /* clears all the digi flags, leaves serial flags */
+               un->un_flag &= ~UN_DIGI_MASK;
+
+               if (file->f_flags & O_EXCL)
+                       un->un_flag |= UN_EXCL;
+
+               /* TODO : include "session" and "pgrp" */
+
+               /*
+                *  In Linux, all terminal parameters are intended to be sticky.
+                *  as a result, we "remove" the code which once reset the ports
+                *  to sane values.
+                */
+
+               drp_param(ch);
+
+       }
+
+       un->un_flag |= UN_INITIALIZED;
+
+       retval = 0;
+
+unlock:
+
+       spin_unlock_irqrestore(&nd->nd_lock, lock_flags);
+
+done:
+       /*
+        * Linux does a close for every open, even failed ones!
+        */
+       if (!counts_were_incremented) {
+               un->un_open_count++;
+               ch->ch_open_count++;
+       }
+
+       if (retval)
+               dev_err(tty->dev, "tty open bad return (%i)\n", retval);
+
+       return retval;
+}
+
+
+
+
+/*
+ * dgrp_tty_close() -- close function for tty_operations
+ */
+static void dgrp_tty_close(struct tty_struct *tty, struct file *file)
+{
+       struct ch_struct *ch;
+       struct un_struct *un;
+       struct nd_struct *nd;
+       int     tpos;
+       int     port;
+       int     err = 0;
+       int     s = 0;
+       ulong  waketime;
+       ulong  lock_flags;
+       int sent_printer_offstr = 0;
+
+       port = PORT_NUM(MINOR(tty_devnum(tty)));
+
+       un = tty->driver_data;
+
+       if (!un)
+               return;
+
+       ch = un->un_ch;
+
+       if (!ch)
+               return;
+
+       nd = ch->ch_nd;
+
+       if (!nd)
+               return;
+
+       spin_lock_irqsave(&nd->nd_lock, lock_flags);
+
+
+       /* Used to be on channel basis, now we check on a unit basis. */
+       if (un->un_open_count != 1)
+               goto unlock;
+
+       /*
+        * OK, its the last close on the unit
+        */
+       un->un_flag |= UN_CLOSING;
+
+       /*
+        * Notify the discipline to only process XON/XOFF characters.
+        */
+       tty->closing = 1;
+
+       /*
+        * Wait for output to drain only if this is
+        * the last close against the channel
+        */
+
+       if (ch->ch_open_count == 1) {
+               /*
+                * If its the print device, we need to ensure at all costs that
+                * the offstr will fit. If it won't, flush our tbuf.
+                */
+               if (IS_PRINT(MINOR(tty_devnum(tty))) &&
+                   (((ch->ch_tout - ch->ch_tin - 1) & TBUF_MASK) <
+                   ch->ch_digi.digi_offlen))
+                       ch->ch_tin = ch->ch_tout;
+
+               /*
+                * Turn off the printer.  Don't bother checking to see if its
+                * IS_PRINT... Since this is the last close the flag is going
+                * to be cleared, so we MUST make sure the offstr gets inserted
+                * into tbuf.
+                */
+
+               if ((ch->ch_flag & CH_PRON) != 0) {
+                       drp_wmove(ch, 0, ch->ch_digi.digi_offstr,
+                                 ch->ch_digi.digi_offlen);
+                       ch->ch_flag &= ~CH_PRON;
+                       sent_printer_offstr = 1;
+               }
+       }
+
+       /*
+        *  Wait until either the output queue has drained, or we see
+        *  absolutely no progress for 15 seconds.
+        */
+
+       tpos = ch->ch_s_tpos;
+
+       waketime = jiffies + 15 * HZ;
+
+       for (;;) {
+
+               /*
+                *  Make sure the port still exists.
+                */
+
+               if (port >= nd->nd_chan_count) {
+                       err = 1;
+                       break;
+               }
+
+               if (signal_pending(current)) {
+                       err = 1;
+                       break;
+               }
+
+               /*
+                * If the port is idle (not opened on the server), we have
+                * no way of draining/flushing/closing the port on that server.
+                * So break out of loop.
+                */
+               if (ch->ch_state == CS_IDLE)
+                       break;
+
+               nd->nd_tx_work = 1;
+
+               /*
+                *  Exit if the queues for this unit are empty,
+                *  and either the other unit is still open or all
+                *  data has drained.
+                */
+
+               if ((un->un_tty)->ops->chars_in_buffer ?
+                   ((un->un_tty)->ops->chars_in_buffer)(un->un_tty) == 0 : 1) {
+
+                       /*
+                        * We don't need to wait for a buffer to drain
+                        * if the other unit is open.
+                        */
+
+                       if (ch->ch_open_count != un->un_open_count)
+                               break;
+
+                       /*
+                        *  The wait is complete when all queues are
+                        *  drained, and any break in progress is complete.
+                        */
+
+                       if (ch->ch_tin == ch->ch_tout &&
+                           ch->ch_s_tin == ch->ch_s_tpos &&
+                           (ch->ch_send & RR_TX_BREAK) == 0) {
+                               break;
+                       }
+               }
+
+               /*
+                * Flush TX data and exit the wait if NDELAY is set,
+                * or this is not a DIGI printer, and the close timeout
+                * expires.
+                */
+
+               if ((file->f_flags & (O_NDELAY | O_NONBLOCK)) ||
+                   ((long)(jiffies - waketime) >= 0 &&
+                     (ch->ch_digi.digi_flags & DIGI_PRINTER) == 0)) {
+
+                               /*
+                                * If we sent the printer off string, we cannot
+                                * flush our internal buffers, or we might lose
+                                * the offstr.
+                                */
+                               if (!sent_printer_offstr)
+                                       dgrp_tty_flush_buffer(tty);
+
+                               tty_ldisc_flush(tty);
+                               break;
+               }
+
+               /*
+                *  Otherwise take a short nap.
+                */
+
+               ch->ch_flag |= CH_DRAIN;
+
+               spin_unlock_irqrestore(&nd->nd_lock, lock_flags);
+
+               schedule_timeout_interruptible(1);
+               s = signal_pending(current);
+
+               spin_lock_irqsave(&nd->nd_lock, lock_flags);
+
+               if (s) {
+                       /*
+                        * If we had sent the printer off string, we now have
+                        * some problems.
+                        *
+                        * The system won't let us sleep since we got an error
+                        * back from sleep, presumably because the user did
+                        * a ctrl-c...
+                        * But we need to ensure that the offstr gets sent!
+                        * Thus, we have to do something else besides sleeping.
+                        * The plan:
+                        * 1) Make this task uninterruptable.
+                        * 2) Set up a timer to go off in 1 sec.
+                        * 3) Act as tho we just got out of the sleep above.
+                        *
+                        * Thankfully, in the real world, this just
+                        * never happens.
+                        */
+
+                       if (sent_printer_offstr) {
+                               spin_unlock_irqrestore(&nd->nd_lock,
+                                                      lock_flags);
+                               drp_my_sleep(ch);
+                               spin_lock_irqsave(&nd->nd_lock, lock_flags);
+                       } else {
+                               err = 1;
+                               break;
+                       }
+               }
+
+               /*
+                *  Restart the wait if any progress is seen.
+                */
+
+               if (ch->ch_s_tpos != tpos) {
+                       tpos = ch->ch_s_tpos;
+
+                       /* TODO:  this gives us timeout problems with nist ?? */
+                       waketime = jiffies + 15 * HZ;
+               }
+       }
+
+       /*
+        *  Close the line discipline
+        */
+
+       /* this is done in tty_io.c */
+       /* if ((un->un_tty)->ldisc.close)
+        *      ((un->un_tty)->ldisc.close)(un->un_tty);
+        */
+
+       /* don't do this here */
+       /* un->un_flag = 0; */
+
+       /*
+        *  Flush the receive buffer on terminal unit close only.
+        */
+
+       if (!IS_PRINT(MINOR(tty_devnum(tty))))
+               ch->ch_rout = ch->ch_rin;
+
+
+       /*
+        * Don't permit the close to happen until we get any pending
+        * sync request responses.
+        * There could be other ports depending upon the response as well.
+        *
+        * Also, don't permit the close to happen until any parameter
+        * changes have been sent out from the state machine as well.
+        * This is required because of a ditty -a race with -HUPCL
+        * We MUST make sure all channel parameters have been sent to the
+        * Portserver before sending a close.
+        */
+
+       if ((err != 1) && (ch->ch_state != CS_IDLE)) {
+               spin_unlock_irqrestore(&nd->nd_lock, lock_flags);
+               s = wait_event_interruptible(ch->ch_flag_wait,
+                       ((ch->ch_flag & (CH_WAITING_SYNC | CH_PARAM)) == 0));
+               spin_lock_irqsave(&nd->nd_lock, lock_flags);
+       }
+
+       /*
+        * Cleanup the channel if last unit open.
+        */
+
+       if (ch->ch_open_count == 1) {
+               ch->ch_flag = 0;
+               ch->ch_category = 0;
+               ch->ch_send = 0;
+               ch->ch_expect = 0;
+               ch->ch_tout = ch->ch_tin;
+               /* (un->un_tty)->device = 0; */
+
+               if (ch->ch_state == CS_READY)
+                       ch->ch_state = CS_SEND_CLOSE;
+       }
+
+       /*
+        * Send the changes to the server
+        */
+       if (ch->ch_state != CS_IDLE) {
+               ch->ch_flag |= CH_PARAM;
+               wake_up_interruptible(&ch->ch_flag_wait);
+       }
+
+       nd->nd_tx_work = 1;
+       nd->nd_tx_ready = 1;
+
+unlock:
+       tty->closing = 0;
+
+       if (ch->ch_open_count <= 0)
+               dev_info(tty->dev,
+                        "%s - unexpected value for ch->ch_open_count: %i\n",
+                        __func__, ch->ch_open_count);
+       else
+               ch->ch_open_count--;
+
+       if (un->un_open_count <= 0)
+               dev_info(tty->dev,
+                        "%s - unexpected value for un->un_open_count: %i\n",
+                        __func__, un->un_open_count);
+       else
+               un->un_open_count--;
+
+       un->un_flag &= ~(UN_NORMAL_ACTIVE | UN_CALLOUT_ACTIVE | UN_CLOSING);
+       if (waitqueue_active(&un->un_close_wait))
+               wake_up_interruptible(&un->un_close_wait);
+
+       spin_unlock_irqrestore(&nd->nd_lock, lock_flags);
+
+       return;
+
+}
+
+static void drp_wmove(struct ch_struct *ch, int from_user, void *buf, int count)
+{
+       int n;
+       int ret = 0;
+
+       ch->ch_nd->nd_tx_work = 1;
+
+       n = TBUF_MAX - ch->ch_tin;
+
+       if (count >= n) {
+               if (from_user)
+                       ret = copy_from_user(ch->ch_tbuf + ch->ch_tin,
+                                            (void __user *) buf, n);
+               else
+                       memcpy(ch->ch_tbuf + ch->ch_tin, buf, n);
+
+               buf = (char *) buf + n;
+               count -= n;
+               ch->ch_tin = 0;
+       }
+
+       if (from_user)
+               ret = copy_from_user(ch->ch_tbuf + ch->ch_tin,
+                                    (void __user *) buf, count);
+       else
+               memcpy(ch->ch_tbuf + ch->ch_tin, buf, count);
+
+       ch->ch_tin += count;
+}
+
+
+static int dgrp_calculate_txprint_bounds(struct ch_struct *ch, int space,
+                                        int *un_flag)
+{
+       clock_t tt;
+       clock_t mt;
+       unsigned short tmax = 0;
+
+       /*
+        * If the terminal device is busy, reschedule when
+        * the terminal device becomes idle.
+        */
+
+       if (ch->ch_tun.un_open_count != 0 &&
+           ch->ch_tun.un_tty->ops->chars_in_buffer &&
+           ((ch->ch_tun.un_tty->ops->chars_in_buffer)(ch->ch_tun.un_tty) != 0)) {
+               *un_flag = UN_PWAIT;
+               return 0;
+       }
+
+       /*
+        * Assure that whenever there is printer data in the output
+        * buffer, there always remains enough space after it to
+        * turn the printer off.
+        */
+       space -= ch->ch_digi.digi_offlen;
+
+       if (space <= 0) {
+               *un_flag = UN_EMPTY;
+               return 0;
+       }
+
+       /*
+        * We measure printer CPS speed by incrementing
+        * ch_cpstime by (HZ / digi_maxcps) for every
+        * character we output, restricting output so
+        * that ch_cpstime never exceeds lbolt.
+        *
+        * However if output has not been done for some
+        * time, lbolt will grow to very much larger than
+        * ch_cpstime, which would allow essentially
+        * unlimited amounts of output until ch_cpstime
+        * finally caught up.   To avoid this, we adjust
+        * cps_time when necessary so the difference
+        * between lbolt and ch_cpstime never results
+        * in sending more than digi_bufsize characters.
+        *
+        * This nicely models a printer with an internal
+        * buffer of digi_bufsize characters.
+        *
+        * Get the time between lbolt and ch->ch_cpstime;
+        */
+
+       tt = jiffies - ch->ch_cpstime;
+
+       /*
+        * Compute the time required to send digi_bufsize
+        * characters.
+        */
+
+       mt = HZ * ch->ch_digi.digi_bufsize / ch->ch_digi.digi_maxcps;
+
+       /*
+        * Compute the number of characters that can be sent
+        * without violating the time constraint.   If the
+        * direct calculation of this number is bigger than
+        * digi_bufsize, limit the number to digi_bufsize,
+        * and adjust cpstime to match.
+        */
+
+       if ((clock_t)(tt + HZ) > (clock_t)(mt + HZ)) {
+               tmax = ch->ch_digi.digi_bufsize;
+               ch->ch_cpstime = jiffies - mt;
+       } else {
+               tmax = ch->ch_digi.digi_maxcps * tt / HZ;
+       }
+
+       /*
+        * If the time constraint now binds, limit the transmit
+        * count accordingly, and tentatively arrange to be
+        * rescheduled based on time.
+        */
+
+       if (tmax < space) {
+               *un_flag = UN_TIME;
+               space = tmax;
+       }
+
+       /*
+        * Compute the total number of characters we can
+        * output before the total number of characters known
+        * to be in the output queue exceeds digi_maxchar.
+        */
+
+       tmax = (ch->ch_digi.digi_maxchar -
+               ((ch->ch_tin - ch->ch_tout) & TBUF_MASK) -
+               ((ch->ch_s_tin - ch->ch_s_tpos) & 0xffff));
+
+
+       /*
+        * If the digi_maxchar constraint now holds, limit
+        * the transmit count accordingly, and arrange to
+        * be rescheduled when the queue becomes empty.
+        */
+
+       if (space > tmax) {
+               *un_flag = UN_EMPTY;
+               space = tmax;
+       }
+
+       if (space <= 0)
+               *un_flag |= UN_EMPTY;
+
+       return space;
+}
+
+
+static int dgrp_tty_write(struct tty_struct *tty,
+                         const unsigned char *buf,
+                         int count)
+{
+       struct nd_struct *nd;
+       struct un_struct *un;
+       struct ch_struct *ch;
+       int     space;
+       int     n;
+       int     t;
+       int sendcount;
+       int un_flag;
+       ulong lock_flags;
+
+       if (tty == NULL)
+               return 0;
+
+       un = tty->driver_data;
+       if (!un)
+               return 0;
+
+       ch = un->un_ch;
+       if (!ch)
+               return 0;
+
+       nd = ch->ch_nd;
+       if (!nd)
+               return 0;
+
+       /*
+        * Ignore the request if the channel is not ready.
+        */
+       if (ch->ch_state != CS_READY)
+               return 0;
+
+       spin_lock_irqsave(&dgrp_poll_data.poll_lock, lock_flags);
+
+       /*
+        * Ignore the request if output is blocked.
+        */
+       if ((un->un_flag & (UN_EMPTY | UN_LOW | UN_TIME | UN_PWAIT)) != 0) {
+               count = 0;
+               goto out;
+       }
+
+       /*
+        * Also ignore the request if DPA has this port open,
+        * and is flow controlled on reading more data.
+        */
+       if (nd->nd_dpa_debug && nd->nd_dpa_flag & DPA_WAIT_SPACE &&
+               nd->nd_dpa_port == MINOR(tty_devnum(ch->ch_tun.un_tty))) {
+               count = 0;
+               goto out;
+       }
+
+       /*
+        *      Limit amount we will write to the amount of space
+        *      available in the channel buffer.
+        */
+       sendcount = 0;
+
+       space = (ch->ch_tout - ch->ch_tin - 1) & TBUF_MASK;
+
+       /*
+        * Handle the printer device.
+        */
+
+       un_flag = UN_LOW;
+
+       if (IS_PRINT(MINOR(tty_devnum(tty)))) {
+               clock_t tt;
+               clock_t mt;
+               unsigned short tmax = 0;
+
+               /*
+                * If the terminal device is busy, reschedule when
+                * the terminal device becomes idle.
+                */
+
+               if (ch->ch_tun.un_open_count != 0 &&
+                   ((ch->ch_tun.un_tty->ops->chars_in_buffer)(ch->ch_tun.un_tty) != 0)) {
+                       un->un_flag |= UN_PWAIT;
+                       count = 0;
+                       goto out;
+               }
+
+               /*
+                * Assure that whenever there is printer data in the output
+                * buffer, there always remains enough space after it to
+                * turn the printer off.
+                */
+               space -= ch->ch_digi.digi_offlen;
+
+               /*
+                * Output the printer on string.
+                */
+
+               if ((ch->ch_flag & CH_PRON) == 0) {
+                       space -= ch->ch_digi.digi_onlen;
+
+                       if (space < 0) {
+                               un->un_flag |= UN_EMPTY;
+                               (ch->ch_nd)->nd_tx_work = 1;
+                               count = 0;
+                               goto out;
+                       }
+
+                       drp_wmove(ch, 0, ch->ch_digi.digi_onstr,
+                               ch->ch_digi.digi_onlen);
+
+                       ch->ch_flag |= CH_PRON;
+               }
+
+               /*
+                * We measure printer CPS speed by incrementing
+                * ch_cpstime by (HZ / digi_maxcps) for every
+                * character we output, restricting output so
+                * that ch_cpstime never exceeds lbolt.
+                *
+                * However if output has not been done for some
+                * time, lbolt will grow to very much larger than
+                * ch_cpstime, which would allow essentially
+                * unlimited amounts of output until ch_cpstime
+                * finally caught up.   To avoid this, we adjust
+                * cps_time when necessary so the difference
+                * between lbolt and ch_cpstime never results
+                * in sending more than digi_bufsize characters.
+                *
+                * This nicely models a printer with an internal
+                * buffer of digi_bufsize characters.
+                *
+                * Get the time between lbolt and ch->ch_cpstime;
+                */
+
+               tt = jiffies - ch->ch_cpstime;
+
+               /*
+                * Compute the time required to send digi_bufsize
+                * characters.
+                */
+
+               mt = HZ * ch->ch_digi.digi_bufsize / ch->ch_digi.digi_maxcps;
+
+               /*
+                * Compute the number of characters that can be sent
+                * without violating the time constraint.   If the
+                * direct calculation of this number is bigger than
+                * digi_bufsize, limit the number to digi_bufsize,
+                * and adjust cpstime to match.
+                */
+
+               if ((clock_t)(tt + HZ) > (clock_t)(mt + HZ)) {
+                       tmax = ch->ch_digi.digi_bufsize;
+                       ch->ch_cpstime = jiffies - mt;
+               } else {
+                       tmax = ch->ch_digi.digi_maxcps * tt / HZ;
+               }
+
+               /*
+                * If the time constraint now binds, limit the transmit
+                * count accordingly, and tentatively arrange to be
+                * rescheduled based on time.
+                */
+
+               if (tmax < space) {
+                       space = tmax;
+                       un_flag = UN_TIME;
+               }
+
+               /*
+                * Compute the total number of characters we can
+                * output before the total number of characters known
+                * to be in the output queue exceeds digi_maxchar.
+                */
+
+               tmax = (ch->ch_digi.digi_maxchar -
+                       ((ch->ch_tin - ch->ch_tout) & TBUF_MASK) -
+                       ((ch->ch_s_tin - ch->ch_s_tpos) & 0xffff));
+
+
+               /*
+                * If the digi_maxchar constraint now holds, limit
+                * the transmit count accordingly, and arrange to
+                * be rescheduled when the queue becomes empty.
+                */
+
+               if (space > tmax) {
+                       space = tmax;
+                       un_flag = UN_EMPTY;
+               }
+
+       }
+       /*
+        * Handle the terminal device.
+        */
+       else {
+
+               /*
+                * If the printer device is on, turn it off.
+                */
+
+               if ((ch->ch_flag & CH_PRON) != 0) {
+
+                       space -= ch->ch_digi.digi_offlen;
+
+                       drp_wmove(ch, 0, ch->ch_digi.digi_offstr,
+                                 ch->ch_digi.digi_offlen);
+
+                       ch->ch_flag &= ~CH_PRON;
+               }
+       }
+
+       /*
+        *      If space is 0 and its because the ch->tbuf
+        *      is full, then Linux will handle a callback when queue
+        *      space becomes available.
+        *      tty_write returns count = 0
+        */
+
+       if (space <= 0) {
+               /* the linux tty_io.c handles this if we return 0 */
+               /* if (fp->flags & O_NONBLOCK) return -EAGAIN; */
+
+               un->un_flag |= UN_EMPTY;
+               (ch->ch_nd)->nd_tx_work = 1;
+               count = 0;
+               goto out;
+       }
+
+       count = min(count, space);
+
+       if (count > 0) {
+
+               un->un_tbusy++;
+
+               /*
+                *      Copy the buffer contents to the ch_tbuf
+                *      being careful to wrap around the circular queue
+                */
+
+               t = TBUF_MAX - ch->ch_tin;
+               n = count;
+
+               if (n >= t) {
+                       memcpy(ch->ch_tbuf + ch->ch_tin, buf, t);
+                       if (nd->nd_dpa_debug && nd->nd_dpa_port == PORT_NUM(MINOR(tty_devnum(un->un_tty))))
+                               dgrp_dpa_data(nd, 0, (char *) buf, t);
+                       buf += t;
+                       n -= t;
+                       ch->ch_tin = 0;
+                       sendcount += n;
+               }
+
+               memcpy(ch->ch_tbuf + ch->ch_tin, buf, n);
+               if (nd->nd_dpa_debug && nd->nd_dpa_port == PORT_NUM(MINOR(tty_devnum(un->un_tty))))
+                       dgrp_dpa_data(nd, 0, (char *) buf, n);
+               buf += n;
+               ch->ch_tin += n;
+               sendcount += n;
+
+               un->un_tbusy--;
+               (ch->ch_nd)->nd_tx_work = 1;
+               if (ch->ch_edelay != DGRP_RTIME) {
+                       (ch->ch_nd)->nd_tx_ready = 1;
+                       wake_up_interruptible(&nd->nd_tx_waitq);
+               }
+       }
+
+       ch->ch_txcount += count;
+
+       if (IS_PRINT(MINOR(tty_devnum(tty)))) {
+
+               /*
+                * Adjust ch_cpstime to account
+                * for the characters just output.
+                */
+
+               if (sendcount > 0) {
+                       int cc = HZ * sendcount + ch->ch_cpsrem;
+
+                       ch->ch_cpstime += cc / ch->ch_digi.digi_maxcps;
+                       ch->ch_cpsrem   = cc % ch->ch_digi.digi_maxcps;
+               }
+
+               /*
+                * If we are now waiting on time, schedule ourself
+                * back when we'll be able to send a block of
+                * digi_maxchar characters.
+                */
+
+               if ((un_flag & UN_TIME) != 0) {
+                       ch->ch_waketime = (ch->ch_cpstime +
+                               (ch->ch_digi.digi_maxchar * HZ /
+                               ch->ch_digi.digi_maxcps));
+               }
+       }
+
+       /*
+        * If the printer unit is waiting for completion
+        * of terminal output, get him going again.
+        */
+
+       if ((ch->ch_pun.un_flag & UN_PWAIT) != 0)
+               (ch->ch_nd)->nd_tx_work = 1;
+
+out:
+       spin_unlock_irqrestore(&dgrp_poll_data.poll_lock, lock_flags);
+
+       return count;
+}
+
+
+/*
+ *     Put a character into ch->ch_buf
+ *
+ *     - used by the line discipline for OPOST processing
+ */
+
+static int dgrp_tty_put_char(struct tty_struct *tty, unsigned char new_char)
+{
+       struct un_struct *un;
+       struct ch_struct *ch;
+       ulong  lock_flags;
+       int space;
+       int retval = 0;
+
+       if (tty == NULL)
+               return 0;
+
+       un = tty->driver_data;
+       if (!un)
+               return 0;
+
+       ch = un->un_ch;
+       if (!ch)
+               return 0;
+
+       if (ch->ch_state != CS_READY)
+               return 0;
+
+       spin_lock_irqsave(&dgrp_poll_data.poll_lock, lock_flags);
+
+
+       /*
+        *      If space is 0 and its because the ch->tbuf
+        *      Warn and dump the character, there isn't anything else
+        *      we can do about it.  David_Fries@digi.com
+        */
+
+       space = (ch->ch_tout - ch->ch_tin - 1) & TBUF_MASK;
+
+       un->un_tbusy++;
+
+       /*
+        * Output the printer on string if device is TXPrint.
+        */
+       if (IS_PRINT(MINOR(tty_devnum(tty))) && (ch->ch_flag & CH_PRON) == 0) {
+               if (space < ch->ch_digi.digi_onlen) {
+                       un->un_tbusy--;
+                       goto out;
+               }
+               space -= ch->ch_digi.digi_onlen;
+               drp_wmove(ch, 0, ch->ch_digi.digi_onstr,
+                         ch->ch_digi.digi_onlen);
+               ch->ch_flag |= CH_PRON;
+       }
+
+       /*
+        * Output the printer off string if device is NOT TXPrint.
+        */
+
+       if (!IS_PRINT(MINOR(tty_devnum(tty))) &&
+           ((ch->ch_flag & CH_PRON) != 0)) {
+               if (space < ch->ch_digi.digi_offlen) {
+                       un->un_tbusy--;
+                       goto out;
+               }
+
+               space -= ch->ch_digi.digi_offlen;
+               drp_wmove(ch, 0, ch->ch_digi.digi_offstr,
+                         ch->ch_digi.digi_offlen);
+               ch->ch_flag &= ~CH_PRON;
+       }
+
+       if (!space) {
+               un->un_tbusy--;
+               goto out;
+       }
+
+       /*
+        *      Copy the character to the ch_tbuf being
+        *      careful to wrap around the circular queue
+        */
+       ch->ch_tbuf[ch->ch_tin] = new_char;
+       ch->ch_tin = (1 + ch->ch_tin) & TBUF_MASK;
+
+       if (IS_PRINT(MINOR(tty_devnum(tty)))) {
+
+               /*
+                * Adjust ch_cpstime to account
+                * for the character just output.
+                */
+
+               int cc = HZ + ch->ch_cpsrem;
+
+               ch->ch_cpstime += cc / ch->ch_digi.digi_maxcps;
+               ch->ch_cpsrem   = cc % ch->ch_digi.digi_maxcps;
+
+               /*
+                * If we are now waiting on time, schedule ourself
+                * back when we'll be able to send a block of
+                * digi_maxchar characters.
+                */
+
+               ch->ch_waketime = (ch->ch_cpstime +
+                       (ch->ch_digi.digi_maxchar * HZ /
+                       ch->ch_digi.digi_maxcps));
+       }
+
+
+       un->un_tbusy--;
+       (ch->ch_nd)->nd_tx_work = 1;
+
+       retval = 1;
+out:
+       spin_unlock_irqrestore(&dgrp_poll_data.poll_lock, lock_flags);
+       return retval;
+}
+
+
+
+/*
+ *     Flush TX buffer (make in == out)
+ *
+ *     check tty_ioctl.c  -- this is called after TCOFLUSH
+ */
+static void dgrp_tty_flush_buffer(struct tty_struct *tty)
+{
+       struct un_struct *un;
+       struct ch_struct *ch;
+
+       if (!tty)
+               return;
+       un = tty->driver_data;
+       if (!un)
+               return;
+
+       ch = un->un_ch;
+       if (!ch)
+               return;
+
+       ch->ch_tout = ch->ch_tin;
+       /* do NOT do this here! */
+       /* ch->ch_s_tpos = ch->ch_s_tin = 0; */
+
+       /* send the flush output command now */
+       ch->ch_send |= RR_TX_FLUSH;
+       (ch->ch_nd)->nd_tx_ready = 1;
+       (ch->ch_nd)->nd_tx_work = 1;
+       wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+
+       if (waitqueue_active(&tty->write_wait))
+               wake_up_interruptible(&tty->write_wait);
+
+       tty_wakeup(tty);
+
+}
+
+/*
+ *     Return space available in Tx buffer
+ *     count = ( ch->ch_tout - ch->ch_tin ) mod (TBUF_MAX - 1)
+ */
+static int dgrp_tty_write_room(struct tty_struct *tty)
+{
+       struct un_struct *un;
+       struct ch_struct *ch;
+       int     count;
+
+       if (!tty)
+               return 0;
+
+       un = tty->driver_data;
+       if (!un)
+               return 0;
+
+       ch = un->un_ch;
+       if (!ch)
+               return 0;
+
+       count = (ch->ch_tout - ch->ch_tin - 1) & TBUF_MASK;
+
+       /* We *MUST* check this, and return 0 if the Printer Unit cannot
+        * take any more data within its time constraints...  If we don't
+        * return 0 and the printer has hit it time constraint, the ld will
+        * call us back doing a put_char, which cannot be rejected!!!
+        */
+       if (IS_PRINT(MINOR(tty_devnum(tty)))) {
+               int un_flag = 0;
+               count = dgrp_calculate_txprint_bounds(ch, count, &un_flag);
+               if (count <= 0)
+                       count = 0;
+
+               ch->ch_pun.un_flag |= un_flag;
+               (ch->ch_nd)->nd_tx_work = 1;
+       }
+
+       return count;
+}
+
+/*
+ *     Return number of characters that have not been transmitted yet.
+ *     chars_in_buffer = ( ch->ch_tin - ch->ch_tout ) mod (TBUF_MAX - 1)
+ *                     + ( ch->ch_s_tin - ch->ch_s_tout ) mod (0xffff)
+ *                     = number of characters "in transit"
+ *
+ * Remember that sequence number math is always with a sixteen bit
+ * mask, not the TBUF_MASK.
+ */
+
+static int dgrp_tty_chars_in_buffer(struct tty_struct *tty)
+{
+       struct un_struct *un;
+       struct ch_struct *ch;
+       int     count;
+       int     count1;
+
+       if (!tty)
+               return 0;
+
+       un = tty->driver_data;
+       if (!un)
+               return 0;
+
+       ch = un->un_ch;
+       if (!ch)
+               return 0;
+
+       count1 = count = (ch->ch_tin - ch->ch_tout) & TBUF_MASK;
+       count += (ch->ch_s_tin - ch->ch_s_tpos) & 0xffff;
+       /* one for tbuf, one for the PS */
+
+       /*
+        * If we are busy transmitting add 1
+        */
+       count += un->un_tbusy;
+
+       return count;
+}
+
+
+/*****************************************************************************
+ *
+ * Helper applications for dgrp_tty_ioctl()
+ *
+ *****************************************************************************
+ */
+
+
+/**
+ * ch_to_tty_flags() -- convert channel flags to termio flags
+ * @ch_flag: Digi channel flags
+ * @flagtype: type of ch_flag (iflag, oflag or cflag)
+ *
+ * take the channel flags of the specified type and return the
+ * corresponding termio flag
+ */
+static tcflag_t ch_to_tty_flags(ushort ch_flag, char flagtype)
+{
+       tcflag_t retval = 0;
+
+       switch (flagtype) {
+       case 'i':
+               retval = ((ch_flag & IF_IGNBRK) ? IGNBRK : 0)
+                    | ((ch_flag & IF_BRKINT) ? BRKINT : 0)
+                    | ((ch_flag & IF_IGNPAR) ? IGNPAR : 0)
+                    | ((ch_flag & IF_PARMRK) ? PARMRK : 0)
+                    | ((ch_flag & IF_INPCK) ? INPCK  : 0)
+                    | ((ch_flag & IF_ISTRIP) ? ISTRIP : 0)
+                    | ((ch_flag & IF_IXON) ? IXON   : 0)
+                    | ((ch_flag & IF_IXANY) ? IXANY  : 0)
+                    | ((ch_flag & IF_IXOFF) ? IXOFF  : 0);
+               break;
+
+       case 'o':
+               retval = ((ch_flag & OF_OLCUC) ? OLCUC : 0)
+                    | ((ch_flag & OF_ONLCR) ? ONLCR  : 0)
+                    | ((ch_flag & OF_OCRNL) ? OCRNL  : 0)
+                    | ((ch_flag & OF_ONOCR) ? ONOCR  : 0)
+                    | ((ch_flag & OF_ONLRET) ? ONLRET : 0)
+                 /* | ((ch_flag & OF_OTAB3) ? OFILL  : 0) */
+                    | ((ch_flag & OF_TABDLY) ? TABDLY : 0);
+               break;
+
+       case 'c':
+               retval = ((ch_flag & CF_CSTOPB) ? CSTOPB : 0)
+                    | ((ch_flag & CF_CREAD) ? CREAD  : 0)
+                    | ((ch_flag & CF_PARENB) ? PARENB : 0)
+                    | ((ch_flag & CF_PARODD) ? PARODD : 0)
+                    | ((ch_flag & CF_HUPCL) ? HUPCL  : 0);
+
+               switch (ch_flag & CF_CSIZE) {
+               case CF_CS5:
+                       retval |= CS5;
+                       break;
+               case CF_CS6:
+                       retval |= CS6;
+                       break;
+               case CF_CS7:
+                       retval |= CS7;
+                       break;
+               case CF_CS8:
+                       retval |= CS8;
+                       break;
+               default:
+                       retval |= CS8;
+                       break;
+               }
+               break;
+       case 'x':
+               break;
+       case 'l':
+               break;
+       default:
+               return 0;
+       }
+
+       return retval;
+}
+
+
+/**
+ * tty_to_ch_flags() -- convert termio flags to digi channel flags
+ * @tty: pointer to a TTY structure holding flag to be converted
+ * @flagtype: identifies which flag (iflags, oflags, or cflags) should
+ *                 be converted
+ *
+ * take the termio flag of the specified type and return the
+ * corresponding Digi version of the flags
+ */
+static ushort tty_to_ch_flags(struct tty_struct *tty, char flagtype)
+{
+       ushort retval = 0;
+       tcflag_t tflag = 0;
+
+       switch (flagtype) {
+       case 'i':
+               tflag  = tty->termios.c_iflag;
+               retval = (I_IGNBRK(tty) ? IF_IGNBRK : 0)
+                     | (I_BRKINT(tty) ? IF_BRKINT : 0)
+                     | (I_IGNPAR(tty) ? IF_IGNPAR : 0)
+                     | (I_PARMRK(tty) ? IF_PARMRK : 0)
+                     | (I_INPCK(tty)  ? IF_INPCK  : 0)
+                     | (I_ISTRIP(tty) ? IF_ISTRIP : 0)
+                     | (I_IXON(tty)   ? IF_IXON   : 0)
+                     | (I_IXANY(tty)  ? IF_IXANY  : 0)
+                     | (I_IXOFF(tty)  ? IF_IXOFF  : 0);
+               break;
+       case 'o':
+               tflag  = tty->termios.c_oflag;
+               /*
+                * If OPOST is set, then do the post processing in the
+                * firmware by setting all the processing flags on.
+                * If ~OPOST, then make sure we are not doing any
+                * output processing!!
+                */
+               if (!O_OPOST(tty))
+                       retval = 0;
+               else
+                       retval = (O_OLCUC(tty) ? OF_OLCUC : 0)
+                            | (O_ONLCR(tty)  ? OF_ONLCR  : 0)
+                            | (O_OCRNL(tty)  ? OF_OCRNL  : 0)
+                            | (O_ONOCR(tty)  ? OF_ONOCR  : 0)
+                            | (O_ONLRET(tty) ? OF_ONLRET : 0)
+                         /* | (O_OFILL(tty)  ? OF_TAB3   : 0) */
+                            | (O_TABDLY(tty) ? OF_TABDLY : 0);
+               break;
+       case 'c':
+               tflag  = tty->termios.c_cflag;
+               retval = (C_CSTOPB(tty) ? CF_CSTOPB : 0)
+                    | (C_CREAD(tty)  ? CF_CREAD  : 0)
+                    | (C_PARENB(tty) ? CF_PARENB : 0)
+                    | (C_PARODD(tty) ? CF_PARODD : 0)
+                    | (C_HUPCL(tty)  ? CF_HUPCL  : 0);
+               switch (C_CSIZE(tty)) {
+               case CS8:
+                       retval |= CF_CS8;
+                       break;
+               case CS7:
+                       retval |= CF_CS7;
+                       break;
+               case CS6:
+                       retval |= CF_CS6;
+                       break;
+               case CS5:
+                       retval |= CF_CS5;
+                       break;
+               default:
+                       retval |= CF_CS8;
+                       break;
+               }
+               break;
+       case 'x':
+               break;
+       case 'l':
+               break;
+       default:
+               return 0;
+       }
+
+       return retval;
+}
+
+
+static int dgrp_tty_send_break(struct tty_struct *tty, int msec)
+{
+       struct un_struct *un;
+       struct ch_struct *ch;
+       int ret = -EIO;
+
+       if (!tty)
+               return ret;
+
+       un = tty->driver_data;
+       if (!un)
+               return ret;
+
+       ch = un->un_ch;
+       if (!ch)
+               return ret;
+
+       dgrp_send_break(ch, msec);
+       return 0;
+}
+
+
+/*
+ * This routine sends a break character out the serial port.
+ *
+ * duration is in 1/1000's of a second
+ */
+static int dgrp_send_break(struct ch_struct *ch, int msec)
+{
+       ulong x;
+
+       wait_event_interruptible(ch->ch_flag_wait,
+               ((ch->ch_flag & CH_TX_BREAK) == 0));
+       ch->ch_break_time += max(msec, 250);
+       ch->ch_send |= RR_TX_BREAK;
+       ch->ch_flag |= CH_TX_BREAK;
+       (ch->ch_nd)->nd_tx_work = 1;
+
+       x = (msec * HZ) / 1000;
+       wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+
+       return 0;
+}
+
+
+/*
+ * Return modem signals to ld.
+ */
+static int dgrp_tty_tiocmget(struct tty_struct *tty)
+{
+       unsigned int mlast;
+       struct un_struct *un = tty->driver_data;
+       struct ch_struct *ch;
+
+       if (!un)
+               return -ENODEV;
+
+       ch = un->un_ch;
+       if (!ch)
+               return -ENODEV;
+
+       mlast = ((ch->ch_s_mlast & ~(DM_RTS | DM_DTR)) |
+               (ch->ch_mout & (DM_RTS | DM_DTR)));
+
+       /* defined in /usr/include/asm/termios.h */
+       mlast =   ((mlast & DM_RTS) ? TIOCM_RTS : 0)
+               | ((mlast & DM_DTR) ? TIOCM_DTR : 0)
+               | ((mlast & DM_CD)  ? TIOCM_CAR : 0)
+               | ((mlast & DM_RI)  ? TIOCM_RNG : 0)
+               | ((mlast & DM_DSR) ? TIOCM_DSR : 0)
+               | ((mlast & DM_CTS) ? TIOCM_CTS : 0);
+
+       return mlast;
+}
+
+
+/*
+ *      Set modem lines
+ */
+static int dgrp_tty_tiocmset(struct tty_struct *tty,
+                            unsigned int set, unsigned int clear)
+{
+       ulong lock_flags;
+       struct un_struct *un = tty->driver_data;
+       struct ch_struct *ch;
+
+       if (!un)
+               return -ENODEV;
+
+       ch = un->un_ch;
+       if (!ch)
+               return -ENODEV;
+
+       if (set & TIOCM_RTS)
+               ch->ch_mout |= DM_RTS;
+
+       if (set & TIOCM_DTR)
+               ch->ch_mout |= DM_DTR;
+
+       if (clear & TIOCM_RTS)
+               ch->ch_mout &= ~(DM_RTS);
+
+       if (clear & TIOCM_DTR)
+               ch->ch_mout &= ~(DM_DTR);
+
+       spin_lock_irqsave(&(ch->ch_nd)->nd_lock, lock_flags);
+       ch->ch_flag |= CH_PARAM;
+       (ch->ch_nd)->nd_tx_work = 1;
+       wake_up_interruptible(&ch->ch_flag_wait);
+
+       spin_unlock_irqrestore(&(ch->ch_nd)->nd_lock, lock_flags);
+
+       return 0;
+}
+
+
+
+/*
+ *      Get current modem status
+ */
+static int get_modem_info(struct ch_struct *ch, unsigned int *value)
+{
+       unsigned int mlast;
+
+       mlast = ((ch->ch_s_mlast & ~(DM_RTS | DM_DTR)) |
+               (ch->ch_mout    &  (DM_RTS | DM_DTR)));
+
+       /* defined in /usr/include/asm/termios.h */
+       mlast =   ((mlast & DM_RTS) ? TIOCM_RTS : 0)
+               | ((mlast & DM_DTR) ? TIOCM_DTR : 0)
+               | ((mlast & DM_CD)  ? TIOCM_CAR : 0)
+               | ((mlast & DM_RI)  ? TIOCM_RNG : 0)
+               | ((mlast & DM_DSR) ? TIOCM_DSR : 0)
+               | ((mlast & DM_CTS) ? TIOCM_CTS : 0);
+       put_user(mlast, (unsigned int __user *) value);
+
+       return 0;
+}
+
+/*
+ *      Set modem lines
+ */
+static int set_modem_info(struct ch_struct *ch, unsigned int command,
+                         unsigned int *value)
+{
+       int error;
+       unsigned int arg;
+       int mval = 0;
+       ulong lock_flags;
+
+       error = access_ok(VERIFY_READ, (void __user *) value, sizeof(int));
+       if (error == 0)
+               return -EFAULT;
+
+       get_user(arg, (unsigned int __user *) value);
+       mval |= ((arg & TIOCM_RTS) ? DM_RTS : 0)
+               | ((arg & TIOCM_DTR) ? DM_DTR : 0);
+
+       switch (command) {
+       case TIOCMBIS:  /* set flags */
+               ch->ch_mout |= mval;
+               break;
+       case TIOCMBIC:  /* clear flags */
+               ch->ch_mout &= ~mval;
+               break;
+       case TIOCMSET:
+               ch->ch_mout = mval;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       spin_lock_irqsave(&(ch->ch_nd)->nd_lock, lock_flags);
+
+       ch->ch_flag |= CH_PARAM;
+       (ch->ch_nd)->nd_tx_work = 1;
+       wake_up_interruptible(&ch->ch_flag_wait);
+
+       spin_unlock_irqrestore(&(ch->ch_nd)->nd_lock, lock_flags);
+
+       return 0;
+}
+
+
+/*
+ *  Assign the custom baud rate to the channel structure
+ */
+static void dgrp_set_custom_speed(struct ch_struct *ch, int newrate)
+{
+       int testdiv;
+       int testrate_high;
+       int testrate_low;
+
+       int deltahigh, deltalow;
+
+       if (newrate < 0)
+               newrate = 0;
+
+       /*
+        * Since the divisor is stored in a 16-bit integer, we make sure
+        * we don't allow any rates smaller than a 16-bit integer would allow.
+        * And of course, rates above the dividend won't fly.
+        */
+       if (newrate && newrate < ((PORTSERVER_DIVIDEND / 0xFFFF) + 1))
+               newrate = ((PORTSERVER_DIVIDEND / 0xFFFF) + 1);
+       if (newrate && newrate > PORTSERVER_DIVIDEND)
+               newrate = PORTSERVER_DIVIDEND;
+
+       while (newrate > 0) {
+               testdiv = PORTSERVER_DIVIDEND / newrate;
+
+               /*
+                * If we try to figure out what rate the PortServer would use
+                * with the test divisor, it will be either equal or higher
+                * than the requested baud rate.  If we then determine the
+                * rate with a divisor one higher, we will get the next lower
+                * supported rate below the requested.
+                */
+               testrate_high = PORTSERVER_DIVIDEND / testdiv;
+               testrate_low  = PORTSERVER_DIVIDEND / (testdiv + 1);
+
+               /*
+                * If the rate for the requested divisor is correct, just
+                * use it and be done.
+                */
+               if (testrate_high == newrate)
+                       break;
+
+               /*
+                * Otherwise, pick the rate that is closer (i.e. whichever rate
+                * has a smaller delta).
+                */
+               deltahigh = testrate_high - newrate;
+               deltalow = newrate - testrate_low;
+
+               if (deltahigh < deltalow)
+                       newrate = testrate_high;
+               else
+                       newrate = testrate_low;
+
+               break;
+       }
+
+       ch->ch_custom_speed = newrate;
+
+       drp_param(ch);
+
+       return;
+}
+
+
+/*
+ # dgrp_tty_digiseta()
+ *
+ * Ioctl to set the information from ditty.
+ *
+ * NOTE: DIGI_IXON, DSRPACE, DCDPACE, and DTRPACE are unsupported.  JAR 990922
+ */
+static int dgrp_tty_digiseta(struct tty_struct *tty,
+                            struct digi_struct *new_info)
+{
+       struct un_struct *un = tty->driver_data;
+       struct ch_struct *ch;
+
+       if (!un)
+               return -ENODEV;
+
+       ch = un->un_ch;
+       if (!ch)
+               return -ENODEV;
+
+       if (copy_from_user(&ch->ch_digi, (void __user *) new_info,
+                          sizeof(struct digi_struct)))
+               return -EFAULT;
+
+       if ((ch->ch_digi.digi_flags & RTSPACE) ||
+           (ch->ch_digi.digi_flags & CTSPACE))
+               tty->termios.c_cflag |= CRTSCTS;
+       else
+               tty->termios.c_cflag &= ~CRTSCTS;
+
+       if (ch->ch_digi.digi_maxcps < 1)
+               ch->ch_digi.digi_maxcps = 1;
+
+       if (ch->ch_digi.digi_maxcps > 10000)
+               ch->ch_digi.digi_maxcps = 10000;
+
+       if (ch->ch_digi.digi_bufsize < 10)
+               ch->ch_digi.digi_bufsize = 10;
+
+       if (ch->ch_digi.digi_maxchar < 1)
+               ch->ch_digi.digi_maxchar = 1;
+
+       if (ch->ch_digi.digi_maxchar > ch->ch_digi.digi_bufsize)
+               ch->ch_digi.digi_maxchar = ch->ch_digi.digi_bufsize;
+
+       if (ch->ch_digi.digi_onlen > DIGI_PLEN)
+               ch->ch_digi.digi_onlen = DIGI_PLEN;
+
+       if (ch->ch_digi.digi_offlen > DIGI_PLEN)
+               ch->ch_digi.digi_offlen = DIGI_PLEN;
+
+       /* make the changes now */
+       drp_param(ch);
+
+       return 0;
+}
+
+
+
+/*
+ * dgrp_tty_digigetedelay()
+ *
+ * Ioctl to get the current edelay setting.
+ *
+ *
+ *
+ */
+static int dgrp_tty_digigetedelay(struct tty_struct *tty, int *retinfo)
+{
+       struct un_struct *un;
+       struct ch_struct *ch;
+       int tmp;
+
+       if (!retinfo)
+               return -EFAULT;
+
+       if (!tty || tty->magic != TTY_MAGIC)
+               return -EFAULT;
+
+       un = tty->driver_data;
+
+       if (!un)
+               return -ENODEV;
+
+       ch = un->un_ch;
+       if (!ch)
+               return -ENODEV;
+
+       tmp = ch->ch_edelay;
+
+       if (copy_to_user((void __user *) retinfo, &tmp, sizeof(*retinfo)))
+               return -EFAULT;
+
+       return 0;
+}
+
+
+/*
+ * dgrp_tty_digisetedelay()
+ *
+ * Ioctl to set the EDELAY setting
+ *
+ */
+static int dgrp_tty_digisetedelay(struct tty_struct *tty, int *new_info)
+{
+       struct un_struct *un;
+       struct ch_struct *ch;
+       int new_digi;
+
+       if (!tty || tty->magic != TTY_MAGIC)
+               return -EFAULT;
+
+       un = tty->driver_data;
+
+       if (!un)
+               return -ENODEV;
+
+       ch = un->un_ch;
+       if (!ch)
+               return -ENODEV;
+
+       if (copy_from_user(&new_digi, (void __user *)new_info, sizeof(int)))
+               return -EFAULT;
+
+       ch->ch_edelay = new_digi;
+
+       /* make the changes now */
+       drp_param(ch);
+
+       return 0;
+}
+
+
+/*
+ *     The usual assortment of ioctl's
+ *
+ *     note:  use tty_check_change to make sure that we are not
+ *     changing the state of a terminal when we are not a process
+ *     in the forground.  See tty_io.c
+ *             rc = tty_check_change(tty);
+ *             if (rc) return rc;
+ */
+static int dgrp_tty_ioctl(struct tty_struct *tty, unsigned int cmd,
+                         unsigned long arg)
+{
+       struct un_struct *un;
+       struct ch_struct *ch;
+       int rc;
+       struct digiflow_struct   dflow;
+
+       if (!tty)
+               return -ENODEV;
+
+       un = tty->driver_data;
+       if (!un)
+               return -ENODEV;
+
+       ch = un->un_ch;
+       if (!ch)
+               return -ENODEV;
+
+       switch (cmd) {
+
+       /*
+        * Here are all the standard ioctl's that we MUST implement
+        */
+
+       case TCSBRK:
+               /*
+                * TCSBRK is SVID version: non-zero arg --> no break
+                * this behaviour is exploited by tcdrain().
+                *
+                * According to POSIX.1 spec (7.2.2.1.2) breaks should be
+                * between 0.25 and 0.5 seconds
+                */
+
+               rc = tty_check_change(tty);
+               if (rc)
+                       return rc;
+               tty_wait_until_sent(tty, 0);
+
+               if (!arg)
+                       rc = dgrp_send_break(ch, 250); /* 1/4 second */
+
+               if (dgrp_tty_chars_in_buffer(tty) != 0)
+                       return -EINTR;
+
+               return 0;
+
+       case TCSBRKP:
+               /* support for POSIX tcsendbreak()
+                *
+                * According to POSIX.1 spec (7.2.2.1.2) breaks should be
+                * between 0.25 and 0.5 seconds so we'll ask for something
+                * in the middle: 0.375 seconds.
+                */
+               rc = tty_check_change(tty);
+               if (rc)
+                       return rc;
+               tty_wait_until_sent(tty, 0);
+
+               rc = dgrp_send_break(ch, arg ? arg*250 : 250);
+
+               if (dgrp_tty_chars_in_buffer(tty) != 0)
+                       return -EINTR;
+               return 0;
+
+       case TIOCSBRK:
+               rc = tty_check_change(tty);
+               if (rc)
+                       return rc;
+               tty_wait_until_sent(tty, 0);
+
+               /*
+                * RealPort doesn't support turning on a break unconditionally.
+                * The RealPort device will stop sending a break automatically
+                * after the specified time value that we send in.
+                */
+               rc = dgrp_send_break(ch, 250); /* 1/4 second */
+
+               if (dgrp_tty_chars_in_buffer(tty) != 0)
+                       return -EINTR;
+               return 0;
+
+       case TIOCCBRK:
+               /*
+                * RealPort doesn't support turning off a break unconditionally.
+                * The RealPort device will stop sending a break automatically
+                * after the specified time value that was sent when turning on
+                * the break.
+                */
+               return 0;
+
+       case TIOCGSOFTCAR:
+               rc = access_ok(VERIFY_WRITE, (void __user *) arg,
+                              sizeof(long));
+               if (rc == 0)
+                       return -EFAULT;
+               put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long __user *) arg);
+               return 0;
+
+       case TIOCSSOFTCAR:
+               get_user(arg, (unsigned long __user *) arg);
+               tty->termios.c_cflag =
+                       ((tty->termios.c_cflag & ~CLOCAL) |
+                        (arg ? CLOCAL : 0));
+               return 0;
+
+       case TIOCMGET:
+               rc = access_ok(VERIFY_WRITE, (void __user *) arg,
+                                sizeof(unsigned int));
+               if (rc == 0)
+                       return -EFAULT;
+               return get_modem_info(ch, (unsigned int *) arg);
+
+       case TIOCMBIS:
+       case TIOCMBIC:
+       case TIOCMSET:
+               return set_modem_info(ch, cmd, (unsigned int *) arg);
+
+       /*
+        * Here are any additional ioctl's that we want to implement
+        */
+
+       case TCFLSH:
+               /*
+                * The linux tty driver doesn't have a flush
+                * input routine for the driver, assuming all backed
+                * up data is in the line disc. buffers.  However,
+                * we all know that's not the case.  Here, we
+                * act on the ioctl, but then lie and say we didn't
+                * so the line discipline will process the flush
+                * also.
+                */
+               rc = tty_check_change(tty);
+               if (rc)
+                       return rc;
+
+               switch (arg) {
+               case TCIFLUSH:
+               case TCIOFLUSH:
+                       /* only flush input if this is the only open unit */
+                       if (!IS_PRINT(MINOR(tty_devnum(tty)))) {
+                               ch->ch_rout = ch->ch_rin;
+                               ch->ch_send |= RR_RX_FLUSH;
+                               (ch->ch_nd)->nd_tx_work = 1;
+                               (ch->ch_nd)->nd_tx_ready = 1;
+                               wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+                       }
+                       if (arg == TCIFLUSH)
+                               break;
+
+               case TCOFLUSH: /* flush output, or the receive buffer */
+                       /*
+                        * This is handled in the tty_ioctl.c code
+                        * calling tty_flush_buffer
+                        */
+                       break;
+
+               default:
+                       /* POSIX.1 says return EINVAL if we got a bad arg */
+                       return -EINVAL;
+               }
+               /* pretend we didn't recognize this IOCTL */
+               return -ENOIOCTLCMD;
+
+#ifdef TIOCGETP
+       case TIOCGETP:
+#endif
+       /*****************************************
+       Linux           HPUX            Function
+       TCSETA          TCSETA          - set the termios
+       TCSETAF         TCSETAF         - wait for drain first, then set termios
+       TCSETAW         TCSETAW         - wait for drain, flush the input queue, then set termios
+       - looking at the tty_ioctl code, these command all call our
+       tty_set_termios at the driver's end, when a TCSETA* is sent,
+       it is expecting the tty to have a termio structure,
+       NOT a termios stucture.  These two structures differ in size
+       and the tty_ioctl code does a conversion before processing them both.
+       - we should treat the TCSETAW TCSETAF ioctls the same, and let
+       the tty_ioctl code do the conversion stuff.
+
+       TCSETS
+       TCSETSF         (none)
+       TCSETSW
+       - the associated tty structure has a termios structure.
+       *****************************************/
+
+       case TCGETS:
+       case TCGETA:
+               return -ENOIOCTLCMD;
+
+       case TCSETAW:
+       case TCSETAF:
+       case TCSETSF:
+       case TCSETSW:
+               /*
+                * The linux tty driver doesn't have a flush
+                * input routine for the driver, assuming all backed
+                * up data is in the line disc. buffers.  However,
+                * we all know that's not the case.  Here, we
+                * act on the ioctl, but then lie and say we didn't
+                * so the line discipline will process the flush
+                * also.
+                */
+
+               /*
+                * Also, now that we have TXPrint, we have to check
+                * if this is the TXPrint device and the terminal
+                * device is open. If so, do NOT run check_change,
+                * as the terminal device is ALWAYS the parent.
+                */
+               if (!IS_PRINT(MINOR(tty_devnum(tty))) ||
+                   !ch->ch_tun.un_open_count) {
+                       rc = tty_check_change(tty);
+                       if (rc)
+                               return rc;
+               }
+
+               /* wait for all the characters in tbuf to drain */
+               tty_wait_until_sent(tty, 0);
+
+               if ((cmd == TCSETSF) || (cmd == TCSETAF)) {
+                       /* flush the contents of the rbuf queue */
+                       /* TODO:  check if this is print device? */
+                       ch->ch_send |= RR_RX_FLUSH;
+                       (ch->ch_nd)->nd_tx_ready = 1;
+                       (ch->ch_nd)->nd_tx_work = 1;
+                       wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+                       /* do we need to do this?  just to be safe! */
+                       ch->ch_rout = ch->ch_rin;
+               }
+
+               /* pretend we didn't recognize this */
+               return -ENOIOCTLCMD;
+
+       case TCXONC:
+               /*
+                * The Linux Line Discipline (LD) would do this for us if we
+                * let it, but we have the special firmware options to do this
+                * the "right way" regardless of hardware or software flow
+                * control so we'll do it outselves instead of letting the LD
+                * do it.
+                */
+               rc = tty_check_change(tty);
+               if (rc)
+                       return rc;
+
+               switch (arg) {
+               case TCOON:
+                       dgrp_tty_start(tty);
+                       return 0;
+               case TCOOFF:
+                       dgrp_tty_stop(tty);
+                       return 0;
+               case TCION:
+                       dgrp_tty_input_start(tty);
+                       return 0;
+               case TCIOFF:
+                       dgrp_tty_input_stop(tty);
+                       return 0;
+               default:
+                       return -EINVAL;
+               }
+
+       case DIGI_GETA:
+               /* get information for ditty */
+               if (copy_to_user((struct digi_struct __user *) arg,
+                                &ch->ch_digi, sizeof(struct digi_struct)))
+                       return -EFAULT;
+               break;
+
+       case DIGI_SETAW:
+       case DIGI_SETAF:
+               /* wait for all the characters in tbuf to drain */
+               tty_wait_until_sent(tty, 0);
+
+               if (cmd == DIGI_SETAF) {
+                       /* flush the contents of the rbuf queue */
+                       /* send down a packet with RR_RX_FLUSH set */
+                       ch->ch_send |= RR_RX_FLUSH;
+                       (ch->ch_nd)->nd_tx_ready = 1;
+                       (ch->ch_nd)->nd_tx_work = 1;
+                       wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+                       /* do we need to do this?  just to be safe! */
+                       ch->ch_rout = ch->ch_rin;
+               }
+
+               /* pretend we didn't recognize this */
+
+       case DIGI_SETA:
+               return dgrp_tty_digiseta(tty, (struct digi_struct *) arg);
+
+       case DIGI_SEDELAY:
+               return dgrp_tty_digisetedelay(tty, (int *) arg);
+
+       case DIGI_GEDELAY:
+               return dgrp_tty_digigetedelay(tty, (int *) arg);
+
+       case DIGI_GETFLOW:
+       case DIGI_GETAFLOW:
+               if (cmd == (DIGI_GETFLOW)) {
+                       dflow.startc = tty->termios.c_cc[VSTART];
+                       dflow.stopc = tty->termios.c_cc[VSTOP];
+               } else {
+                       dflow.startc = ch->ch_xxon;
+                       dflow.stopc = ch->ch_xxoff;
+               }
+
+               if (copy_to_user((char __user *)arg, &dflow, sizeof(dflow)))
+                       return -EFAULT;
+               break;
+
+       case DIGI_SETFLOW:
+       case DIGI_SETAFLOW:
+
+               if (copy_from_user(&dflow, (char __user *)arg, sizeof(dflow)))
+                       return -EFAULT;
+
+               if (cmd == (DIGI_SETFLOW)) {
+                       tty->termios.c_cc[VSTART] = dflow.startc;
+                       tty->termios.c_cc[VSTOP] = dflow.stopc;
+               } else {
+                       ch->ch_xxon = dflow.startc;
+                       ch->ch_xxoff = dflow.stopc;
+               }
+               break;
+
+       case DIGI_GETCUSTOMBAUD:
+               rc = access_ok(VERIFY_WRITE, (void __user *) arg, sizeof(int));
+               if (rc == 0)
+                       return -EFAULT;
+               put_user(ch->ch_custom_speed, (unsigned int __user *) arg);
+               break;
+
+       case DIGI_SETCUSTOMBAUD:
+       {
+               int new_rate;
+
+               get_user(new_rate, (unsigned int __user *) arg);
+               dgrp_set_custom_speed(ch, new_rate);
+
+               break;
+       }
+
+       default:
+               return -ENOIOCTLCMD;
+       }
+
+       return 0;
+}
+
+/*
+ *  This routine allows the tty driver to be notified when
+ *  the device's termios setting have changed.  Note that we
+ *  should be prepared to accept the case where old == NULL
+ *  and try to do something rational.
+ *
+ *  So we need to make sure that our copies of ch_oflag,
+ *  ch_clag, and ch_iflag reflect the tty->termios flags.
+ */
+static void dgrp_tty_set_termios(struct tty_struct *tty, struct ktermios *old)
+{
+       struct ktermios *ts;
+       struct ch_struct *ch;
+       struct un_struct *un;
+
+       /* seems silly, but we have to check all these! */
+       if (!tty)
+               return;
+
+       un = tty->driver_data;
+       if (!un)
+               return;
+
+       ts = &tty->termios;
+
+       ch = un->un_ch;
+       if (!ch)
+               return;
+
+       drp_param(ch);
+
+       /* the CLOCAL flag has just been set */
+       if (!(old->c_cflag & CLOCAL) && C_CLOCAL(tty))
+               wake_up_interruptible(&un->un_open_wait);
+}
+
+
+/*
+ *     Throttle receiving data.  We just set a bit and stop reading
+ *     data out of the channel buffer.  It will back up and the
+ *     FEP will do whatever is necessary to stop the far end.
+ */
+static void dgrp_tty_throttle(struct tty_struct *tty)
+{
+       struct ch_struct *ch;
+
+       if (!tty)
+               return;
+
+       ch = ((struct un_struct *) tty->driver_data)->un_ch;
+       if (!ch)
+               return;
+
+       ch->ch_flag |= CH_RXSTOP;
+}
+
+
+static void dgrp_tty_unthrottle(struct tty_struct *tty)
+{
+       struct ch_struct *ch;
+
+       if (!tty)
+               return;
+
+       ch = ((struct un_struct *) tty->driver_data)->un_ch;
+       if (!ch)
+               return;
+
+       ch->ch_flag &= ~CH_RXSTOP;
+}
+
+/*
+ *     Stop the transmitter
+ */
+static void dgrp_tty_stop(struct tty_struct *tty)
+{
+       struct ch_struct *ch;
+
+       if (!tty)
+               return;
+
+       ch = ((struct un_struct *) tty->driver_data)->un_ch;
+       if (!ch)
+               return;
+
+       ch->ch_send |= RR_TX_STOP;
+       ch->ch_send &= ~RR_TX_START;
+
+       /* make the change NOW! */
+       (ch->ch_nd)->nd_tx_ready = 1;
+       if (waitqueue_active(&(ch->ch_nd)->nd_tx_waitq))
+               wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+}
+
+/*
+ *     Start the transmitter
+ */
+static void dgrp_tty_start(struct tty_struct *tty)
+{
+       struct ch_struct *ch;
+
+       if (!tty)
+               return;
+
+       ch = ((struct un_struct *) tty->driver_data)->un_ch;
+       if (!ch)
+               return;
+
+       /* TODO: don't do anything if the transmitter is not stopped */
+
+       ch->ch_send |= RR_TX_START;
+       ch->ch_send &= ~RR_TX_STOP;
+
+       /* make the change NOW! */
+       (ch->ch_nd)->nd_tx_ready = 1;
+       (ch->ch_nd)->nd_tx_work = 1;
+       if (waitqueue_active(&(ch->ch_nd)->nd_tx_waitq))
+               wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+
+}
+
+/*
+ *     Stop the reciever
+ */
+static void dgrp_tty_input_stop(struct tty_struct *tty)
+{
+       struct ch_struct *ch;
+
+       if (!tty)
+               return;
+
+       ch = ((struct un_struct *) tty->driver_data)->un_ch;
+       if (!ch)
+               return;
+
+       ch->ch_send |= RR_RX_STOP;
+       ch->ch_send &= ~RR_RX_START;
+       (ch->ch_nd)->nd_tx_ready = 1;
+       if (waitqueue_active(&(ch->ch_nd)->nd_tx_waitq))
+               wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+
+}
+
+
+static void dgrp_tty_send_xchar(struct tty_struct *tty, char c)
+{
+       struct un_struct *un;
+       struct ch_struct *ch;
+
+       if (!tty)
+               return;
+
+       un = tty->driver_data;
+       if (!un)
+               return;
+
+       ch = un->un_ch;
+       if (!ch)
+               return;
+       if (c == STOP_CHAR(tty))
+               ch->ch_send |= RR_RX_STOP;
+       else if (c == START_CHAR(tty))
+               ch->ch_send |= RR_RX_START;
+
+       ch->ch_nd->nd_tx_ready = 1;
+       ch->ch_nd->nd_tx_work = 1;
+
+       return;
+}
+
+
+static void dgrp_tty_input_start(struct tty_struct *tty)
+{
+       struct ch_struct *ch;
+
+       if (!tty)
+               return;
+
+       ch = ((struct un_struct *) tty->driver_data)->un_ch;
+       if (!ch)
+               return;
+
+       ch->ch_send |= RR_RX_START;
+       ch->ch_send &= ~RR_RX_STOP;
+       (ch->ch_nd)->nd_tx_ready = 1;
+       (ch->ch_nd)->nd_tx_work = 1;
+       if (waitqueue_active(&(ch->ch_nd)->nd_tx_waitq))
+               wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+
+}
+
+
+/*
+ *     Hangup the port.  Like a close, but don't wait for output
+ *     to drain.
+ *
+ *     How do we close all the channels that are open?
+ */
+static void dgrp_tty_hangup(struct tty_struct *tty)
+{
+       struct ch_struct *ch;
+       struct nd_struct *nd;
+       struct un_struct *un;
+
+       if (!tty)
+               return;
+
+       un = tty->driver_data;
+       if (!un)
+               return;
+
+       ch = un->un_ch;
+       if (!ch)
+               return;
+
+       nd = ch->ch_nd;
+
+       if (C_HUPCL(tty)) {
+               /* LOWER DTR */
+               ch->ch_mout &= ~DM_DTR;
+               /* Don't do this here */
+               /* ch->ch_flag |= CH_HANGUP; */
+               ch->ch_nd->nd_tx_ready = 1;
+               ch->ch_nd->nd_tx_work  = 1;
+               if (waitqueue_active(&ch->ch_flag_wait))
+                       wake_up_interruptible(&ch->ch_flag_wait);
+       }
+
+}
+
+/************************************************************************/
+/*                                                                      */
+/*       TTY Initialization/Cleanup Functions                           */
+/*                                                                      */
+/************************************************************************/
+
+/*
+ *     Uninitialize the TTY portion of the supplied node.  Free all
+ *      memory and resources associated with this node.  Do it in reverse
+ *      allocation order: this might possibly result in less fragmentation
+ *      of memory, though I don't know this for sure.
+ */
+void
+dgrp_tty_uninit(struct nd_struct *nd)
+{
+       char id[3];
+
+       ID_TO_CHAR(nd->nd_ID, id);
+
+       if (nd->nd_ttdriver_flags & SERIAL_TTDRV_REG) {
+               tty_unregister_driver(nd->nd_serial_ttdriver);
+
+               kfree(nd->nd_serial_ttdriver->ttys);
+               nd->nd_serial_ttdriver->ttys = NULL;
+
+               put_tty_driver(nd->nd_serial_ttdriver);
+               nd->nd_ttdriver_flags &= ~SERIAL_TTDRV_REG;
+       }
+
+       if (nd->nd_ttdriver_flags & CALLOUT_TTDRV_REG) {
+               tty_unregister_driver(nd->nd_callout_ttdriver);
+
+               kfree(nd->nd_callout_ttdriver->ttys);
+               nd->nd_callout_ttdriver->ttys = NULL;
+
+               put_tty_driver(nd->nd_callout_ttdriver);
+               nd->nd_ttdriver_flags &= ~CALLOUT_TTDRV_REG;
+       }
+
+       if (nd->nd_ttdriver_flags & XPRINT_TTDRV_REG) {
+               tty_unregister_driver(nd->nd_xprint_ttdriver);
+
+               kfree(nd->nd_xprint_ttdriver->ttys);
+               nd->nd_xprint_ttdriver->ttys = NULL;
+
+               put_tty_driver(nd->nd_xprint_ttdriver);
+               nd->nd_ttdriver_flags &= ~XPRINT_TTDRV_REG;
+       }
+}
+
+
+
+/*
+ *     Initialize the TTY portion of the supplied node.
+ */
+int
+dgrp_tty_init(struct nd_struct *nd)
+{
+       char id[3];
+       int  rc;
+       int  i;
+
+       ID_TO_CHAR(nd->nd_ID, id);
+
+       /*
+        *  Initialize the TTDRIVER structures.
+        */
+
+       nd->nd_serial_ttdriver = alloc_tty_driver(CHAN_MAX);
+       sprintf(nd->nd_serial_name,  "tty_dgrp_%s_", id);
+
+       nd->nd_serial_ttdriver->owner = THIS_MODULE;
+       nd->nd_serial_ttdriver->name = nd->nd_serial_name;
+       nd->nd_serial_ttdriver->name_base = 0;
+       nd->nd_serial_ttdriver->major = 0;
+       nd->nd_serial_ttdriver->minor_start = 0;
+       nd->nd_serial_ttdriver->type = TTY_DRIVER_TYPE_SERIAL;
+       nd->nd_serial_ttdriver->subtype = SERIAL_TYPE_NORMAL;
+       nd->nd_serial_ttdriver->init_termios = DefaultTermios;
+       nd->nd_serial_ttdriver->driver_name = "dgrp";
+       nd->nd_serial_ttdriver->flags = (TTY_DRIVER_REAL_RAW |
+                                        TTY_DRIVER_DYNAMIC_DEV |
+                                        TTY_DRIVER_HARDWARE_BREAK);
+
+       /* The kernel wants space to store pointers to tty_structs. */
+       nd->nd_serial_ttdriver->ttys =
+               kzalloc(CHAN_MAX * sizeof(struct tty_struct *), GFP_KERNEL);
+       if (!nd->nd_serial_ttdriver->ttys)
+               return -ENOMEM;
+
+       tty_set_operations(nd->nd_serial_ttdriver, &dgrp_tty_ops);
+
+       if (!(nd->nd_ttdriver_flags & SERIAL_TTDRV_REG)) {
+               /*
+                *   Register tty devices
+                */
+               rc = tty_register_driver(nd->nd_serial_ttdriver);
+               if (rc < 0) {
+                       /*
+                        * If errno is EBUSY, this means there are no more
+                        * slots available to have us auto-majored.
+                        * (Which is currently supported up to 256)
+                        *
+                        * We can still request majors above 256,
+                        * we just have to do it manually.
+                        */
+                       if (rc == -EBUSY) {
+                               int i;
+                               int max_majors = 1U << (32 - MINORBITS);
+                               for (i = 256; i < max_majors; i++) {
+                                       nd->nd_serial_ttdriver->major = i;
+                                       rc = tty_register_driver(nd->nd_serial_ttdriver);
+                                       if (rc >= 0)
+                                               break;
+                               }
+                               /* Really fail now, since we ran out
+                                * of majors to try. */
+                               if (i == max_majors)
+                                       return rc;
+
+                       } else {
+                               return rc;
+                       }
+               }
+               nd->nd_ttdriver_flags |= SERIAL_TTDRV_REG;
+       }
+
+       nd->nd_callout_ttdriver = alloc_tty_driver(CHAN_MAX);
+       sprintf(nd->nd_callout_name, "cu_dgrp_%s_",  id);
+
+       nd->nd_callout_ttdriver->owner = THIS_MODULE;
+       nd->nd_callout_ttdriver->name = nd->nd_callout_name;
+       nd->nd_callout_ttdriver->name_base = 0;
+       nd->nd_callout_ttdriver->major = nd->nd_serial_ttdriver->major;
+       nd->nd_callout_ttdriver->minor_start = 0x40;
+       nd->nd_callout_ttdriver->type = TTY_DRIVER_TYPE_SERIAL;
+       nd->nd_callout_ttdriver->subtype = SERIAL_TYPE_CALLOUT;
+       nd->nd_callout_ttdriver->init_termios = DefaultTermios;
+       nd->nd_callout_ttdriver->driver_name = "dgrp";
+       nd->nd_callout_ttdriver->flags = (TTY_DRIVER_REAL_RAW |
+                                         TTY_DRIVER_DYNAMIC_DEV |
+                                         TTY_DRIVER_HARDWARE_BREAK);
+
+       /* The kernel wants space to store pointers to tty_structs. */
+       nd->nd_callout_ttdriver->ttys =
+               kzalloc(CHAN_MAX * sizeof(struct tty_struct *), GFP_KERNEL);
+       if (!nd->nd_callout_ttdriver->ttys)
+               return -ENOMEM;
+
+       tty_set_operations(nd->nd_callout_ttdriver, &dgrp_tty_ops);
+
+       if (dgrp_register_cudevices) {
+               if (!(nd->nd_ttdriver_flags & CALLOUT_TTDRV_REG)) {
+                       /*
+                        *   Register cu devices
+                        */
+                       rc = tty_register_driver(nd->nd_callout_ttdriver);
+                       if (rc < 0)
+                               return rc;
+                       nd->nd_ttdriver_flags |= CALLOUT_TTDRV_REG;
+               }
+       }
+
+
+       nd->nd_xprint_ttdriver = alloc_tty_driver(CHAN_MAX);
+       sprintf(nd->nd_xprint_name,  "pr_dgrp_%s_", id);
+
+       nd->nd_xprint_ttdriver->owner = THIS_MODULE;
+       nd->nd_xprint_ttdriver->name = nd->nd_xprint_name;
+       nd->nd_xprint_ttdriver->name_base = 0;
+       nd->nd_xprint_ttdriver->major = nd->nd_serial_ttdriver->major;
+       nd->nd_xprint_ttdriver->minor_start = 0x80;
+       nd->nd_xprint_ttdriver->type = TTY_DRIVER_TYPE_SERIAL;
+       nd->nd_xprint_ttdriver->subtype = SERIAL_TYPE_XPRINT;
+       nd->nd_xprint_ttdriver->init_termios = DefaultTermios;
+       nd->nd_xprint_ttdriver->driver_name = "dgrp";
+       nd->nd_xprint_ttdriver->flags = (TTY_DRIVER_REAL_RAW |
+                                        TTY_DRIVER_DYNAMIC_DEV |
+                                        TTY_DRIVER_HARDWARE_BREAK);
+
+       /* The kernel wants space to store pointers to tty_structs. */
+       nd->nd_xprint_ttdriver->ttys =
+               kzalloc(CHAN_MAX * sizeof(struct tty_struct *), GFP_KERNEL);
+       if (!nd->nd_xprint_ttdriver->ttys)
+               return -ENOMEM;
+
+       tty_set_operations(nd->nd_xprint_ttdriver, &dgrp_tty_ops);
+
+       if (dgrp_register_prdevices) {
+               if (!(nd->nd_ttdriver_flags & XPRINT_TTDRV_REG)) {
+                       /*
+                        *   Register transparent print devices
+                        */
+                       rc = tty_register_driver(nd->nd_xprint_ttdriver);
+                       if (rc < 0)
+                               return rc;
+                       nd->nd_ttdriver_flags |= XPRINT_TTDRV_REG;
+               }
+       }
+
+       for (i = 0; i < CHAN_MAX; i++) {
+               struct ch_struct *ch = nd->nd_chan + i;
+
+               ch->ch_nd = nd;
+               ch->ch_digi = digi_init;
+               ch->ch_edelay = 100;
+               ch->ch_custom_speed = 0;
+               ch->ch_portnum = i;
+               ch->ch_tun.un_ch = ch;
+               ch->ch_pun.un_ch = ch;
+               ch->ch_tun.un_type = SERIAL_TYPE_NORMAL;
+               ch->ch_pun.un_type = SERIAL_TYPE_XPRINT;
+
+               init_waitqueue_head(&(ch->ch_flag_wait));
+               init_waitqueue_head(&(ch->ch_sleep));
+
+               init_waitqueue_head(&(ch->ch_tun.un_open_wait));
+               init_waitqueue_head(&(ch->ch_tun.un_close_wait));
+
+               init_waitqueue_head(&(ch->ch_pun.un_open_wait));
+               init_waitqueue_head(&(ch->ch_pun.un_close_wait));
+               tty_port_init(&ch->port);
+               tty_port_init(&ch->port);
+       }
+       return 0;
+}
diff --git a/drivers/staging/dgrp/digirp.h b/drivers/staging/dgrp/digirp.h
new file mode 100644 (file)
index 0000000..33c1394
--- /dev/null
@@ -0,0 +1,129 @@
+/************************************************************************
+ * HP-UX Realport Daemon interface file.
+ *
+ * Copyright (C) 1998, by Digi International.  All Rights Reserved.
+ ************************************************************************/
+
+#ifndef _DIGIDRP_H
+#define _DIGIDRP_H
+
+/************************************************************************
+ * This file contains defines for the ioctl() interface to
+ * the realport driver.   This ioctl() interface is used by the
+ * daemon to set speed setup parameters honored by the driver.
+ ************************************************************************/
+
+struct link_struct {
+       int lk_fast_rate;  /* Fast line rate to be used
+                             when the delay is less-equal
+                             to lk_fast_delay */
+
+       int lk_fast_delay; /* Fast line rate delay in
+                             milliseconds */
+
+       int lk_slow_rate;  /* Slow line rate to be used when
+                             the delay is greater-equal
+                             to lk_slow_delay */
+
+       int lk_slow_delay; /* Slow line rate delay in
+                             milliseconds */
+
+       int lk_header_size; /* Estimated packet header size
+                              when sent across the slowest
+                              link.  */
+};
+
+#define DIGI_GETLINK   _IOW('e', 103, struct link_struct)      /* Get link parameters */
+#define DIGI_SETLINK   _IOW('e', 104, struct link_struct)      /* Set link parameters */
+
+
+/************************************************************************
+ * This module provides application access to special Digi
+ * serial line enhancements which are not standard UNIX(tm) features.
+ ************************************************************************/
+
+struct digiflow_struct {
+       unsigned char   startc;                         /* flow cntl start char */
+       unsigned char   stopc;                          /* flow cntl stop char  */
+};
+
+/************************************************************************
+ * Values for digi_flags
+ ************************************************************************/
+#define DIGI_IXON      0x0001          /* Handle IXON in the FEP       */
+#define DIGI_FAST      0x0002          /* Fast baud rates              */
+#define RTSPACE                0x0004          /* RTS input flow control       */
+#define CTSPACE                0x0008          /* CTS output flow control      */
+#define DSRPACE                0x0010          /* DSR output flow control      */
+#define DCDPACE                0x0020          /* DCD output flow control      */
+#define DTRPACE                0x0040          /* DTR input flow control       */
+#define DIGI_COOK      0x0080          /* Cooked processing done in FEP */
+#define DIGI_FORCEDCD  0x0100          /* Force carrier                */
+#define        DIGI_ALTPIN     0x0200          /* Alternate RJ-45 pin config   */
+#define        DIGI_AIXON      0x0400          /* Aux flow control in fep      */
+#define        DIGI_PRINTER    0x0800          /* Hold port open for flow cntrl */
+#define DIGI_PP_INPUT  0x1000          /* Change parallel port to input */
+#define DIGI_422       0x4000          /* Change parallel port to input */
+#define DIGI_RTS_TOGGLE        0x8000          /* Support RTS Toggle            */
+
+
+/************************************************************************
+ * Values associated with transparent print
+ ************************************************************************/
+#define DIGI_PLEN      8               /* String length */
+#define        DIGI_TSIZ       10              /* Terminal string len */
+
+
+/************************************************************************
+ * Structure used with ioctl commands for DIGI parameters.
+ ************************************************************************/
+struct digi_struct {
+       unsigned short  digi_flags;             /* Flags (see above)    */
+       unsigned short  digi_maxcps;            /* Max printer CPS      */
+       unsigned short  digi_maxchar;           /* Max chars in print queue */
+       unsigned short  digi_bufsize;           /* Buffer size          */
+       unsigned char   digi_onlen;             /* Length of ON string  */
+       unsigned char   digi_offlen;            /* Length of OFF string */
+       char            digi_onstr[DIGI_PLEN];  /* Printer on string    */
+       char            digi_offstr[DIGI_PLEN]; /* Printer off string   */
+       char            digi_term[DIGI_TSIZ];   /* terminal string      */
+};
+
+/************************************************************************
+ * Ioctl command arguments for DIGI parameters.
+ ************************************************************************/
+/* Read params */
+#define DIGI_GETA      _IOR('e', 94, struct digi_struct)
+
+/* Set params */
+#define DIGI_SETA      _IOW('e', 95, struct digi_struct)
+
+/* Drain & set params  */
+#define DIGI_SETAW     _IOW('e', 96, struct digi_struct)
+
+/* Drain, flush & set params */
+#define DIGI_SETAF     _IOW('e', 97, struct digi_struct)
+
+/* Get startc/stopc flow control characters */
+#define        DIGI_GETFLOW    _IOR('e', 99, struct digiflow_struct)
+
+/* Set startc/stopc flow control characters */
+#define        DIGI_SETFLOW    _IOW('e', 100, struct digiflow_struct)
+
+/* Get Aux. startc/stopc flow control chars */
+#define        DIGI_GETAFLOW   _IOR('e', 101, struct digiflow_struct)
+
+/* Set Aux. startc/stopc flow control chars */
+#define        DIGI_SETAFLOW   _IOW('e', 102, struct digiflow_struct)
+
+/* Set integer baud rate */
+#define        DIGI_SETCUSTOMBAUD      _IOW('e', 106, int)
+
+/* Get integer baud rate */
+#define        DIGI_GETCUSTOMBAUD      _IOR('e', 107, int)
+
+#define        DIGI_GEDELAY    _IOR('d', 246, int)     /* Get edelay */
+#define        DIGI_SEDELAY    _IOW('d', 247, int)     /* Get edelay */
+
+
+#endif /* _DIGIDRP_H */
diff --git a/drivers/staging/dgrp/drp.h b/drivers/staging/dgrp/drp.h
new file mode 100644 (file)
index 0000000..84a1e7b
--- /dev/null
@@ -0,0 +1,693 @@
+/*
+ *
+ * Copyright 1999 Digi International (www.digi.com)
+ *     Gene Olson  <gene at digi dot com>
+ *     James Puzzo <jamesp at digi dot com>
+ *     Scott Kilau <scottk 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.
+ *
+ */
+
+/************************************************************************
+ * Master include file for Linux Realport Driver.
+ ************************************************************************/
+
+#ifndef __DRP_H
+#define __DRP_H
+
+#include <linux/types.h>
+#include <linux/wait.h>
+#include <linux/semaphore.h>
+#include <linux/tty.h>
+
+
+#include "digirp.h"
+
+/************************************************************************
+ * Tuning parameters.
+ ************************************************************************/
+
+#define CHAN_MAX       64              /* Max # ports per server */
+
+#define SEQ_MAX                128             /* Max # transmit sequences (2^n) */
+#define SEQ_MASK       (SEQ_MAX-1)     /* Sequence buffer modulus mask */
+
+#define TBUF_MAX       4096            /* Size of transmit buffer (2^n) */
+#define RBUF_MAX       4096            /* Size of receive buffer (2^n) */
+
+#define TBUF_MASK      (TBUF_MAX-1)    /* Transmit buffer modulus mask */
+#define RBUF_MASK      (RBUF_MAX-1)    /* Receive buffer modulus mask */
+
+#define TBUF_LOW       1000            /* Transmit low water mark */
+
+#define UIO_BASE       1000            /* Base for write operations */
+#define UIO_MIN                2000            /* Minimum size application buffer */
+#define UIO_MAX                8100            /* Unix I/O buffer size */
+
+#define MON_MAX                65536           /* Monitor buffer size (2^n) */
+#define MON_MASK       (MON_MAX-1)     /* Monitor wrap mask */
+
+#define DPA_MAX                65536           /* DPA buffer size (2^n) */
+#define DPA_MASK       (DPA_MAX-1)     /* DPA wrap mask */
+#define DPA_HIGH_WATER 58000           /* Enforce flow control when
+                                        * over this amount
+                                        */
+
+#define IDLE_MAX       (20 * HZ)       /* Max TCP link idle time */
+
+#define MAX_DESC_LEN   100             /* Maximum length of stored PS
+                                        * description
+                                        */
+
+#define WRITEBUFLEN    ((4096) + 4)    /* 4 extra for alignment play space */
+
+#define VPDSIZE                512
+
+/************************************************************************
+ * Minor device decoding conventions.
+ ************************************************************************
+ *
+ * For Linux, the net and mon devices are handled via "proc", so we
+ * only have to mux the "tty" devices.  Since every PortServer will
+ * have an individual major number, the PortServer number does not
+ * need to be encoded, and in fact, does not need to exist.
+ *
+ */
+
+/*
+ * Port device decoding conventions:
+ *
+ *     Device 00 - 3f        64 dial-in modem devices. (tty)
+ *     Device 40 - 7f        64 dial-out tty devices.  (cu)
+ *     Device 80 - bf        64 dial-out printer devices.
+ *
+ *  IS_PRINT(dev)              This is a printer device.
+ *
+ *  OPEN_CATEGORY(dev)         Specifies the device category.  No two
+ *                             devices of different categories may be open
+ *                             at the same time.
+ *
+ * The following require the category returned by OPEN_CATEGORY().
+ *
+ *  OPEN_WAIT_AVAIL(cat)       Waits on open until the device becomes
+ *                             available.  Fails if NDELAY specified.
+ *
+ *  OPEN_WAIT_CARRIER(cat)     Waits on open if carrier is not present.
+ *                             Succeeds if NDELAY is given.
+ *
+ *  OPEN_FORCES_CARRIER(cat)   Carrier is forced high on open.
+ *
+ */
+
+#define PORT_NUM(dev)                  ((dev) & 0x3f)
+
+#define OPEN_CATEGORY(dev)             ((((dev) & 0x80) & 0x40))
+#define IS_PRINT(dev)                  (((dev) & 0xff) >= 0x80)
+
+#define OPEN_WAIT_AVAIL(cat)           (((cat) & 0x40) == 0x000)
+#define OPEN_WAIT_CARRIER(cat)         (((cat) & 0x40) == 0x000)
+#define OPEN_FORCES_CARRIER(cat)       (((cat) & 0x40) != 0x000)
+
+
+/************************************************************************
+ * Modem signal defines for 16450/16550 compatible FEP.
+ * set in ch_mout, ch_mflow, ch_mlast etc
+ ************************************************************************/
+
+/* TODO : Re-verify that these modem signal definitions are correct */
+
+#define DM_DTR         0x01
+#define DM_RTS         0x02
+#define DM_RTS_TOGGLE  0x04
+
+#define DM_OUT1                0x04
+#define DM_OUT2                0x08
+
+#define DM_CTS         0x10
+#define DM_DSR         0x20
+#define DM_RI          0x40
+#define DM_CD          0x80            /* This is the DCD flag */
+
+
+/************************************************************************
+ * Realport Event Flags.
+ ************************************************************************/
+
+#define EV_OPU         0x0001          /* Ouput paused by client */
+#define EV_OPS         0x0002          /* Output paused by XOFF */
+#define EV_OPX         0x0004          /* Output paused by XXOFF */
+#define EV_OPH         0x0008          /* Output paused by MFLOW */
+#define EV_IPU         0x0010          /* Input paused by client */
+#define EV_IPS         0x0020          /* Input paused by hi/low water */
+#define EV_TXB         0x0040          /* Transmit break pending */
+#define EV_TXI         0x0080          /* Transmit immediate pending */
+#define EV_TXF         0x0100          /* Transmit flow control pending */
+#define EV_RXB         0x0200          /* Break received */
+
+
+/************************************************************************
+ * Realport CFLAGS.
+ ************************************************************************/
+
+#define CF_CS5         0x0000          /* 5 bit characters */
+#define CF_CS6         0x0010          /* 6 bit characters */
+#define CF_CS7         0x0020          /* 7 bit characters */
+#define CF_CS8         0x0030          /* 8 bit characters */
+#define CF_CSIZE       0x0030          /* Character size */
+#define CF_CSTOPB      0x0040          /* Two stop bits */
+#define CF_CREAD       0x0080          /* Enable receiver */
+#define CF_PARENB      0x0100          /* Enable parity */
+#define CF_PARODD      0x0200          /* Odd parity */
+#define CF_HUPCL       0x0400          /* Drop DTR on close */
+
+
+/************************************************************************
+ * Realport XFLAGS.
+ ************************************************************************/
+
+#define XF_XPAR                0x0001          /* Enable Mark/Space Parity */
+#define XF_XMODEM      0x0002          /* Enable in-band modem signalling */
+#define XF_XCASE       0x0004          /* Convert special characters */
+#define XF_XEDATA      0x0008          /* Error data in stream */
+#define XF_XTOSS       0x0010          /* Toss IXANY characters */
+#define XF_XIXON       0x0020          /* xxon/xxoff enable */
+
+
+/************************************************************************
+ * Realport IFLAGS.
+ ************************************************************************/
+
+#define IF_IGNBRK      0x0001          /* Ignore input break */
+#define IF_BRKINT      0x0002          /* Break interrupt */
+#define IF_IGNPAR      0x0004          /* Ignore error characters */
+#define IF_PARMRK      0x0008          /* Error chars marked with 0xff */
+#define IF_INPCK       0x0010          /* Input parity checking enabled */
+#define IF_ISTRIP      0x0020          /* Input chars masked with 0x7F */
+#define IF_IXON                0x0400          /* Output software flow control */
+#define IF_IXANY       0x0800          /* Restart output on any char */
+#define        IF_IXOFF        0x1000          /* Input software flow control */
+#define IF_DOSMODE     0x8000          /* 16450-compatible errors */
+
+
+/************************************************************************
+ * Realport OFLAGS.
+ ************************************************************************/
+
+#define OF_OLCUC       0x0002          /* Map lower to upper case */
+#define OF_ONLCR       0x0004          /* Map NL to CR-NL */
+#define OF_OCRNL       0x0008          /* Map CR to NL */
+#define OF_ONOCR       0x0010          /* No CR output at column 0 */
+#define OF_ONLRET      0x0020          /* Assume NL does NL/CR */
+#define OF_TAB3                0x1800          /* Tabs expand to 8 spaces */
+#define OF_TABDLY      0x1800          /* Tab delay */
+
+/************************************************************************
+ * Unit flag definitions for un_flag.
+ ************************************************************************/
+
+/* These are the DIGI unit flags */
+#define UN_EXCL                0x00010000      /* Exclusive open */
+#define UN_STICKY      0x00020000      /* TTY Settings are now sticky */
+#define UN_BUSY                0x00040000      /* Some work this channel */
+#define UN_PWAIT       0x00080000      /* Printer waiting for terminal */
+#define UN_TIME                0x00100000      /* Waiting on time */
+#define UN_EMPTY       0x00200000      /* Waiting output queue empty */
+#define UN_LOW         0x00400000      /* Waiting output low water */
+#define UN_DIGI_MASK   0x00FF0000      /* Waiting output low water */
+
+/*
+ * Definitions for async_struct (and serial_struct) flags field
+ *
+ * these are the ASYNC flags copied from serial.h
+ *
+ */
+#define UN_HUP_NOTIFY  0x0001 /* Notify getty on hangups and
+                               * closes on the callout port
+                               */
+#define UN_FOURPORT    0x0002  /* Set OU1, OUT2 per AST Fourport settings */
+#define UN_SAK         0x0004  /* Secure Attention Key (Orange book) */
+#define UN_SPLIT_TERMIOS 0x0008 /* Separate termios for dialin/callout */
+
+#define UN_SPD_MASK    0x0030
+#define UN_SPD_HI      0x0010  /* Use 56000 instead of 38400 bps */
+#define UN_SPD_VHI     0x0020  /* Use 115200 instead of 38400 bps */
+#define UN_SPD_CUST    0x0030  /* Use user-specified divisor */
+
+#define UN_SKIP_TEST   0x0040 /* Skip UART test during autoconfiguration */
+#define UN_AUTO_IRQ    0x0080 /* Do automatic IRQ during autoconfiguration */
+
+#define UN_SESSION_LOCKOUT 0x0100 /* Lock out cua opens based on session */
+#define UN_PGRP_LOCKOUT           0x0200 /* Lock out cua opens based on pgrp */
+#define UN_CALLOUT_NOHUP   0x0400 /* Don't do hangups for cua device */
+
+#define UN_FLAGS       0x0FFF  /* Possible legal async flags */
+#define UN_USR_MASK    0x0430  /* Legal flags that non-privileged
+                                * users can set or reset
+                                */
+
+#define UN_INITIALIZED         0x80000000 /* Serial port was initialized */
+#define UN_CALLOUT_ACTIVE      0x40000000 /* Call out device is active */
+#define UN_NORMAL_ACTIVE       0x20000000 /* Normal device is active */
+#define UN_BOOT_AUTOCONF       0x10000000 /* Autoconfigure port on bootup */
+#define UN_CLOSING             0x08000000 /* Serial port is closing */
+#define UN_CTS_FLOW            0x04000000 /* Do CTS flow control */
+#define UN_CHECK_CD            0x02000000 /* i.e., CLOCAL */
+#define UN_SHARE_IRQ           0x01000000 /* for multifunction cards */
+
+
+/************************************************************************
+ * Structure for terminal or printer unit.  struct un_struct
+ *
+ * Note that in some places the code assumes the "tty_t" is placed
+ * first in the structure.
+ ************************************************************************/
+
+struct un_struct {
+       struct tty_struct *un_tty;              /* System TTY struct */
+       struct ch_struct *un_ch;                /* Associated channel */
+
+       ushort     un_open_count;               /* Successful open count */
+       int             un_flag;                /* Unit flags */
+       ushort     un_tbusy;            /* Busy transmit count */
+
+       wait_queue_head_t  un_open_wait;
+       wait_queue_head_t  un_close_wait;
+       ushort  un_type;
+       struct device *un_sysfs;
+};
+
+
+/************************************************************************
+ * Channel State Numbers for ch_state.
+ ************************************************************************/
+
+/*
+ * The ordering is important.
+ *
+ *    state <= CS_WAIT_CANCEL implies the channel is definitely closed.
+ *
+ *    state >= CS_WAIT_FAIL  implies the channel is definitely open.
+ *
+ *    state >= CS_READY implies data is allowed on the channel.
+ */
+
+enum dgrp_ch_state_t {
+       CS_IDLE = 0,        /* Channel is idle */
+       CS_WAIT_OPEN = 1,   /* Waiting for Immediate Open Resp */
+       CS_WAIT_CANCEL = 2, /* Waiting for Per/Incom Cancel Resp */
+       CS_WAIT_FAIL = 3,   /* Waiting for Immed Open Failure */
+       CS_SEND_QUERY = 4,  /* Ready to send Port Query */
+       CS_WAIT_QUERY = 5,  /* Waiting for Port Query Response */
+       CS_READY = 6,       /* Ready to accept commands and data */
+       CS_SEND_CLOSE = 7,  /* Ready to send Close Request */
+       CS_WAIT_CLOSE = 8   /* Waiting for Close Response */
+};
+
+/************************************************************************
+ * Device flag definitions for ch_flag.
+ ************************************************************************/
+
+/*
+ *  Note that the state of the two carrier based flags is key. When
+ *  we check for carrier state transitions, we look at the current
+ *  physical state of the DCD line and compare it with PHYS_CD (which
+ *  was the state the last time we checked), and we also determine
+ *  a new virtual state (composite of the physical state, FORCEDCD,
+ *  CLOCAL, etc.) and compare it with VIRT_CD.
+ *
+ *  VIRTUAL transitions high will have the side effect of waking blocked
+ *  opens.
+ *
+ *  PHYSICAL transitions low will cause hangups to occur _IF_ the virtual
+ *  state is also low. We DON'T want to hangup on a PURE virtual drop.
+ */
+
+#define CH_HANGUP      0x00002         /* Server port ready to close */
+
+#define CH_VIRT_CD     0x00004         /* Carrier was virtually present */
+#define CH_PHYS_CD     0x00008         /* Carrier was physically present */
+
+#define CH_CLOCAL      0x00010         /* CLOCAL set in cflags */
+#define CH_BAUD0       0x00020         /* Baud rate zero hangup */
+
+#define CH_FAST_READ   0x00040         /* Fast reads are enabled */
+#define CH_FAST_WRITE  0x00080         /* Fast writes are enabled */
+
+#define CH_PRON                0x00100         /* Printer on string active */
+#define CH_RX_FLUSH    0x00200         /* Flushing receive data */
+#define CH_LOW         0x00400         /* Thread waiting for LOW water */
+#define CH_EMPTY       0x00800         /* Thread waiting for EMPTY */
+#define CH_DRAIN       0x01000         /* Close is waiting to drain */
+#define CH_INPUT       0x02000         /* Thread waiting for INPUT */
+#define CH_RXSTOP      0x04000         /* Stop output to ldisc */
+#define CH_PARAM       0x08000         /* A parameter was updated */
+#define CH_WAITING_SYNC 0x10000                /* A pending sync was assigned
+                                        * to this port.
+                                        */
+#define CH_PORT_GONE   0x20000         /* Port has disappeared */
+#define CH_TX_BREAK    0x40000         /* TX Break to be sent,
+                                        * but has not yet.
+                                        */
+
+/************************************************************************
+ * Types of Open Requests for ch_otype.
+ ************************************************************************/
+
+#define OTYPE_IMMEDIATE          0             /* Immediate Open */
+#define OTYPE_PERSISTENT  1            /* Persistent Open */
+#define OTYPE_INCOMING   2             /* Incoming Open */
+
+
+/************************************************************************
+ * Request/Response flags.
+ ************************************************************************/
+
+#define RR_SEQUENCE    0x0001          /* Get server RLAST, TIN */
+#define RR_STATUS      0x0002          /* Get server MINT, EINT */
+#define RR_BUFFER      0x0004          /* Get server RSIZE, TSIZE */
+#define RR_CAPABILITY  0x0008          /* Get server port capabilities */
+
+#define RR_TX_FLUSH    0x0040          /* Flush output buffers */
+#define RR_RX_FLUSH    0x0080          /* Flush input buffers */
+
+#define RR_TX_STOP     0x0100          /* Pause output */
+#define RR_RX_STOP     0x0200          /* Pause input */
+#define RR_TX_START    0x0400          /* Start output */
+#define RR_RX_START    0x0800          /* Start input */
+
+#define RR_TX_BREAK    0x1000          /* Send BREAK */
+#define RR_TX_ICHAR    0x2000          /* Send character immediate */
+
+
+/************************************************************************
+ * Channel information structure.   struct ch_struct
+ ************************************************************************/
+
+struct ch_struct {
+       struct digi_struct ch_digi;             /* Digi variables */
+       int     ch_edelay;              /* Digi edelay */
+
+       struct tty_port port;
+       struct un_struct ch_tun;        /* Terminal unit info */
+       struct un_struct ch_pun;        /* Printer unit info */
+
+       struct nd_struct *ch_nd;        /* Node pointer */
+       u8  *ch_tbuf;           /* Local Transmit Buffer */
+       u8  *ch_rbuf;           /* Local Receive Buffer */
+       ulong   ch_cpstime;             /* Printer CPS time */
+       ulong   ch_waketime;            /* Printer wake time */
+
+       ulong   ch_flag;                /* CH_* flags */
+
+       enum dgrp_ch_state_t ch_state;          /* CS_* Protocol state */
+       ushort  ch_send;                /* Bit vector of RR_* requests */
+       ushort  ch_expect;              /* Bit vector of RR_* responses */
+       ushort  ch_wait_carrier;        /* Thread count waiting for carrier */
+       ushort  ch_wait_count[3];       /* Thread count waiting by otype */
+
+       ushort  ch_portnum;             /* Port number */
+       ushort  ch_open_count;          /* Successful open count */
+       ushort  ch_category;            /* Device category */
+       ushort  ch_open_error;          /* Last open error number */
+       ushort  ch_break_time;          /* Pending break request time */
+       ushort  ch_cpsrem;              /* Printer CPS remainder */
+       ushort  ch_ocook;               /* Realport fastcook oflags */
+       ushort  ch_inwait;              /* Thread count in CLIST input */
+
+       ushort  ch_tin;                 /* Local transmit buffer in ptr */
+       ushort  ch_tout;                /* Local transmit buffer out ptr */
+       ushort  ch_s_tin;               /* Realport TIN */
+       ushort  ch_s_tpos;              /* Realport TPOS */
+       ushort  ch_s_tsize;             /* Realport TSIZE */
+       ushort  ch_s_treq;              /* Realport TREQ */
+       ushort  ch_s_elast;             /* Realport ELAST */
+
+       ushort  ch_rin;                 /* Local receive buffer in ptr */
+       ushort  ch_rout;                /* Local receive buffer out ptr */
+       ushort  ch_s_rin;               /* Realport RIN */
+       /* David Fries 7-13-2001, ch_s_rin should be renamed ch_s_rout because
+        * the variable we want to represent is the PortServer's ROUT, which is
+        * the sequence number for the next byte the PortServer will send us.
+        * RIN is the sequence number for the next byte the PortServer will
+        * receive from the uart.  The port server will send data as long as
+        * ROUT is less than RWIN.  What would happen is the port is opened, it
+        * receives data, it gives the value of RIN, we set the RWIN to
+        * RIN+RBUF_MAX-1, it sends us RWIN-ROUT bytes which overflows.  ROUT
+        * is set to zero when the port is opened, so we start at zero and
+        * count up as data is received.
+        */
+       ushort  ch_s_rwin;              /* Realport RWIN */
+       ushort  ch_s_rsize;             /* Realport RSIZE */
+
+       ushort  ch_tmax;                /* Local TMAX */
+       ushort  ch_ttime;               /* Local TTIME */
+       ushort  ch_rmax;                /* Local RMAX */
+       ushort  ch_rtime;               /* Local RTIME */
+       ushort  ch_rlow;                /* Local RLOW */
+       ushort  ch_rhigh;               /* Local RHIGH */
+
+       ushort  ch_s_tmax;              /* Realport TMAX */
+       ushort  ch_s_ttime;             /* Realport TTIME */
+       ushort  ch_s_rmax;              /* Realport RMAX */
+       ushort  ch_s_rtime;             /* Realport RTIME */
+       ushort  ch_s_rlow;              /* Realport RLOW */
+       ushort  ch_s_rhigh;             /* Realport RHIGH */
+
+       ushort  ch_brate;               /* Local baud rate */
+       ushort  ch_cflag;               /* Local tty cflags */
+       ushort  ch_iflag;               /* Local tty iflags */
+       ushort  ch_oflag;               /* Local tty oflags */
+       ushort  ch_xflag;               /* Local tty xflags */
+
+       ushort  ch_s_brate;             /* Realport BRATE */
+       ushort  ch_s_cflag;             /* Realport CFLAG */
+       ushort  ch_s_iflag;             /* Realport IFLAG */
+       ushort  ch_s_oflag;             /* Realport OFLAG */
+       ushort  ch_s_xflag;             /* Realport XFLAG */
+
+       u8      ch_otype;               /* Open request type */
+       u8      ch_pscan_savechar;      /* Last character read by parity scan */
+       u8      ch_pscan_state;         /* PScan State based on last 2 chars */
+       u8      ch_otype_waiting;       /* Type of open pending in server */
+       u8      ch_flush_seq;           /* Receive flush end sequence */
+       u8      ch_s_mlast;             /* Realport MLAST */
+
+       u8      ch_mout;                /* Local MOUT */
+       u8      ch_mflow;               /* Local MFLOW */
+       u8      ch_mctrl;               /* Local MCTRL */
+       u8      ch_xon;                 /* Local XON */
+       u8      ch_xoff;                /* Local XOFF */
+       u8      ch_lnext;               /* Local LNEXT */
+       u8      ch_xxon;                /* Local XXON */
+       u8      ch_xxoff;               /* Local XXOFF */
+
+       u8      ch_s_mout;              /* Realport MOUT */
+       u8      ch_s_mflow;             /* Realport MFLOW */
+       u8      ch_s_mctrl;             /* Realport MCTRL */
+       u8      ch_s_xon;               /* Realport XON */
+       u8      ch_s_xoff;              /* Realport XOFF */
+       u8      ch_s_lnext;             /* Realport LNEXT */
+       u8      ch_s_xxon;              /* Realport XXON */
+       u8      ch_s_xxoff;             /* Realport XXOFF */
+
+       wait_queue_head_t ch_flag_wait; /* Wait queue for ch_flag changes */
+       wait_queue_head_t ch_sleep;     /* Wait queue for my_sleep() */
+
+       int     ch_custom_speed;        /* Realport custom speed */
+       int     ch_txcount;             /* Running TX count */
+       int     ch_rxcount;             /* Running RX count */
+};
+
+
+/************************************************************************
+ * Node State definitions.
+ ************************************************************************/
+
+enum dgrp_nd_state_t {
+       NS_CLOSED = 0,     /* Network device is closed */
+       NS_IDLE = 1,       /* Network connection inactive */
+       NS_SEND_QUERY = 2, /* Send server query */
+       NS_WAIT_QUERY = 3, /* Wait for query response */
+       NS_READY = 4,      /* Network ready */
+       NS_SEND_ERROR = 5  /* Must send error hangup */
+};
+
+#define ND_STATE_STR(x) \
+       ((x) == NS_CLOSED     ? "CLOSED"     : \
+       ((x) == NS_IDLE       ? "IDLE"       : \
+       ((x) == NS_SEND_QUERY ? "SEND_QUERY" : \
+       ((x) == NS_WAIT_QUERY ? "WAIT_QUERY" : \
+       ((x) == NS_READY      ? "READY"      : \
+       ((x) == NS_SEND_ERROR ? "SEND_ERROR" : "UNKNOWN"))))))
+
+/************************************************************************
+ * Node Flag definitions.
+ ************************************************************************/
+
+#define ND_SELECT      0x0001          /* Multiple net read selects */
+#define ND_DEB_WAIT    0x0002          /* Debug Device waiting */
+
+
+/************************************************************************
+ * Monitoring flag definitions.
+ ************************************************************************/
+
+#define MON_WAIT_DATA  0x0001          /* Waiting for buffer data */
+#define MON_WAIT_SPACE 0x0002          /* Waiting for buffer space */
+
+/************************************************************************
+ * DPA flag definitions.
+ ************************************************************************/
+
+#define DPA_WAIT_DATA  0x0001          /* Waiting for buffer data */
+#define DPA_WAIT_SPACE 0x0002          /* Waiting for buffer space */
+
+
+/************************************************************************
+ * Definitions taken from Realport Dump.
+ ************************************************************************/
+
+#define RPDUMP_MAGIC   "Digi-RealPort-1.0"
+
+#define RPDUMP_MESSAGE 0xE2            /* Descriptive message */
+#define RPDUMP_RESET   0xE7            /* Connection reset */
+#define RPDUMP_CLIENT  0xE8            /* Client data */
+#define RPDUMP_SERVER  0xE9            /* Server data */
+
+
+/************************************************************************
+ * Node request/response definitions.
+ ************************************************************************/
+
+#define NR_ECHO                0x0001          /* Server echo packet */
+#define NR_IDENT       0x0002          /* Server Product ID */
+#define NR_CAPABILITY  0x0004          /* Server Capabilties */
+#define NR_VPD         0x0008          /* Server VPD, if any */
+#define NR_PASSWORD    0x0010          /* Server Password */
+
+/************************************************************************
+ * Registration status of the node's Linux struct tty_driver structures.
+ ************************************************************************/
+#define SERIAL_TTDRV_REG   0x0001     /* nd_serial_ttdriver registered */
+#define CALLOUT_TTDRV_REG  0x0002     /* nd_callout_ttdriver registered */
+#define XPRINT_TTDRV_REG   0x0004     /* nd_xprint_ttdriver registered */
+
+
+/************************************************************************
+ * Node structure.  There exists one of these for each associated
+ * realport server.
+ ************************************************************************/
+
+struct nd_struct {
+       struct list_head        list;
+       long          nd_major;            /* Node's major number           */
+       long          nd_ID;               /* Node's ID code                */
+
+       char          nd_serial_name[50];   /* "tty_dgrp_<id>_" + null      */
+       char          nd_callout_name[50];  /* "cu_dgrp_<id>_" + null       */
+       char          nd_xprint_name[50];   /* "pr_dgrp_<id>_" + null       */
+
+       char         password[16];        /* Password for server, if needed */
+       int          nd_tty_ref_cnt;      /* Linux tty reference count     */
+
+       struct proc_dir_entry *nd_net_de; /* Dir entry for /proc/dgrp/net  */
+       struct proc_dir_entry *nd_mon_de; /* Dir entry for /proc/dgrp/mon  */
+       struct proc_dir_entry *nd_ports_de; /* Dir entry for /proc/dgrp/ports*/
+       struct proc_dir_entry *nd_dpa_de; /* Dir entry for /proc/dgrp/dpa  */
+
+       spinlock_t nd_lock;               /* General node lock             */
+
+       struct semaphore nd_net_semaphore; /* Net read/write lock           */
+       struct semaphore nd_mon_semaphore; /* Monitor buffer lock           */
+       spinlock_t nd_dpa_lock;         /* DPA buffer lock           */
+
+       enum dgrp_nd_state_t nd_state;    /* NS_* network state */
+       int           nd_chan_count;       /* # active channels             */
+       int           nd_flag;             /* Node flags                    */
+       int           nd_send;             /* Responses to send             */
+       int           nd_expect;           /* Responses we expect           */
+
+       u8       *nd_iobuf;            /* Network R/W Buffer            */
+       wait_queue_head_t nd_tx_waitq;    /* Network select wait queue     */
+
+       u8       *nd_inputbuf;         /* Input Buffer                  */
+       u8       *nd_inputflagbuf;     /* Input Flags Buffer            */
+
+       int           nd_tx_deposit;       /* Accumulated transmit deposits */
+       int           nd_tx_charge;        /* Accumulated transmit charges  */
+       int           nd_tx_credit;        /* Current TX credit             */
+       int           nd_tx_ready;         /* Ready to transmit             */
+       int           nd_tx_work;          /* TX work waiting               */
+       ulong        nd_tx_time;          /* Last transmit time            */
+       ulong        nd_poll_time;        /* Next scheduled poll time      */
+
+       int           nd_delay;            /* Current TX delay              */
+       int           nd_rate;             /* Current TX rate               */
+       struct link_struct nd_link;             /* Link speed params.            */
+
+       int           nd_seq_in;           /* TX seq in ptr                 */
+       int           nd_seq_out;          /* TX seq out ptr                */
+       int           nd_unack;            /* Unacknowledged byte count     */
+       int           nd_remain;           /* Remaining receive bytes       */
+       int           nd_tx_module;        /* Current TX module #           */
+       int           nd_rx_module;        /* Current RX module #           */
+       char         *nd_error;            /* Protocol error message        */
+
+       int           nd_write_count;      /* drp_write() call count        */
+       int           nd_read_count;       /* drp_read() count              */
+       int           nd_send_count;       /* TCP message sent              */
+       int           nd_tx_byte;          /* Transmit byte count           */
+       int           nd_rx_byte;          /* Receive byte count            */
+
+       ulong        nd_mon_lbolt;       /* Monitor start time             */
+       int           nd_mon_flag;        /* Monitor flags                  */
+       int           nd_mon_in;          /* Monitor in pointer             */
+       int           nd_mon_out;         /* Monitor out pointer            */
+       wait_queue_head_t nd_mon_wqueue;  /* Monitor wait queue (on flags)  */
+       u8       *nd_mon_buf;         /* Monitor buffer                 */
+
+       ulong        nd_dpa_lbolt;      /* DPA start time             */
+       int          nd_dpa_flag;       /* DPA flags                  */
+       int          nd_dpa_in;         /* DPA in pointer             */
+       int          nd_dpa_out;        /* DPA out pointer            */
+       wait_queue_head_t nd_dpa_wqueue; /* DPA wait queue (on flags)  */
+       u8        *nd_dpa_buf;  /* DPA buffer                 */
+
+       uint         nd_dpa_debug;
+       uint         nd_dpa_port;
+
+       wait_queue_head_t nd_seq_wque[SEQ_MAX];   /* TX thread wait queues */
+       u8        nd_seq_wait[SEQ_MAX];   /* Transmit thread wait count */
+
+       ushort       nd_seq_size[SEQ_MAX];   /* Transmit seq packet size   */
+       ulong        nd_seq_time[SEQ_MAX];   /* Transmit seq packet time   */
+
+       ushort       nd_hw_ver;           /* HW version returned from PS   */
+       ushort       nd_sw_ver;           /* SW version returned from PS   */
+       uint         nd_hw_id;            /* HW ID returned from PS        */
+       u8        nd_ps_desc[MAX_DESC_LEN+1];  /* Description from PS   */
+       uint         nd_vpd_len;                /* VPD len, if any */
+       u8           nd_vpd[VPDSIZE];           /* VPD, if any */
+
+       ulong        nd_ttdriver_flags;   /* Registration status            */
+       struct tty_driver *nd_serial_ttdriver;  /* Linux TTYDRIVER structure */
+       struct tty_driver *nd_callout_ttdriver; /* Linux TTYDRIVER structure */
+       struct tty_driver *nd_xprint_ttdriver;  /* Linux TTYDRIVER structure */
+
+       u8           *nd_writebuf;              /* Used to cache data read
+                                                * from user
+                                                */
+       struct ch_struct nd_chan[CHAN_MAX];  /* Channel array               */
+       struct device *nd_class_dev;    /* Hang our sysfs stuff off of here */
+};
+
+#endif /* __DRP_H */