diff options
author | Bill Pemberton <wfp5p@virginia.edu> | 2012-09-20 16:55:27 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-09-21 09:04:18 -0700 |
commit | 0b52b74972712479ca285c04452db0d4b9025f80 (patch) | |
tree | 7e4c77510c7e420dcf672b931d0359cce68755a0 /drivers/staging/dgrp | |
parent | 5de69349ed4c10f43fa6dc6617051d94eee04e6c (diff) | |
download | op-kernel-dev-0b52b74972712479ca285c04452db0d4b9025f80.zip op-kernel-dev-0b52b74972712479ca285c04452db0d4b9025f80.tar.gz |
staging: Add dgrp driver for Digi Realport devices
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>
Diffstat (limited to 'drivers/staging/dgrp')
-rw-r--r-- | drivers/staging/dgrp/Kconfig | 9 | ||||
-rw-r--r-- | drivers/staging/dgrp/Makefile | 12 | ||||
-rw-r--r-- | drivers/staging/dgrp/README | 2 | ||||
-rw-r--r-- | drivers/staging/dgrp/TODO | 7 | ||||
-rw-r--r-- | drivers/staging/dgrp/dgrp_common.c | 200 | ||||
-rw-r--r-- | drivers/staging/dgrp/dgrp_common.h | 208 | ||||
-rw-r--r-- | drivers/staging/dgrp/dgrp_dpa_ops.c | 556 | ||||
-rw-r--r-- | drivers/staging/dgrp/dgrp_driver.c | 110 | ||||
-rw-r--r-- | drivers/staging/dgrp/dgrp_mon_ops.c | 346 | ||||
-rw-r--r-- | drivers/staging/dgrp/dgrp_net_ops.c | 3731 | ||||
-rw-r--r-- | drivers/staging/dgrp/dgrp_ports_ops.c | 170 | ||||
-rw-r--r-- | drivers/staging/dgrp/dgrp_specproc.c | 821 | ||||
-rw-r--r-- | drivers/staging/dgrp/dgrp_sysfs.c | 555 | ||||
-rw-r--r-- | drivers/staging/dgrp/dgrp_tty.c | 3331 | ||||
-rw-r--r-- | drivers/staging/dgrp/digirp.h | 129 | ||||
-rw-r--r-- | drivers/staging/dgrp/drp.h | 693 |
16 files changed, 10880 insertions, 0 deletions
diff --git a/drivers/staging/dgrp/Kconfig b/drivers/staging/dgrp/Kconfig new file mode 100644 index 0000000..39f4bb6 --- /dev/null +++ b/drivers/staging/dgrp/Kconfig @@ -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 index 0000000..d9c3b88 --- /dev/null +++ b/drivers/staging/dgrp/Makefile @@ -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 index 0000000..1d8aaee --- /dev/null +++ b/drivers/staging/dgrp/README @@ -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 index 0000000..63341ad --- /dev/null +++ b/drivers/staging/dgrp/TODO @@ -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 index 0000000..fce74e7 --- /dev/null +++ b/drivers/staging/dgrp/dgrp_common.c @@ -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 index 0000000..05ff338 --- /dev/null +++ b/drivers/staging/dgrp/dgrp_common.h @@ -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 index 0000000..49e6709 --- /dev/null +++ b/drivers/staging/dgrp/dgrp_dpa_ops.c @@ -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 index 0000000..6e4a0eb --- /dev/null +++ b/drivers/staging/dgrp/dgrp_driver.c @@ -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 index 0000000..268dcb9 --- /dev/null +++ b/drivers/staging/dgrp/dgrp_mon_ops.c @@ -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 index 0000000..d9d6b67 --- /dev/null +++ b/drivers/staging/dgrp/dgrp_net_ops.c @@ -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 index 0000000..cd1fc20 --- /dev/null +++ b/drivers/staging/dgrp/dgrp_ports_ops.c @@ -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 index 0000000..259d23a --- /dev/null +++ b/drivers/staging/dgrp/dgrp_specproc.c @@ -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 index 0000000..e5a3c88 --- /dev/null +++ b/drivers/staging/dgrp/dgrp_sysfs.c @@ -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 index 0000000..7d7de87 --- /dev/null +++ b/drivers/staging/dgrp/dgrp_tty.c @@ -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 index 0000000..33c1394 --- /dev/null +++ b/drivers/staging/dgrp/digirp.h @@ -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 index 0000000..84a1e7b --- /dev/null +++ b/drivers/staging/dgrp/drp.h @@ -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 */ |