lkml.org 
[lkml]   [2020]   [Jul]   [14]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
From
Subject[PATCH] tty: Add MOXA NPort Real TTY Driver
Date
This driver supports tty functions for all of MOXA's NPort series
with v5.0. Using this driver, host part can use tty to connect NPort
device server by ethernet.

The following Moxa products are supported:
* CN2600 Series
* CN2500 Series
* NPort DE Series
* NPort 5000A-M12 Series
* NPort 5100 Series
* NPort 5200 Series
* NPort 5400 Series
* NPort 5600 Desktop Series
* NPort 5600 Rackmount Series
* NPort Wireless Series
* NPort IA5000 Series
* NPort 6000 Series
* NPort S8000 Series
* NPort S8455I Series
* NPort S9000 Series
* NE-4100 Series
* MiiNePort Series

Signed-off-by: Johnson Chen <johnsonch.chen@moxa.com>
Signed-off-by: Jason Chen <jason.chen@moxa.com>
Signed-off-by: Danny Lin <danny.lin@moxa.com>
Signed-off-by: Victor Yu <victor.yu@moxa.com>
---
drivers/tty/Kconfig | 11 +
drivers/tty/Makefile | 1 +
drivers/tty/npreal2.c | 3042 +++++++++++++++++++++++++++++++++++++++++
drivers/tty/npreal2.h | 140 ++
4 files changed, 3194 insertions(+)
create mode 100644 drivers/tty/npreal2.c
create mode 100644 drivers/tty/npreal2.h

diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig
index 93fd984eb2f5..79b545269b71 100644
--- a/drivers/tty/Kconfig
+++ b/drivers/tty/Kconfig
@@ -259,6 +259,17 @@ config MOXA_SMARTIO
This driver can also be built as a module. The module will be called
mxser. If you want to do that, say M here.

+config MOXA_NPORT_REAL_TTY
+ tristate "Moxa NPort Real TTY support v5.0"
+ help
+ Say Y here if you have a Moxa NPort serial device server.
+
+ The purpose of this driver is to map NPort serial port to host tty
+ port. Using this driver, you can use NPort serial port as local tty port.
+
+ This driver can also be built as a module. The module will be called
+ npreal2 by setting M.
+
config SYNCLINK
tristate "Microgate SyncLink card support"
depends on SERIAL_NONSTANDARD && PCI && ISA_DMA_API
diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile
index 020b1cd9294f..6d07985d6962 100644
--- a/drivers/tty/Makefile
+++ b/drivers/tty/Makefile
@@ -24,6 +24,7 @@ obj-$(CONFIG_CYCLADES) += cyclades.o
obj-$(CONFIG_ISI) += isicom.o
obj-$(CONFIG_MOXA_INTELLIO) += moxa.o
obj-$(CONFIG_MOXA_SMARTIO) += mxser.o
+obj-$(CONFIG_MOXA_NPORT_REAL_TTY) += npreal2.o
obj-$(CONFIG_NOZOMI) += nozomi.o
obj-$(CONFIG_NULL_TTY) += ttynull.o
obj-$(CONFIG_ROCKETPORT) += rocket.o
diff --git a/drivers/tty/npreal2.c b/drivers/tty/npreal2.c
new file mode 100644
index 000000000000..65c773420755
--- /dev/null
+++ b/drivers/tty/npreal2.c
@@ -0,0 +1,3042 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * npreal2.c -- MOXA NPort Server family Real TTY driver.
+ *
+ * Copyright (c) 1999-2020 Moxa Technologies (support@moxa.com)
+ *
+ * Supports the following Moxa Product:
+ * CN2600 Series
+ * CN2500 Series
+ * NPort DE Series
+ * NPort 5000A-M12 Series
+ * NPort 5100 Series
+ * NPort 5200 Series
+ * NPort 5400 Series
+ * NPort 5600 Desktop Series
+ * NPort 5600 Rackmount Series
+ * NPort Wireless Series
+ * NPort IA5000 Series
+ * NPort 6000 Series
+ * NPort S8000 Series
+ * NPort S8455I Series
+ * NPort S9000 Series
+ * NE-4100 Series
+ * MiiNePort Series
+ */
+
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/fcntl.h>
+#include <linux/version.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/major.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/ptrace.h>
+#include <linux/poll.h>
+#include <linux/proc_fs.h>
+#include <linux/uaccess.h>
+#include <linux/serial.h>
+#include <linux/serial_reg.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/timer.h>
+#include "npreal2.h"
+
+static int ttymajor = NPREALMAJOR;
+static int verbose = 1;
+
+MODULE_AUTHOR("<support@moxa.com>");
+MODULE_DESCRIPTION("MOXA Async/NPort Server Family Real TTY Driver");
+module_param(ttymajor, int, 0);
+module_param(verbose, int, 0644);
+MODULE_VERSION(NPREAL_VERSION);
+MODULE_LICENSE("GPL");
+
+struct server_setting_struct {
+ int32_t server_type;
+ int32_t disable_fifo;
+};
+
+struct npreal_struct {
+ struct tty_port ttyPort;
+ struct work_struct tqueue;
+ struct work_struct process_flip_tqueue;
+ struct ktermios normal_termios;
+ struct ktermios callout_termios;
+ /* kernel counters for the 4 input interrupts */
+ struct async_icount icount;
+ struct semaphore rx_semaphore;
+ struct nd_struct *net_node;
+ struct tty_struct *tty;
+ struct pid *session;
+ struct pid *pgrp;
+ wait_queue_head_t open_wait;
+ wait_queue_head_t close_wait;
+ wait_queue_head_t delta_msr_wait;
+ unsigned long baud_base;
+ unsigned long event;
+ unsigned short closing_wait;
+ int port;
+ int flags;
+ int type; /* UART type */
+ int xmit_fifo_size;
+ int custom_divisor;
+ int x_char; /* xon/xoff character */
+ int close_delay;
+ int modem_control; /* Modem control register */
+ int modem_status; /* Line status */
+ int count; /* # of fd on device */
+ int xmit_head;
+ int xmit_tail;
+ int xmit_cnt;
+ unsigned char *xmit_buf;
+
+ /*
+ * We use spin_lock_irqsave instead of semaphonre here.
+ * Reason: When we use pppd to dialout via Real TTY driver,
+ * some driver functions, such as npreal_write(), would be
+ * invoked under interrpute mode which causes warning in
+ * down/up tx_semaphore.
+ */
+ spinlock_t tx_lock;
+};
+
+struct nd_struct {
+ struct semaphore cmd_semaphore;
+ struct proc_dir_entry *node_entry;
+ struct npreal_struct *tty_node;
+ struct semaphore semaphore;
+ wait_queue_head_t initialize_wait;
+ wait_queue_head_t select_in_wait;
+ wait_queue_head_t select_out_wait;
+ wait_queue_head_t select_ex_wait;
+ wait_queue_head_t cmd_rsp_wait;
+ int32_t server_type;
+ int do_session_recovery_len;
+ int cmd_rsp_flag;
+ int tx_ready;
+ int rx_ready;
+ int cmd_ready;
+ int wait_oqueue_responsed;
+ int oqueue;
+ int rsp_length;
+ unsigned long flag;
+ unsigned char cmd_buffer[84];
+ unsigned char rsp_buffer[84];
+};
+
+static const struct proc_ops npreal_net_fops;
+static const struct tty_operations mpvar_ops;
+static struct proc_dir_entry *npvar_proc_root;
+static struct tty_driver *npvar_sdriver;
+static struct npreal_struct *npvar_table;
+static struct nd_struct *npvar_net_nodes;
+
+static void npreal_do_softint(struct work_struct *work)
+{
+ struct npreal_struct *info = container_of(work, struct npreal_struct, tqueue);
+ struct tty_struct *tty;
+
+ if (!info)
+ return;
+
+ tty = info->tty;
+ if (tty) {
+ if (test_and_clear_bit(NPREAL_EVENT_TXLOW, &info->event)) {
+ if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+ tty->ldisc->ops->write_wakeup)
+ (tty->ldisc->ops->write_wakeup)(tty);
+ wake_up_interruptible(&tty->write_wait);
+ }
+
+ if (test_and_clear_bit(NPREAL_EVENT_HANGUP, &info->event)) {
+ /* Do it when entering npreal_hangup() */
+ tty_hangup(tty);
+ }
+ }
+}
+
+/**
+ * npreal_flush_to_ldisc() - Read data from tty device to line discipline
+ * @tty: pointer for struct tty_struct
+ * @filp: pointer for struct file
+ *
+ * This routine is called out of the software interrupt to flush data
+ * from the flip buffer to the line discipline.
+ *
+ */
+
+static void npreal_flush_to_ldisc(struct work_struct *work)
+{
+ struct npreal_struct *info = container_of(work, struct npreal_struct, process_flip_tqueue);
+ struct tty_struct *tty;
+ struct tty_port *port;
+
+ if (info == NULL)
+ return;
+
+ tty = info->tty;
+ if (tty == NULL)
+ return;
+
+ port = tty->port;
+ tty_flip_buffer_push(port);
+}
+
+static inline void npreal_check_modem_status(struct npreal_struct *info, int status)
+{
+ int is_dcd_changed = 0;
+
+ if ((info->modem_status & UART_MSR_DSR) != (status & UART_MSR_DSR))
+ info->icount.dsr++;
+ if ((info->modem_status & UART_MSR_DCD) != (status & UART_MSR_DCD)) {
+ info->icount.dcd++;
+ is_dcd_changed = 1;
+ }
+
+ if ((info->modem_status & UART_MSR_CTS) != (status & UART_MSR_CTS))
+ info->icount.cts++;
+
+ info->modem_status = status;
+ wake_up_interruptible(&info->delta_msr_wait);
+
+ if ((info->flags & ASYNC_CHECK_CD) && (is_dcd_changed)) {
+ if (status & UART_MSR_DCD) {
+ wake_up_interruptible(&info->open_wait);
+ } else {
+ set_bit(NPREAL_EVENT_HANGUP, &info->event);
+ schedule_work(&info->tqueue);
+ }
+ }
+}
+
+static int npreal_wait_and_set_command(struct nd_struct *nd, char command_set, char command)
+{
+ unsigned long et;
+
+ if ((command_set != NPREAL_LOCAL_COMMAND_SET) &&
+ ((nd->flag & NPREAL_NET_DO_SESSION_RECOVERY) ||
+ (nd->flag & NPREAL_NET_NODE_DISCONNECTED))) {
+
+ if (nd->flag & NPREAL_NET_DO_SESSION_RECOVERY)
+ return -EAGAIN;
+
+ return -EIO;
+ }
+
+ down(&nd->cmd_semaphore);
+ nd->cmd_rsp_flag = 0;
+ up(&nd->cmd_semaphore);
+
+ et = jiffies + NPREAL_CMD_TIMEOUT;
+ while (1) {
+ down(&nd->cmd_semaphore);
+ if (!(nd->cmd_buffer[0] == 0 || ((jiffies - et >= 0) ||
+ signal_pending(current)))) {
+ up(&nd->cmd_semaphore);
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(1);
+ } else {
+ nd->cmd_buffer[0] = command_set;
+ nd->cmd_buffer[1] = command;
+ up(&nd->cmd_semaphore);
+ return 0;
+ }
+ }
+}
+
+static int npreal_wait_command_completed(struct nd_struct *nd, char command_set, char command,
+ long timeout, char *rsp_buf, int *rsp_len)
+{
+ long st = 0, tmp = 0;
+
+ if ((command_set != NPREAL_LOCAL_COMMAND_SET) &&
+ ((nd->flag & NPREAL_NET_DO_SESSION_RECOVERY) ||
+ (nd->flag & NPREAL_NET_NODE_DISCONNECTED))) {
+
+ if (nd->flag & NPREAL_NET_DO_SESSION_RECOVERY)
+ return -EAGAIN;
+ else
+ return -EIO;
+ }
+
+ if (*rsp_len <= 0)
+ return -EIO;
+
+ while (1) {
+ down(&nd->cmd_semaphore);
+
+ if ((nd->rsp_length) && (nd->rsp_buffer[0] == command_set) &&
+ (nd->rsp_buffer[1] == command)) {
+ if (nd->rsp_length > *rsp_len)
+ return -1;
+
+ *rsp_len = nd->rsp_length;
+ memcpy(rsp_buf, nd->rsp_buffer, *rsp_len);
+ nd->rsp_length = 0;
+ up(&nd->cmd_semaphore);
+ return 0;
+
+ } else if (timeout > 0) {
+ up(&nd->cmd_semaphore);
+ if (signal_pending(current))
+ return -EIO;
+
+ st = jiffies;
+ if (wait_event_interruptible_timeout(nd->cmd_rsp_wait,
+ nd->cmd_rsp_flag == 1, timeout) != 0) {
+ down(&nd->cmd_semaphore);
+ nd->cmd_rsp_flag = 0;
+ up(&nd->cmd_semaphore);
+ }
+
+ tmp = abs((long)jiffies - (long)st);
+
+ if (tmp >= timeout)
+ timeout = 0;
+ else
+ timeout -= tmp;
+ } else {
+ up(&nd->cmd_semaphore);
+ return -ETIME;
+ }
+ }
+}
+
+static int npreal_set_unused_command_done(struct nd_struct *nd, char *rsp_buf, int *rsp_len)
+{
+ npreal_wait_and_set_command(nd, NPREAL_LOCAL_COMMAND_SET, LOCAL_CMD_TTY_UNUSED);
+ nd->cmd_buffer[2] = 0;
+ nd->cmd_ready = 1;
+ smp_mb(); /* use smp_mb() with waitqueue_active() */
+ /* used waitqueue_active() is safe because smp_mb() is used */
+ if (waitqueue_active(&nd->select_ex_wait))
+ wake_up_interruptible(&nd->select_ex_wait);
+
+ return npreal_wait_command_completed(nd, NPREAL_LOCAL_COMMAND_SET, LOCAL_CMD_TTY_UNUSED,
+ NPREAL_CMD_TIMEOUT, rsp_buf, rsp_len);
+}
+
+static int npreal_set_used_command_done(struct nd_struct *nd, char *rsp_buf, int *rsp_len)
+{
+ nd->cmd_buffer[0] = 0;
+ npreal_wait_and_set_command(nd, NPREAL_LOCAL_COMMAND_SET, LOCAL_CMD_TTY_USED);
+ nd->cmd_buffer[2] = 0;
+ nd->cmd_ready = 1;
+ smp_mb(); /* use smp_mb() with waitqueue_active() */
+ /* used waitqueue_active() is safe because smp_mb() is used */
+ if (waitqueue_active(&nd->select_ex_wait))
+ wake_up_interruptible(&nd->select_ex_wait);
+
+ return npreal_wait_command_completed(nd, NPREAL_LOCAL_COMMAND_SET, LOCAL_CMD_TTY_USED,
+ NPREAL_CMD_TIMEOUT, rsp_buf, rsp_len);
+}
+
+static int npreal_set_tx_fifo_command_done(struct npreal_struct *info, struct nd_struct *nd,
+ char *rsp_buf, int *rsp_len)
+{
+ int ret;
+
+ ret = npreal_wait_and_set_command(nd, NPREAL_ASPP_COMMAND_SET, ASPP_CMD_TX_FIFO);
+ if (ret < 0)
+ return ret;
+
+ nd->cmd_buffer[2] = 1;
+ nd->cmd_buffer[3] = info->xmit_fifo_size;
+ nd->cmd_ready = 1;
+ smp_mb(); /* use smp_mb() with waitqueue_active() */
+ /* used waitqueue_active() is safe because smp_mb() is used */
+ if (waitqueue_active(&nd->select_ex_wait))
+ wake_up_interruptible(&nd->select_ex_wait);
+
+ *rsp_len = RSP_BUFFER_SIZE;
+ ret = npreal_wait_command_completed(nd, NPREAL_ASPP_COMMAND_SET, ASPP_CMD_TX_FIFO,
+ NPREAL_CMD_TIMEOUT, rsp_buf, rsp_len);
+ if (ret)
+ return -EIO;
+
+ return 0;
+}
+
+static int npreal_set_port_command_done(struct npreal_struct *info, struct nd_struct *nd,
+ struct ktermios *termio, char *rsp_buf, int *rsp_len,
+ int32_t mode, int32_t baud, int baudIndex)
+{
+ int ret;
+
+ ret = npreal_wait_and_set_command(nd, NPREAL_ASPP_COMMAND_SET, ASPP_CMD_PORT_INIT);
+ if (ret < 0)
+ return ret;
+
+ nd->cmd_buffer[2] = 8;
+ nd->cmd_buffer[3] = baudIndex;
+ nd->cmd_buffer[4] = mode;
+
+ if (info->modem_control & UART_MCR_DTR)
+ nd->cmd_buffer[5] = 1;
+ else
+ nd->cmd_buffer[5] = 0;
+
+ if (info->modem_control & UART_MCR_RTS)
+ nd->cmd_buffer[6] = 1;
+ else
+ nd->cmd_buffer[6] = 0;
+
+ if (termio->c_cflag & CRTSCTS) {
+ nd->cmd_buffer[7] = 1;
+ nd->cmd_buffer[8] = 1;
+ } else {
+ nd->cmd_buffer[7] = 0;
+ nd->cmd_buffer[8] = 0;
+ }
+
+ if (termio->c_iflag & IXON)
+ nd->cmd_buffer[9] = 1;
+ else
+ nd->cmd_buffer[9] = 0;
+
+ if (termio->c_iflag & IXOFF)
+ nd->cmd_buffer[10] = 1;
+ else
+ nd->cmd_buffer[10] = 0;
+
+ nd->cmd_ready = 1;
+ smp_mb(); /* use smp_mb() with waitqueue_active() */
+ /* used waitqueue_active() is safe because smp_mb() is used */
+ if (waitqueue_active(&nd->select_ex_wait))
+ wake_up_interruptible(&nd->select_ex_wait);
+
+ ret = npreal_wait_command_completed(nd, NPREAL_ASPP_COMMAND_SET, ASPP_CMD_PORT_INIT,
+ NPREAL_CMD_TIMEOUT, rsp_buf, rsp_len);
+ if (ret)
+ return -EIO;
+
+ if ((*rsp_len != 6) || (rsp_buf[2] != 3))
+ return -EIO;
+
+ return 0;
+}
+
+static int npreal_set_baud_command_done(struct npreal_struct *info, struct nd_struct *nd,
+ char *rsp_buf, int *rsp_len, int baud)
+{
+ int ret;
+
+ ret = npreal_wait_and_set_command(nd, NPREAL_ASPP_COMMAND_SET, ASPP_CMD_SETBAUD);
+ if (ret < 0)
+ return ret;
+
+ if (((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST) && info->custom_divisor)
+ baud = info->baud_base / info->custom_divisor;
+
+ nd->cmd_buffer[2] = 4;
+ memcpy(&nd->cmd_buffer[3], &baud, 4);
+ nd->cmd_ready = 1;
+ smp_mb(); /* use smp_mb() with waitqueue_active() */
+ /* used waitqueue_active() is safe because smp_mb() is used */
+ if (waitqueue_active(&nd->select_ex_wait))
+ wake_up_interruptible(&nd->select_ex_wait);
+
+ *rsp_len = RSP_BUFFER_SIZE;
+ ret = npreal_wait_command_completed(nd, NPREAL_ASPP_COMMAND_SET, ASPP_CMD_SETBAUD,
+ NPREAL_CMD_TIMEOUT, rsp_buf, rsp_len);
+ if (ret)
+ return -EIO;
+
+ if ((*rsp_len != 4) || (rsp_buf[2] != 'O') || (rsp_buf[3] != 'K'))
+ return -EIO;
+
+ return 0;
+}
+
+static int npreal_set_xonxoff_command_done(struct npreal_struct *info, struct ktermios *termio,
+ struct nd_struct *nd, char *rsp_buf, int *rsp_len)
+{
+ int ret;
+
+ ret = npreal_wait_and_set_command(nd, NPREAL_ASPP_COMMAND_SET, ASPP_CMD_XONXOFF);
+ if (ret)
+ return ret;
+
+ nd->cmd_buffer[2] = 2;
+ nd->cmd_buffer[3] = termio->c_cc[VSTART];
+ nd->cmd_buffer[4] = termio->c_cc[VSTOP];
+ nd->cmd_ready = 1;
+ smp_mb(); /* use smp_mb() with waitqueue_active() */
+ /* used waitqueue_active() is safe because smp_mb() is used */
+ if (waitqueue_active(&nd->select_ex_wait))
+ wake_up_interruptible(&nd->select_ex_wait);
+
+ ret = npreal_wait_command_completed(nd, NPREAL_ASPP_COMMAND_SET, ASPP_CMD_XONXOFF,
+ NPREAL_CMD_TIMEOUT, rsp_buf, rsp_len);
+ if (ret)
+ return -EIO;
+
+ if ((*rsp_len != 4) || (rsp_buf[2] != 'O') || (rsp_buf[3] != 'K'))
+ return -EIO;
+
+ return 0;
+}
+
+static int npreal_set_generic_command_done(struct npreal_struct *info, int cmd)
+{
+ struct nd_struct *nd;
+ char rsp_buffer[RSP_BUFFER_SIZE];
+ int rsp_length = RSP_BUFFER_SIZE;
+
+ nd = info->net_node;
+ if (!nd)
+ return -EIO;
+
+ if (npreal_wait_and_set_command(nd, NPREAL_ASPP_COMMAND_SET, cmd) < 0)
+ return -EIO;
+
+ nd->cmd_buffer[2] = 0;
+ nd->cmd_ready = 1;
+ smp_mb(); /* use smp_mb() with waitqueue_active() */
+ /* used waitqueue_active() is safe because smp_mb() is used */
+ if (waitqueue_active(&nd->select_ex_wait))
+ wake_up_interruptible(&nd->select_ex_wait);
+ if (npreal_wait_command_completed(nd, NPREAL_ASPP_COMMAND_SET, cmd, NPREAL_CMD_TIMEOUT,
+ rsp_buffer, &rsp_length))
+ return -EIO;
+
+ if ((rsp_length != 4) || (rsp_buffer[2] != 'O') || (rsp_buffer[3] != 'K'))
+ return -EIO;
+
+ return 0;
+}
+
+static void npreal_disconnect(struct nd_struct *nd, char *buf, int *size)
+{
+ int i = 0;
+
+ nd->cmd_buffer[0] = 0;
+ for (i = 0; i < 2; i++) {
+ if (npreal_set_unused_command_done(nd, buf, size) == 0)
+ break;
+ }
+}
+
+static int npreal_linectrl(struct nd_struct *nd, int modem_control)
+{
+ char rsp_buffer[RSP_BUFFER_SIZE];
+ int rsp_length = RSP_BUFFER_SIZE;
+
+ if (!nd)
+ return -EIO;
+
+ if (npreal_wait_and_set_command(nd, NPREAL_ASPP_COMMAND_SET, ASPP_CMD_LINECTRL) < 0)
+ return -EIO;
+
+ nd->cmd_buffer[2] = 2;
+ if (modem_control & UART_MCR_DTR)
+ nd->cmd_buffer[3] = 1;
+ else
+ nd->cmd_buffer[3] = 0;
+
+ if (modem_control & UART_MCR_RTS)
+ nd->cmd_buffer[4] = 1;
+ else
+ nd->cmd_buffer[4] = 0;
+
+ nd->cmd_ready = 1;
+ smp_mb(); /* use smp_mb() with waitqueue_active() */
+ /* used waitqueue_active() is safe because smp_mb() is used */
+ if (waitqueue_active(&nd->select_ex_wait))
+ wake_up_interruptible(&nd->select_ex_wait);
+
+ if (npreal_wait_command_completed(nd, NPREAL_ASPP_COMMAND_SET, ASPP_CMD_LINECTRL,
+ NPREAL_CMD_TIMEOUT, rsp_buffer, &rsp_length))
+ return -EIO;
+
+ if ((rsp_length != 4) || (rsp_buffer[2] != 'O') || (rsp_buffer[3] != 'K'))
+ return -EIO;
+
+ return 0;
+}
+
+static void npreal_flush_buffer(struct tty_struct *tty)
+{
+ struct npreal_struct *info = (struct npreal_struct *)tty->driver_data;
+ struct nd_struct *nd;
+ char rsp_buffer[RSP_BUFFER_SIZE];
+ int rsp_length = RSP_BUFFER_SIZE;
+ unsigned long flags;
+
+ if (!info)
+ return;
+
+ spin_lock_irqsave(&info->tx_lock, flags);
+ info->xmit_tail = 0;
+ info->xmit_head = 0;
+ info->xmit_cnt = 0;
+ spin_unlock_irqrestore(&info->tx_lock, flags);
+ wake_up_interruptible(&tty->write_wait);
+
+ if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc->ops->write_wakeup)
+ (tty->ldisc->ops->write_wakeup)(tty);
+
+ nd = info->net_node;
+ if (!nd)
+ return;
+
+ nd->tx_ready = 0;
+ if (npreal_wait_and_set_command(nd, NPREAL_ASPP_COMMAND_SET, ASPP_CMD_FLUSH) < 0)
+ return;
+
+ nd->cmd_buffer[2] = 1;
+ nd->cmd_buffer[3] = ASPP_FLUSH_ALL_BUFFER;
+ nd->cmd_ready = 1;
+ smp_mb(); /* use smp_mb() with waitqueue_active() */
+ /* used waitqueue_active() is safe because smp_mb() is used */
+ if (waitqueue_active(&nd->select_ex_wait))
+ wake_up_interruptible(&nd->select_ex_wait);
+
+ npreal_wait_command_completed(nd, NPREAL_ASPP_COMMAND_SET, ASPP_CMD_FLUSH,
+ NPREAL_CMD_TIMEOUT, rsp_buffer, &rsp_length);
+}
+
+static long npreal_wait_oqueue(struct npreal_struct *info, long timeout)
+{
+ struct nd_struct *nd;
+ long st = 0, tmp = 0;
+ uint32_t tout;
+
+ nd = info->net_node;
+ if (!nd)
+ return -EIO;
+
+ if (npreal_wait_and_set_command(nd, NPREAL_ASPP_COMMAND_SET, ASPP_CMD_WAIT_OQUEUE) < 0)
+ return -EIO;
+
+ if (timeout < HZ / 10)
+ timeout = HZ / 10;
+
+ st = jiffies;
+
+ if (timeout != MAX_SCHEDULE_TIMEOUT)
+ tout = (uint32_t)timeout;
+ else
+ tout = 0x7FFFFFFF;
+
+ nd->cmd_buffer[2] = 4;
+ memcpy(&nd->cmd_buffer[3], (void *)&tout, 4);
+ nd->wait_oqueue_responsed = 0;
+ nd->cmd_ready = 1;
+ smp_mb(); /* use smp_mb() with waitqueue_active() */
+ /* used waitqueue_active() is safe because smp_mb() is used */
+ if (waitqueue_active(&nd->select_ex_wait))
+ wake_up_interruptible(&nd->select_ex_wait);
+
+ while (nd->cmd_ready == 1) {
+ if (wait_event_interruptible_timeout(nd->cmd_rsp_wait, nd->cmd_rsp_flag == 1,
+ timeout) != 0) {
+ down(&nd->cmd_semaphore);
+ nd->cmd_rsp_flag = 0;
+ up(&nd->cmd_semaphore);
+ } else {
+ return -EIO;
+ }
+ }
+
+ nd->cmd_buffer[0] = 0;
+ do {
+ if (nd->wait_oqueue_responsed == 0) {
+ if (wait_event_interruptible_timeout(nd->cmd_rsp_wait,
+ nd->cmd_rsp_flag == 1, timeout)) {
+ down(&nd->cmd_semaphore);
+ nd->cmd_rsp_flag = 0;
+ up(&nd->cmd_semaphore);
+ }
+
+ tmp = abs((long)jiffies - (long)st);
+ if (tmp >= timeout)
+ timeout = 0;
+ else
+ timeout -= tmp;
+ } else {
+ return nd->oqueue;
+ }
+ } while (timeout > 0);
+
+ return -EIO;
+}
+
+static int32_t npreal_port_init_mode(struct ktermios *termio)
+{
+ int32_t mode;
+
+ mode = termio->c_cflag & CSIZE;
+
+ switch (mode) {
+ case CS5:
+ mode = ASPP_IOCTL_BITS5;
+ break;
+
+ case CS6:
+ mode = ASPP_IOCTL_BITS6;
+ break;
+
+ case CS7:
+ mode = ASPP_IOCTL_BITS7;
+ break;
+
+ case CS8:
+ mode = ASPP_IOCTL_BITS8;
+ break;
+ }
+
+ if (termio->c_cflag & CSTOPB)
+ mode |= ASPP_IOCTL_STOP2;
+ else
+ mode |= ASPP_IOCTL_STOP1;
+
+ if (termio->c_cflag & PARENB) {
+#ifdef CMSPAR
+ if (termio->c_cflag & CMSPAR) {
+ if (termio->c_cflag & PARODD)
+ mode |= ASPP_IOCTL_MARK;
+ else
+ mode |= ASPP_IOCTL_SPACE;
+ }
+#endif
+ if (termio->c_cflag & PARODD)
+ mode |= ASPP_IOCTL_ODD;
+ else
+ mode |= ASPP_IOCTL_EVEN;
+ } else {
+ mode |= ASPP_IOCTL_NONE;
+ }
+
+ return mode;
+}
+
+static void npreal_port_init_baud(struct npreal_struct *info, struct ktermios *termio,
+ struct ktermios *old_termios, int32_t *baud_ret, int *index_ret)
+{
+ int baudIndex;
+ int32_t baud;
+
+ switch (termio->c_cflag & (CBAUD | CBAUDEX)) {
+ case B921600:
+ baud = 921600L;
+ baudIndex = ASPP_IOCTL_B921600;
+ break;
+
+ case B460800:
+ baud = 460800;
+ baudIndex = ASPP_IOCTL_B460800;
+ break;
+
+ case B230400:
+ baud = 230400L;
+ baudIndex = ASPP_IOCTL_B230400;
+ break;
+
+ case B115200:
+ baud = 115200L;
+ baudIndex = ASPP_IOCTL_B115200;
+ break;
+
+ case B57600:
+ baud = 57600L;
+ baudIndex = ASPP_IOCTL_B57600;
+ break;
+
+ case B38400:
+ baud = 38400L;
+ baudIndex = ASPP_IOCTL_B38400;
+
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI) {
+ baud = 57600L;
+ baudIndex = ASPP_IOCTL_B57600;
+ }
+
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI) {
+ baud = 115200L;
+ baudIndex = ASPP_IOCTL_B115200;
+ }
+
+#ifdef ASYNC_SPD_SHI
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI) {
+ baud = 230400L;
+ baudIndex = ASPP_IOCTL_B230400;
+ }
+#endif
+
+#ifdef ASYNC_SPD_WARP
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP) {
+ baud = 460800L;
+ baudIndex = ASPP_IOCTL_B460800;
+ }
+#endif
+ break;
+
+ case B19200:
+ baud = 19200L;
+ baudIndex = ASPP_IOCTL_B19200;
+ break;
+
+ case B9600:
+ baud = 9600L;
+ baudIndex = ASPP_IOCTL_B9600;
+ break;
+
+ case B4800:
+ baud = 4800L;
+ baudIndex = ASPP_IOCTL_B4800;
+ break;
+
+ case B2400:
+ baud = 2400L;
+ baudIndex = ASPP_IOCTL_B2400;
+ break;
+
+ case B1800:
+ baud = 1800L;
+ baudIndex = 0xff;
+ break;
+
+ case B1200:
+ baud = 1200L;
+ baudIndex = ASPP_IOCTL_B1200;
+ break;
+
+ case B600:
+ baud = 600L;
+ baudIndex = ASPP_IOCTL_B600;
+ break;
+
+ case B300:
+ baud = 300L;
+ baudIndex = ASPP_IOCTL_B300;
+ break;
+
+ case B200:
+ baud = 200L;
+ baudIndex = 0xff;
+ break;
+
+ case B150:
+ baud = 150L;
+ baudIndex = ASPP_IOCTL_B150;
+ break;
+
+ case B134:
+ baud = 134L;
+ baudIndex = ASPP_IOCTL_B134;
+ break;
+
+ case B110:
+ baud = 110L;
+ baudIndex = ASPP_IOCTL_B110;
+ break;
+
+ case B75:
+ baud = 75L;
+ baudIndex = ASPP_IOCTL_B75;
+ break;
+
+ case B50:
+ baud = 50L;
+ baudIndex = ASPP_IOCTL_B50;
+ break;
+
+ default:
+ baud = tty_termios_baud_rate(termio);
+ baudIndex = 0xff;
+ }
+
+#ifdef ASYNC_SPD_CUST
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)
+ baudIndex = 0xff;
+#endif
+
+ if (baud > 921600L) {
+ termio->c_cflag &= ~(CBAUD | CBAUDEX);
+ termio->c_cflag |= old_termios->c_cflag & (CBAUD | CBAUDEX);
+ }
+
+ *baud_ret = baud;
+ *index_ret = baudIndex;
+}
+
+static int npreal_port_init(struct npreal_struct *info, struct ktermios *old_termios)
+{
+ struct ktermios *termio;
+ struct nd_struct *nd;
+ int rsp_length = RSP_BUFFER_SIZE;
+ int baudIndex, modem_status;
+ int ret;
+ int32_t baud, mode;
+ char rsp_buffer[RSP_BUFFER_SIZE];
+
+ nd = info->net_node;
+ if (!info->tty || !nd)
+ return -EIO;
+
+ termio = &(info->tty->termios);
+ mode = npreal_port_init_mode(termio);
+ npreal_port_init_baud(info, termio, old_termios, &baud, &baudIndex);
+ ret = npreal_set_port_command_done(info, nd, termio, rsp_buffer, &rsp_length, mode, baud,
+ baudIndex);
+ if (ret < 0)
+ return ret;
+
+ modem_status = 0;
+ if (((unsigned char)rsp_buffer[3] == 0xff) && ((unsigned char)rsp_buffer[4] == 0xff) &&
+ ((unsigned char)rsp_buffer[5] == 0xff)) {
+ termio->c_cflag &= ~(CBAUD | CBAUDEX);
+ termio->c_cflag |= old_termios->c_cflag & (CBAUD | CBAUDEX);
+ } else {
+ if (rsp_buffer[3])
+ modem_status |= UART_MSR_DSR;
+ if (rsp_buffer[4])
+ modem_status |= UART_MSR_CTS;
+ if (rsp_buffer[5])
+ modem_status |= UART_MSR_DCD;
+ }
+
+ npreal_check_modem_status(info, modem_status);
+
+ if ((baudIndex == 0xff) && (baud != 0)) {
+ ret = npreal_set_baud_command_done(info, nd, rsp_buffer, &rsp_length, baud);
+ if (ret)
+ return ret;
+ }
+
+ if (termio->c_iflag & (IXON | IXOFF)) {
+ ret = npreal_set_xonxoff_command_done(info, termio, nd, rsp_buffer, &rsp_length);
+ if (ret)
+ return ret;
+ }
+
+ if (termio->c_cflag & CLOCAL)
+ info->flags &= ~ASYNC_CHECK_CD;
+ else
+ info->flags |= ASYNC_CHECK_CD;
+
+ if (!info->tty)
+ return -EIO;
+
+ return 0;
+}
+
+static void npreal_init_net_node(struct nd_struct *net_node, struct npreal_struct *tty_node,
+ struct proc_dir_entry *de)
+{
+ net_node->tty_node = tty_node;
+ net_node->node_entry = de;
+ net_node->cmd_rsp_flag = 0;
+ net_node->flag = 0;
+
+ sema_init(&net_node->cmd_semaphore, 1);
+ sema_init(&net_node->semaphore, 1);
+
+ init_waitqueue_head(&net_node->initialize_wait);
+ init_waitqueue_head(&net_node->select_in_wait);
+ init_waitqueue_head(&net_node->select_out_wait);
+ init_waitqueue_head(&net_node->select_ex_wait);
+ init_waitqueue_head(&net_node->cmd_rsp_wait);
+}
+
+static void npreal_init_tty_node(struct npreal_struct *tty_node, struct nd_struct *net_node, int i)
+{
+ tty_node->net_node = net_node;
+ tty_node->port = i;
+ tty_node->type = PORT_16550A;
+ tty_node->flags = 0;
+ tty_node->xmit_fifo_size = 16;
+ tty_node->baud_base = 921600L;
+ tty_node->close_delay = 5 * HZ / 10;
+ tty_node->closing_wait = 30 * HZ;
+ tty_node->normal_termios = npvar_sdriver->init_termios;
+
+ memset(&tty_node->icount, 0, sizeof(tty_node->icount));
+ INIT_WORK(&tty_node->process_flip_tqueue, npreal_flush_to_ldisc);
+ INIT_WORK(&tty_node->tqueue, npreal_do_softint);
+ spin_lock_init(&tty_node->tx_lock);
+ sema_init(&tty_node->rx_semaphore, 1);
+
+ init_waitqueue_head(&tty_node->open_wait);
+ init_waitqueue_head(&tty_node->close_wait);
+ init_waitqueue_head(&tty_node->delta_msr_wait);
+}
+
+static int npreal_init(struct npreal_struct *tty_node, struct nd_struct *net_node)
+{
+ struct proc_dir_entry *de;
+ char buf[4];
+ int i;
+
+ npvar_proc_root = proc_mkdir("npreal2", NULL);
+ if (!npvar_proc_root)
+ return -ENOMEM;
+
+ tty_node = &npvar_table[0];
+ net_node = &npvar_net_nodes[0];
+
+ for (i = 0; i < NPREAL_PORTS; i++, tty_node++, net_node++) {
+ sprintf(buf, "%d", i);
+ de = proc_create_data(buf, 0666 | S_IFREG, npvar_proc_root, &npreal_net_fops,
+ (void *)net_node);
+ if (!de)
+ return -ENOMEM;
+
+ npreal_init_net_node(net_node, tty_node, de);
+ npreal_init_tty_node(tty_node, net_node, i);
+ }
+
+ return 0;
+}
+
+static int npreal_chars_in_buffer(struct tty_struct *tty)
+{
+ struct npreal_struct *info = (struct npreal_struct *)tty->driver_data;
+
+ if (!info)
+ return -EIO;
+
+ return info->xmit_cnt;
+}
+
+static int npreal_block_till_ready(struct tty_struct *tty, struct file *filp,
+ struct npreal_struct *info)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ struct nd_struct *nd;
+ int do_clocal = 0;
+ int retval = 0;
+
+ nd = info->net_node;
+ if (!nd)
+ return -EIO;
+
+ if ((filp->f_flags & O_NONBLOCK) || test_bit(TTY_IO_ERROR, &tty->flags)) {
+ info->flags |= ASYNC_NORMAL_ACTIVE;
+ return 0;
+ }
+
+ if (tty->termios.c_cflag & CLOCAL)
+ do_clocal = 1;
+
+ add_wait_queue(&info->open_wait, &wait);
+ while (1) {
+ if (tty_hung_up_p(filp))
+ break;
+ else if (info->flags & ASYNC_CLOSING) {
+ if (SERIAL_DO_RESTART && !(info->flags & ASYNC_HUP_NOTIFY))
+ retval = -ERESTARTSYS;
+ else
+ retval = -EAGAIN;
+ break;
+ }
+
+ if (!(info->flags & ASYNC_CLOSING) &&
+ (do_clocal || (info->modem_status & UART_MSR_DCD)))
+ break;
+
+ if (signal_pending(current)) {
+ retval = -EIO;
+ break;
+ }
+
+ current->state = TASK_INTERRUPTIBLE;
+ schedule();
+ }
+
+ remove_wait_queue(&info->open_wait, &wait);
+ if (!retval)
+ info->flags |= ASYNC_NORMAL_ACTIVE;
+
+ return retval;
+}
+
+static void set_common_xmit_fifo_size(struct npreal_struct *info, struct nd_struct *nd)
+{
+ if (info->type == PORT_16550A) {
+ if (nd->server_type == CN2500)
+ info->xmit_fifo_size = 64;
+ else
+ info->xmit_fifo_size = 16;
+ } else {
+ info->xmit_fifo_size = 1;
+ }
+}
+
+static int npreal_port_shutdown(struct npreal_struct *info)
+{
+ struct nd_struct *nd;
+ char rsp_buffer[RSP_BUFFER_SIZE];
+ int rsp_length = RSP_BUFFER_SIZE;
+
+ nd = info->net_node;
+ if (!nd)
+ return -EIO;
+
+ npreal_disconnect(nd, rsp_buffer, &rsp_length);
+ nd->flag &= ~NPREAL_NET_TTY_INUSED;
+ return 0;
+}
+
+static int npreal_get_serial_info(struct npreal_struct *info, struct serial_struct *retinfo)
+{
+ struct serial_struct tmp;
+
+ if (!retinfo)
+ return -EFAULT;
+
+ memset(&tmp, 0, sizeof(tmp));
+ tmp.type = info->type;
+ tmp.line = info->port;
+ tmp.flags = info->flags;
+ tmp.close_delay = info->close_delay;
+ tmp.closing_wait = info->closing_wait;
+ tmp.custom_divisor = info->custom_divisor;
+ tmp.baud_base = info->baud_base;
+ tmp.hub6 = 0;
+
+ if (copy_to_user(retinfo, &tmp, sizeof(*retinfo)))
+ return -EFAULT;
+ else
+ return 0;
+}
+
+static int npreal_set_serial_info(struct npreal_struct *info, struct serial_struct *new_info)
+{
+ struct serial_struct new_serial;
+ int rsp_length = RSP_BUFFER_SIZE;
+ int retval = 0;
+ unsigned int flags;
+ char rsp_buffer[RSP_BUFFER_SIZE];
+
+
+ if ((!new_info) || copy_from_user(&new_serial, new_info, sizeof(new_serial)))
+ return -EFAULT;
+
+ flags = info->flags & ASYNC_SPD_MASK;
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ if ((new_serial.close_delay != info->close_delay) ||
+ ((new_serial.flags & ~ASYNC_USR_MASK) != (info->flags & ~ASYNC_USR_MASK)))
+ return -EPERM;
+
+ info->flags = ((info->flags & ~ASYNC_USR_MASK) |
+ (new_serial.flags & ASYNC_USR_MASK));
+ } else {
+ info->flags = ((info->flags & ~ASYNC_FLAGS) | (new_serial.flags & ASYNC_FLAGS));
+ info->close_delay = new_serial.close_delay * HZ / 100;
+
+ if (new_serial.closing_wait == ASYNC_CLOSING_WAIT_NONE)
+ info->closing_wait = ASYNC_CLOSING_WAIT_NONE;
+ else
+ info->closing_wait = new_serial.closing_wait * HZ / 100;
+ }
+
+ info->type = new_serial.type;
+ set_common_xmit_fifo_size(info, info->net_node);
+
+ if (info->flags & ASYNC_INITIALIZED) {
+ if (flags != (info->flags & ASYNC_SPD_MASK))
+ retval = npreal_port_init(info, 0);
+
+ if (info->net_node)
+ npreal_set_tx_fifo_command_done(info, info->net_node, rsp_buffer,
+ &rsp_length);
+
+ }
+
+ info->custom_divisor = new_serial.custom_divisor;
+
+ if (info->custom_divisor == 0)
+ info->baud_base = 921600L;
+ else
+ info->baud_base = new_serial.baud_base;
+
+ return retval;
+}
+
+/**
+ * npreal_get_lsr_info() - get line status register info
+ *
+ * Let user call ioctl() to get info when the UART physically is emptied.
+ * On bus types like RS485, the transmitter must release the bus after
+ * transmitting. This must be done when the transmit shift register is
+ * empty, not be done when the transmit holding register is empty.
+ * This functionality allows an RS485 driver to be written in user space.
+ *
+ * Always return 0 when function is ended.
+ */
+static int npreal_get_lsr_info(struct npreal_struct *info,
+ unsigned int *value)
+{
+ unsigned int result = 0;
+
+ if (npreal_wait_oqueue(info, 0) == 0)
+ result = TIOCSER_TEMT;
+
+ put_user(result, value);
+
+ return 0;
+}
+
+static int npreal_start_break(struct nd_struct *nd)
+{
+ char rsp_buffer[RSP_BUFFER_SIZE];
+ int rsp_length = RSP_BUFFER_SIZE;
+
+ npreal_wait_and_set_command(nd, NPREAL_ASPP_COMMAND_SET, ASPP_CMD_START_BREAK);
+ nd->cmd_buffer[2] = 0;
+ nd->cmd_ready = 1;
+ smp_mb(); /* use smp_mb() with waitqueue_active() */
+ /* used waitqueue_active() is safe because smp_mb() is used */
+ if (waitqueue_active(&nd->select_ex_wait))
+ wake_up_interruptible(&nd->select_ex_wait);
+
+ if (npreal_wait_command_completed(nd, NPREAL_ASPP_COMMAND_SET, ASPP_CMD_START_BREAK,
+ NPREAL_CMD_TIMEOUT, rsp_buffer, &rsp_length))
+ return -EIO;
+
+ if ((rsp_length != 4) || (rsp_buffer[2] != 'O') || (rsp_buffer[3] != 'K'))
+ return -EIO;
+
+ return 0;
+}
+
+static int npreal_stop_break(struct nd_struct *nd)
+{
+ char rsp_buffer[RSP_BUFFER_SIZE];
+ int rsp_length = RSP_BUFFER_SIZE;
+
+ npreal_wait_and_set_command(nd, NPREAL_ASPP_COMMAND_SET, ASPP_CMD_STOP_BREAK);
+ nd->cmd_buffer[2] = 0;
+ nd->cmd_ready = 1;
+ smp_mb(); /* use smp_mb() with waitqueue_active() */
+ /* used waitqueue_active() is safe because smp_mb() is used */
+ if (waitqueue_active(&nd->select_ex_wait))
+ wake_up_interruptible(&nd->select_ex_wait);
+
+ rsp_length = sizeof(rsp_buffer);
+ if (npreal_wait_command_completed(nd, NPREAL_ASPP_COMMAND_SET, ASPP_CMD_STOP_BREAK,
+ NPREAL_CMD_TIMEOUT, rsp_buffer, &rsp_length))
+ return -EIO;
+
+ if (rsp_length != 4 || (rsp_buffer[2] != 'O') || (rsp_buffer[3] != 'K'))
+ return -EIO;
+
+ return 0;
+}
+
+/**
+ * npreal_send_break() - Sends break characters out the serial port.
+ * @info: pointer for npread_struct
+ * @duration: the time during sending break signals
+ *
+ * This is called by npreal_ioctl() with case of TCSBRK or TCSBRKP
+ */
+static void npreal_send_break(struct npreal_struct *info, unsigned int duration)
+{
+ struct nd_struct *nd;
+
+ nd = info->net_node;
+ if (!nd)
+ return;
+
+ npreal_start_break(nd);
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(duration);
+ npreal_stop_break(nd);
+}
+
+static void npreal_remove_proc_entry(struct proc_dir_entry *pde, int idx)
+{
+ char tmp[10];
+
+ if (!pde)
+ return;
+
+ sprintf(tmp, "%d", idx);
+
+ if (idx == 404)
+ remove_proc_entry("npreal2", NULL);
+ else
+ remove_proc_entry(tmp, npvar_proc_root);
+}
+
+static void npreal_process_notify(struct nd_struct *nd, char *rsp_buffer, int rsp_length)
+{
+ struct npreal_struct *info = nd->tty_node;
+ int state;
+
+ if (!info || rsp_length != 5)
+ return;
+
+ if (rsp_buffer[2] & ASPP_NOTIFY_MSR_CHG) {
+ state = 0;
+
+ if (rsp_buffer[3] & 0x10)
+ state |= UART_MSR_CTS;
+ if (rsp_buffer[3] & 0x20)
+ state |= UART_MSR_DSR;
+ if (rsp_buffer[3] & 0x80)
+ state |= UART_MSR_DCD;
+ npreal_check_modem_status(info, state);
+ }
+
+ if (rsp_buffer[2] & ASPP_NOTIFY_BREAK) {
+ struct tty_struct *tty;
+
+ down(&info->rx_semaphore);
+ tty = info->tty;
+ if (!tty || !info->ttyPort.low_latency) {
+ up(&info->rx_semaphore);
+ return;
+ }
+
+ tty_insert_flip_char(&info->ttyPort, 0, TTY_BREAK);
+ up(&info->rx_semaphore);
+ info->icount.rx++;
+ info->icount.brk++;
+ schedule_work(&info->process_flip_tqueue);
+
+ if (info->flags & ASYNC_SAK)
+ do_SAK(info->tty);
+ }
+
+ if (rsp_buffer[2] & ASPP_NOTIFY_PARITY)
+ info->icount.parity++;
+ if (rsp_buffer[2] & ASPP_NOTIFY_FRAMING)
+ info->icount.frame++;
+ if ((rsp_buffer[2] & ASPP_NOTIFY_SW_OVERRUN) || (rsp_buffer[2] & ASPP_NOTIFY_HW_OVERRUN))
+ info->icount.overrun++;
+}
+
+static int32_t npreal_do_session_mode(struct ktermios *termio)
+{
+ int32_t mode;
+
+ mode = termio->c_cflag & CSIZE;
+ switch (mode) {
+ case CS5:
+ mode = ASPP_IOCTL_BITS5;
+ break;
+
+ case CS6:
+ mode = ASPP_IOCTL_BITS6;
+ break;
+
+ case CS7:
+ mode = ASPP_IOCTL_BITS7;
+ break;
+
+ case CS8:
+ mode = ASPP_IOCTL_BITS8;
+ break;
+
+ }
+
+ if (termio->c_cflag & CSTOPB)
+ mode |= ASPP_IOCTL_STOP2;
+ else
+ mode |= ASPP_IOCTL_STOP1;
+
+ if (termio->c_cflag & PARENB) {
+ if (termio->c_cflag & PARODD)
+ mode |= ASPP_IOCTL_ODD;
+ else
+ mode |= ASPP_IOCTL_EVEN;
+ } else {
+ mode |= ASPP_IOCTL_NONE;
+ }
+
+ return mode;
+}
+
+static void npreal_do_session_baud(struct npreal_struct *info, struct ktermios *termio,
+ int32_t *baud_ret, int *baud_ind_ret)
+{
+ int32_t baud;
+ int baudIndex;
+
+ switch (termio->c_cflag & (CBAUD | CBAUDEX)) {
+ case B921600:
+ baud = 921600L;
+ baudIndex = ASPP_IOCTL_B921600;
+ break;
+
+ case B460800:
+ baud = 460800;
+ baudIndex = ASPP_IOCTL_B460800;
+ break;
+
+ case B230400:
+ baud = 230400L;
+ baudIndex = ASPP_IOCTL_B230400;
+ break;
+
+ case B115200:
+ baud = 115200L;
+ baudIndex = ASPP_IOCTL_B115200;
+ break;
+
+ case B57600:
+ baud = 57600L;
+ baudIndex = ASPP_IOCTL_B57600;
+ break;
+
+ case B38400:
+ baud = 38400L;
+ baudIndex = ASPP_IOCTL_B38400;
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI) {
+ baud = 57600L;
+ baudIndex = ASPP_IOCTL_B57600;
+ }
+
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI) {
+ baud = 115200L;
+ baudIndex = ASPP_IOCTL_B115200;
+ }
+#ifdef ASYNC_SPD_SHI
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI) {
+ baud = 230400L;
+ baudIndex = ASPP_IOCTL_B230400;
+ }
+#endif
+#ifdef ASYNC_SPD_WARP
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP) {
+ baud = 460800L;
+ baudIndex = ASPP_IOCTL_B460800;
+ }
+#endif
+ break;
+
+ case B19200:
+ baud = 19200L;
+ baudIndex = ASPP_IOCTL_B19200;
+ break;
+
+ case B9600:
+ baud = 9600L;
+ baudIndex = ASPP_IOCTL_B9600;
+ break;
+
+ case B4800:
+ baud = 4800L;
+ baudIndex = ASPP_IOCTL_B4800;
+ break;
+
+ case B2400:
+ baud = 2400L;
+ baudIndex = ASPP_IOCTL_B2400;
+ break;
+
+ case B1800:
+ baud = 1800L;
+ baudIndex = 0xff;
+ break;
+
+ case B1200:
+ baud = 1200L;
+ baudIndex = ASPP_IOCTL_B1200;
+ break;
+
+ case B600:
+ baud = 600L;
+ baudIndex = ASPP_IOCTL_B600;
+ break;
+
+ case B300:
+ baud = 300L;
+ baudIndex = ASPP_IOCTL_B300;
+ break;
+
+ case B200:
+ baud = 200L;
+ baudIndex = 0xff;
+ break;
+
+ case B150:
+ baud = 150L;
+ baudIndex = ASPP_IOCTL_B150;
+ break;
+
+ case B134:
+ baud = 134L;
+ baudIndex = ASPP_IOCTL_B134;
+ break;
+
+ case B110:
+ baud = 110L;
+ baudIndex = ASPP_IOCTL_B110;
+ break;
+
+ case B75:
+ baud = 75L;
+ baudIndex = ASPP_IOCTL_B75;
+ break;
+
+ case B50:
+ baud = 50L;
+ baudIndex = 0xff;
+ break;
+
+ default:
+ baud = 0;
+ baudIndex = 0xff;
+ }
+#ifdef ASYNC_SPD_CUST
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)
+ baudIndex = 0xff;
+#endif
+ *baud_ret = baud;
+ *baud_ind_ret = baudIndex;
+}
+
+static void npreal_do_session_buffer(struct nd_struct *nd, struct npreal_struct *info,
+ struct ktermios *termio, int baud, int mode, int baudIndex)
+{
+ nd->cmd_buffer[0] = NPREAL_ASPP_COMMAND_SET;
+ nd->cmd_buffer[1] = ASPP_CMD_PORT_INIT;
+ nd->cmd_buffer[2] = 8;
+ nd->cmd_buffer[3] = baudIndex; /* baud rate */
+ nd->cmd_buffer[4] = mode; /* mode */
+
+ /* line control */
+ if (info->modem_control & UART_MCR_DTR)
+ nd->cmd_buffer[5] = 1;
+ else
+ nd->cmd_buffer[5] = 0;
+ if (info->modem_control & UART_MCR_RTS)
+ nd->cmd_buffer[6] = 1;
+ else
+ nd->cmd_buffer[6] = 0;
+
+ /* flow control */
+ if ((info->flags & ASYNC_INITIALIZED) && (termio->c_cflag & CRTSCTS)) {
+ nd->cmd_buffer[7] = 1;
+ nd->cmd_buffer[8] = 1;
+ } else {
+ nd->cmd_buffer[7] = 0;
+ nd->cmd_buffer[8] = 0;
+ }
+
+ if (termio->c_iflag & IXON)
+ nd->cmd_buffer[9] = 1;
+ else
+ nd->cmd_buffer[9] = 0;
+ if (termio->c_iflag & IXOFF)
+ nd->cmd_buffer[10] = 1;
+ else
+ nd->cmd_buffer[10] = 0;
+
+ if ((baudIndex == 0xff) && (baud != 0)) {
+ nd->cmd_buffer[11] = ASPP_CMD_SETBAUD;
+ nd->cmd_buffer[12] = 4;
+
+ if (((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST) && info->custom_divisor)
+ baud = info->baud_base/info->custom_divisor;
+
+ memcpy(&nd->cmd_buffer[13], &baud, 4);
+ }
+
+ if (termio->c_iflag & (IXON | IXOFF)) {
+ nd->cmd_buffer[17] = ASPP_CMD_XONXOFF;
+ nd->cmd_buffer[18] = 2;
+ nd->cmd_buffer[19] = termio->c_cc[VSTART];
+ nd->cmd_buffer[20] = termio->c_cc[VSTOP];
+ }
+
+ nd->cmd_buffer[21] = ASPP_CMD_TX_FIFO;
+ nd->cmd_buffer[22] = 1;
+ nd->cmd_buffer[23] = info->xmit_fifo_size;
+ nd->cmd_buffer[24] = ASPP_CMD_LINECTRL;
+ nd->cmd_buffer[25] = 2;
+
+ if (info->modem_control & UART_MCR_DTR)
+ nd->cmd_buffer[26] = 1;
+ else
+ nd->cmd_buffer[26] = 0;
+ if (info->modem_control & UART_MCR_RTS)
+ nd->cmd_buffer[27] = 1;
+ else
+ nd->cmd_buffer[27] = 0;
+
+ nd->do_session_recovery_len = 27 + 1;
+}
+
+static void npreal_do_session_recovery(struct npreal_struct *info)
+{
+ struct tty_struct *tty;
+ struct nd_struct *nd;
+ struct ktermios *termio;
+ int32_t baud, mode;
+ int baudIndex;
+
+ tty = info->tty;
+ nd = info->net_node;
+
+ if (!tty || !nd)
+ return;
+
+ if (!(nd->flag & NPREAL_NET_NODE_OPENED) || !(nd->flag & NPREAL_NET_NODE_CONNECTED))
+ return;
+
+ if (info->flags & ASYNC_INITIALIZED) {
+ termio = &(info->tty->termios);
+ } else {
+ termio = &info->normal_termios;
+
+ if (!termio)
+ return;
+ }
+
+ down(&nd->cmd_semaphore);
+ mode = npreal_do_session_mode(termio);
+ npreal_do_session_baud(info, termio, &baud, &baudIndex);
+ npreal_do_session_buffer(nd, info, termio, baud, mode, baudIndex);
+ nd->flag |= NPREAL_NET_DO_SESSION_RECOVERY;
+ nd->cmd_ready = 1;
+ up(&nd->cmd_semaphore);
+ /* waitqueue_active() is safe because nd->cmd_ready is in semaphore.*/
+ if (waitqueue_active(&nd->select_ex_wait))
+ wake_up_interruptible(&nd->select_ex_wait);
+}
+
+static int npreal_startup_serial_port(struct npreal_struct *info, struct nd_struct *nd)
+{
+ int ret;
+
+ nd->flag &= ~NPREAL_NET_DO_SESSION_RECOVERY;
+ info->modem_status = 0;
+ info->modem_control = 0;
+
+ if (info->tty->termios.c_cflag & CBAUD)
+ info->modem_control = UART_MCR_DTR | UART_MCR_RTS;
+
+ ret = npreal_port_init(info, 0);
+ if (ret != 0)
+ return ret;
+
+ set_common_xmit_fifo_size(info, nd);
+ return 0;
+}
+
+static int npreal_startup_init(struct npreal_struct *info, struct nd_struct *nd,
+ struct tty_struct *tty, unsigned long *page)
+{
+ DECLARE_WAITQUEUE(wait, current);
+
+ if (!nd)
+ return -EIO;
+
+ add_wait_queue(&nd->initialize_wait, &wait);
+ while (test_and_set_bit(NPREAL_NET_DO_INITIALIZE, &nd->flag)) {
+ if (signal_pending(current))
+ break;
+
+ schedule();
+ }
+
+ remove_wait_queue(&nd->initialize_wait, &wait);
+
+ info->tty = tty;
+
+ if (info->flags & ASYNC_INITIALIZED) {
+ clear_bit(NPREAL_NET_DO_INITIALIZE, &nd->flag);
+ smp_mb(); /* use smp_mb() with waitqueue_active() */
+ /* used waitqueue_active() is safe because smp_mb() is used */
+ if (waitqueue_active(&nd->initialize_wait))
+ wake_up_interruptible(&nd->initialize_wait);
+
+ return 0;
+ }
+
+ *page = __get_free_page(GFP_KERNEL);
+ if (!(*page)) {
+ clear_bit(NPREAL_NET_DO_INITIALIZE, &nd->flag);
+ smp_mb(); /* use smp_mb() with waitqueue_active() */
+ /* used waitqueue_active() is safe because smp_mb() is used */
+ if (waitqueue_active(&nd->initialize_wait))
+ wake_up_interruptible(&nd->initialize_wait);
+
+ return -ENOMEM;
+ }
+
+ return 1;
+}
+
+static int npreal_startup_tty_usage(struct npreal_struct *info, struct nd_struct *nd,
+ char *rsp_buf, int *rsp_len)
+{
+ int ret;
+
+ ret = npreal_set_used_command_done(nd, rsp_buf, rsp_len);
+ if (ret != 0) {
+ npreal_set_unused_command_done(nd, rsp_buf, rsp_len);
+ return ret;
+ } else if (OFFLINE_POLLING) {
+ if (!rsp_buf[2]) {
+ npreal_set_unused_command_done(nd, rsp_buf, rsp_len);
+ return ret;
+ }
+ }
+
+ nd->flag |= NPREAL_NET_TTY_INUSED;
+ return 0;
+}
+
+static int npreal_startup_disconnect(struct npreal_struct *info, struct nd_struct *nd,
+ char *rsp_buf, int *rsp_len)
+{
+ int ret;
+
+ nd->flag &= ~NPREAL_NET_NODE_DISCONNECTED;
+
+ ret = npreal_set_unused_command_done(nd, rsp_buf, rsp_len);
+ if (ret != 0)
+ nd->flag |= NPREAL_NET_NODE_DISCONNECTED;
+ else
+ nd->flag &= ~NPREAL_NET_TTY_INUSED;
+
+ return ret;
+}
+
+static int npreal_startup(struct npreal_struct *info, struct file *filp,
+ struct tty_struct *tty)
+{
+ struct nd_struct *nd = info->net_node;
+ unsigned long page;
+ int rsp_length = RSP_BUFFER_SIZE;
+ int cnt = 0, ret;
+ char rsp_buffer[RSP_BUFFER_SIZE];
+
+ ret = npreal_startup_init(info, nd, tty, &page);
+ if (ret < 1)
+ return ret;
+
+ if (!(nd->flag & NPREAL_NET_TTY_INUSED)) {
+ ret = npreal_startup_tty_usage(info, nd, rsp_buffer, &rsp_length);
+ if (ret)
+ goto startup_err;
+ } else {
+ if (nd->flag & NPREAL_NET_NODE_DISCONNECTED) {
+ npreal_startup_disconnect(info, nd, rsp_buffer, &rsp_length);
+ goto startup_err;
+ }
+
+ while ((nd->cmd_ready == 1) && (cnt++ < 10)) {
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(HZ / 100);
+ }
+ }
+
+ ret = npreal_startup_serial_port(info, nd);
+ if (ret)
+ goto startup_err;
+
+ ret = npreal_set_tx_fifo_command_done(info, nd, rsp_buffer, &rsp_length);
+ if (ret)
+ goto startup_err;
+
+ if (info->xmit_buf)
+ free_page(page);
+ else
+ info->xmit_buf = (unsigned char *)page;
+
+ if (info->tty)
+ test_and_clear_bit(TTY_IO_ERROR, &info->tty->flags);
+
+ info->xmit_tail = 0;
+ info->xmit_head = 0;
+ info->xmit_cnt = 0;
+ info->flags |= ASYNC_INITIALIZED;
+ clear_bit(NPREAL_NET_DO_INITIALIZE, &nd->flag);
+ smp_mb(); /* use smp_mb() with waitqueue_active() */
+ /* used waitqueue_active() is safe because smp_mb() is used */
+ if (waitqueue_active(&nd->initialize_wait))
+ wake_up_interruptible(&nd->initialize_wait);
+
+ return 0;
+
+startup_err:
+ npreal_disconnect(nd, rsp_buffer, &rsp_length);
+ free_page(page);
+ clear_bit(NPREAL_NET_DO_INITIALIZE, &nd->flag);
+ smp_mb(); /* use smp_mb() with waitqueue_active() */
+ /* used waitqueue_active() is safe because smp_mb() is used */
+ if (waitqueue_active(&nd->initialize_wait))
+ wake_up_interruptible(&nd->initialize_wait);
+
+ return ret;
+}
+
+static int npreal_open_startup(struct tty_struct *tty, struct npreal_struct *info,
+ struct file *filp)
+{
+ long jiff_th;
+ int ret, retry;
+
+ retry = NPREAL_CMD_TRY - 1;
+ while (retry) {
+ /* For some circumstance, device may reset the connection
+ * during the port opening. These code is to reopen the port
+ * without telling application. Considering a real situation
+ * of connection lost, we use -ETIME to exit the retry loop.
+ */
+ ret = npreal_startup(info, filp, tty);
+ if (ret == 0)
+ break;
+ else if (ret == -ETIME) {
+ pr_err("npreal_startup failed(%d)\n", ret);
+ return -EIO;
+ }
+
+ jiff_th = (NPREAL_CMD_TRY-retry)*HZ/2;
+ schedule_timeout_uninterruptible(jiff_th);
+
+ retry--;
+ if (retry < 0) {
+ pr_err("npreal_startup failed\n");
+ return -EIO;
+ }
+ }
+ return 0;
+}
+
+/**
+ * npreal_throttle() - Notify the tty driver input buffer is full
+ * @tty: pointer for struct tty_struct
+ *
+ * This routine is called by the upper-layer tty layer to signal that
+ * incoming characters should be throttled. (tty driver buffer is full)
+ *
+ */
+static void npreal_throttle(struct tty_struct *tty)
+{
+ struct npreal_struct *info = (struct npreal_struct *)tty->driver_data;
+ struct nd_struct *nd;
+
+ if (!info)
+ return;
+
+ nd = info->net_node;
+ if (!nd)
+ return;
+
+ nd->rx_ready = 0;
+ smp_mb(); /* use smp_mb() with waitqueue_active() */
+ /* used waitqueue_active() is safe because smp_mb() is used */
+ if (waitqueue_active(&nd->select_out_wait))
+ wake_up_interruptible(&nd->select_out_wait);
+}
+
+/**
+ * npreal_unthrottle() - characters can now be sent to the tty
+ * @tty: pointer for struct tty_struct
+ *
+ * Notify the tty driver that characters can now be sent to the tty without
+ * fear of overrunning the input buffers of the line disciplines. tty
+ * driver buffer have space to receiver data, wake up net
+ * select_out_wait(net).
+ *
+ */
+
+static void npreal_unthrottle(struct tty_struct *tty)
+{
+ struct npreal_struct *info = (struct npreal_struct *)tty->driver_data;
+ struct nd_struct *nd;
+
+ if (!info)
+ return;
+
+ nd = info->net_node;
+ if (!nd)
+ return;
+
+ nd->rx_ready = 1;
+ smp_mb(); /* use smp_mb() with waitqueue_active() */
+ /* used waitqueue_active() is safe because smp_mb() is used */
+ if (waitqueue_active(&nd->select_out_wait))
+ wake_up_interruptible(&nd->select_out_wait);
+}
+
+/**
+ * npreal_hangup() - This routine will shutdown a serial port
+ * @tty: pointer for tty_struct
+ *
+ * interrupts maybe disabled, and DTR is dropped if the hangup on close
+ * termio flag is on.
+ *
+ */
+static void npreal_shutdown(struct npreal_struct *info)
+{
+ struct nd_struct *nd = info->net_node;
+ unsigned long flags;
+
+ while (test_and_set_bit(NPREAL_NET_DO_INITIALIZE, &nd->flag)) {
+ if (signal_pending(current))
+ break;
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(HZ / 100);
+ }
+
+ if (!(info->flags & ASYNC_INITIALIZED))
+ goto done;
+
+ spin_lock_irqsave(&info->tx_lock, flags);
+ if (info->xmit_buf) {
+ free_page((unsigned long)info->xmit_buf);
+ info->xmit_buf = 0;
+ }
+ spin_unlock_irqrestore(&info->tx_lock, flags);
+
+ if (info->tty) {
+ set_bit(TTY_IO_ERROR, &info->tty->flags);
+ npreal_unthrottle(info->tty);
+ }
+
+ if (!info->tty || (info->tty->termios.c_cflag & HUPCL))
+ info->modem_control &= ~(UART_MCR_DTR | UART_MCR_RTS);
+
+done:
+ npreal_port_shutdown(info);
+ info->flags &= ~(ASYNC_NORMAL_ACTIVE |
+ ASYNC_INITIALIZED | ASYNC_CLOSING);
+ down(&info->rx_semaphore);
+ info->tty = 0;
+ up(&info->rx_semaphore);
+ clear_bit(NPREAL_NET_DO_INITIALIZE, &nd->flag);
+ wake_up_interruptible(&info->open_wait);
+ wake_up_interruptible(&info->close_wait);
+ smp_mb(); /* use smp_mb() with waitqueue_active() */
+ /* used waitqueue_active() is safe because smp_mb() is used */
+ if (waitqueue_active(&nd->initialize_wait))
+ wake_up_interruptible(&nd->initialize_wait);
+}
+
+/**
+ * npreal_open() - This routine is called whenever a serial port is opened.
+ * @tty: pointer for struct tty_struct
+ * @filp: pointer for struct file
+ *
+ * Note that on failure, we don't decrement the module use count - the tty
+ * driver later will call npreal_close, which will decrement it for us as
+ * long as tty->driver_data is set non-NULL.
+ *
+ * Set tty->driver_data before entering npreal_startup(), so that the tty
+ * driver can decrease refcount if npreal_startup() failed, by calling
+ * npreal_close().
+ *
+ * For some circumstance, device may reset the connection during the
+ * port opening. These code is to reopen the port without telling
+ * application. Considering a real situation of connection lost, we
+ * use -ETIME to exit the retry loop.
+ *
+ * A zero is returned on success and a negative errno code for failure.
+ *
+ */
+static int npreal_open(struct tty_struct *tty, struct file *filp)
+{
+ struct npreal_struct *info;
+ struct nd_struct *nd;
+ int ret, line;
+
+ try_module_get(THIS_MODULE);
+ line = tty->index;
+
+ if (line < 0 || line >= NPREAL_PORTS) {
+ module_put(THIS_MODULE);
+ return (-ENODEV);
+ }
+
+ info = npvar_table + line;
+ nd = info->net_node;
+
+ if (!nd || !(nd->flag & NPREAL_NET_NODE_OPENED)) {
+ pr_err("net device not ready\n");
+ module_put(THIS_MODULE);
+ return -ENODEV;
+ }
+
+ tty->driver_data = info;
+ info->count++;
+
+ ret = npreal_open_startup(tty, info, filp);
+ if (ret)
+ return ret;
+
+ ret = npreal_block_till_ready(tty, filp, info);
+ if (ret)
+ return ret;
+
+ if ((info->count == 1) && (info->flags & ASYNC_SPLIT_TERMIOS)) {
+ if (tty->driver->subtype == SERIAL_TYPE_NORMAL)
+ tty->termios = info->normal_termios;
+
+ ret = npreal_port_init(info, 0);
+ if (ret)
+ return ret;
+ }
+
+ info->session = task_session(current);
+ info->pgrp = task_pgrp(current);
+ info->ttyPort.low_latency = 1;
+
+ return 0;
+}
+
+/**
+ * npreal_close() - This routine is called when the serial port gets closed
+ * @tty: pointer for struct tty_struct
+ * @filp: pointer for struct file
+ *
+ * This function will wait for the last remaining data to be sent and check
+ * tty count, and then shutdown NPort tty later on.
+ */
+static void npreal_close(struct tty_struct *tty, struct file *filp)
+{
+ struct npreal_struct *info = (struct npreal_struct *)tty->driver_data;
+ long timeout, et, val;
+ int cnt;
+
+ if (!info)
+ return;
+
+ if (tty_hung_up_p(filp)) {
+ info->count--;
+ module_put(THIS_MODULE);
+ return;
+ }
+
+ if (tty->count == 1 && (info->count != 1)) {
+ pr_warn("[%d] bad serial port count; tty->count is 1, info->count is %d\n",
+ current->pid, info->count);
+ info->count = 1;
+ }
+
+ if (--info->count < 0) {
+ pr_warn("%s: bad serial port count for port %d: %d\n", __func__, info->port,
+ info->count);
+ info->count = 0;
+ }
+
+ if (info->count) {
+ module_put(THIS_MODULE);
+ return;
+ }
+
+ if (info->flags & ASYNC_CLOSING)
+ return;
+
+ info->flags |= ASYNC_CLOSING;
+ tty->closing = 1;
+
+ if (info->flags & ASYNC_NORMAL_ACTIVE)
+ info->normal_termios = tty->termios;
+
+ if (!(filp->f_flags & O_NONBLOCK) && info->closing_wait != ASYNC_CLOSING_WAIT_NONE) {
+ et = jiffies + info->closing_wait;
+ tty_wait_until_sent(tty, info->closing_wait);
+ cnt = 0;
+
+ while ((timeout = et - (long)jiffies) > 0) {
+ val = npreal_wait_oqueue(info, timeout);
+
+ if (val < 0 || (val == 0 && (++cnt >= 3)))
+ break;
+ else if (val > 0)
+ cnt = 0;
+
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(HZ / 100);
+ }
+ }
+
+ npreal_flush_buffer(tty);
+ if (tty->ldisc->ops->flush_buffer)
+ tty->ldisc->ops->flush_buffer(tty);
+
+ npreal_shutdown(info);
+ tty->closing = 0;
+ module_put(THIS_MODULE);
+}
+
+/**
+ * npreal_wrtie() - Called when some data will be sent to tty device
+ * @tty: pointer for struct tty_struct
+ * @buf: pointer for struct file
+ * @count: number of data should be sent
+ *
+ * npreal_write() represents data should be sent to tty device
+ * (NPort server device) from host. This function will copy data from user
+ * space to kernel space, and set nd->tx_ready = 1. When nd->tx_ready = 1,
+ * polling parameters will changed and wake up NPreal daemon in userspace
+ * by /proc/npreal2, and then NPreal daemon will read data later.
+ *
+ * If return 0, it means fail; if return positive value, it means bytes of
+ * data should be sent.
+ *
+ */
+static int npreal_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+ struct npreal_struct *info = (struct npreal_struct *)tty->driver_data;
+ struct nd_struct *nd;
+ unsigned long flags;
+ int c, total = 0;
+
+ if ((!info) || !tty || !info->xmit_buf)
+ return 0;
+
+ nd = info->net_node;
+
+ if (!nd)
+ return 0;
+ while (1) {
+ c = min(count, min((int)(SERIAL_XMIT_SIZE - info->xmit_cnt - 1),
+ (int)(SERIAL_XMIT_SIZE - info->xmit_head)));
+ if (c <= 0)
+ break;
+
+ spin_lock_irqsave(&info->tx_lock, flags);
+ memcpy(info->xmit_buf + info->xmit_head, buf, c);
+ info->xmit_head = (info->xmit_head + c) & (SERIAL_XMIT_SIZE - 1);
+ info->xmit_cnt += c;
+ spin_unlock_irqrestore(&info->tx_lock, flags);
+
+ buf += c;
+ count -= c;
+ total += c;
+ }
+
+ if (info->xmit_cnt) {
+ nd->tx_ready = 1;
+ smp_mb(); /* use smp_mb() with waitqueue_active() */
+ /* used waitqueue_active() is safe because smp_mb() is used */
+ if (waitqueue_active(&nd->select_in_wait))
+ wake_up_interruptible(&nd->select_in_wait);
+ }
+
+ return total;
+}
+
+static int npreal_put_char(struct tty_struct *tty, unsigned char ch)
+{
+ struct npreal_struct *info = (struct npreal_struct *)tty->driver_data;
+ struct nd_struct *nd;
+ unsigned long flags;
+
+ if (!tty || !info)
+ return 0;
+
+ if (!info->xmit_buf || (info->xmit_cnt >= SERIAL_XMIT_SIZE - 1))
+ return 0;
+
+ nd = info->net_node;
+ if (!nd)
+ return 0;
+
+ spin_lock_irqsave(&info->tx_lock, flags);
+ info->xmit_buf[info->xmit_head++] = ch;
+ info->xmit_head &= SERIAL_XMIT_SIZE - 1;
+ info->xmit_cnt++;
+ spin_unlock_irqrestore(&info->tx_lock, flags);
+ nd->tx_ready = 1;
+ smp_mb(); /* use smp_mb() with waitqueue_active() */
+ /* used waitqueue_active() is safe because smp_mb() is used */
+ if (waitqueue_active(&nd->select_in_wait))
+ wake_up_interruptible(&nd->select_in_wait);
+
+ return 1;
+}
+
+static int npreal_write_room(struct tty_struct *tty)
+{
+ struct npreal_struct *info = (struct npreal_struct *)tty->driver_data;
+ int ret;
+
+ if (!info)
+ return 0;
+
+ ret = SERIAL_XMIT_SIZE - info->xmit_cnt - 1;
+
+ return ret < 0 ? 0 : ret;
+}
+static void npreal_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+ struct npreal_struct *info;
+
+ info = ((struct npreal_struct *)tty->driver_data);
+ if (info)
+ npreal_wait_oqueue(info, timeout);
+}
+
+static int npreal_break(struct tty_struct *ttyinfo, int break_state)
+{
+ struct npreal_struct *info;
+ struct nd_struct *nd;
+
+ info = (struct npreal_struct *)ttyinfo->driver_data;
+ if (!info)
+ return -EFAULT;
+
+ nd = info->net_node;
+ if (!nd)
+ return -EFAULT;
+
+ if (break_state == -1)
+ npreal_start_break(nd);
+ else
+ npreal_stop_break(nd);
+
+ return 0;
+}
+
+static int npreal_ioctl(struct tty_struct *tty, unsigned int cmd,
+ unsigned long arg)
+{
+ struct npreal_struct *info = (struct npreal_struct *)tty->driver_data;
+ struct serial_icounter_struct *p_cuser; /* user space */
+ unsigned long templ;
+ int ret = 0;
+
+ if (!info)
+ return -ENODEV;
+
+ if ((cmd != TIOCGSERIAL) && (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT) &&
+ test_bit(TTY_IO_ERROR, &tty->flags))
+ return -EIO;
+
+ switch (cmd) {
+ case TCFLSH:
+ ret = tty_check_change(tty);
+ if (!ret) {
+ switch (arg) {
+ case TCIFLUSH:
+ if (tty->ldisc->ops->flush_buffer)
+ tty->ldisc->ops->flush_buffer(tty);
+ break;
+
+ case TCIOFLUSH:
+ if (tty->ldisc->ops->flush_buffer)
+ tty->ldisc->ops->flush_buffer(tty);
+ npreal_flush_buffer(tty);
+ break;
+
+ case TCOFLUSH:
+ npreal_flush_buffer(tty);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+ }
+ break;
+
+ case TCSBRK: /* SVID version: non-zero arg --> no break */
+ ret = tty_check_change(tty);
+ if (!ret) {
+ tty_wait_until_sent(tty, 0);
+ if (!arg)
+ npreal_send_break(info, HZ / 4);
+ }
+ break;
+
+ case TCSBRKP: /* support for POSIX tcsendbreak() */
+ ret = tty_check_change(tty);
+ if (!ret) {
+ tty_wait_until_sent(tty, 0);
+ npreal_send_break(info, arg ? arg * (HZ / 10) : HZ / 4);
+ }
+ break;
+
+ case TIOCGSOFTCAR:
+ put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long *)arg);
+ break;
+
+ case TIOCSSOFTCAR:
+ get_user(templ, (unsigned long *)arg);
+ tty->termios.c_cflag = ((tty->termios.c_cflag & ~CLOCAL) | (arg ? CLOCAL : 0));
+ break;
+
+ case TIOCGSERIAL:
+ ret = (npreal_get_serial_info(info, (struct serial_struct *)arg));
+ break;
+
+ case TIOCSSERIAL:
+ ret = (npreal_set_serial_info(info, (struct serial_struct *)arg));
+ break;
+
+ case TIOCSERGETLSR: /* Get line status register */
+ ret = (npreal_get_lsr_info(info, (unsigned int *)arg));
+ break;
+
+ case TIOCMIWAIT: {
+ struct async_icount cprev;
+ DECLARE_WAITQUEUE(wait, current);
+
+ cprev = info->icount;
+ add_wait_queue(&info->delta_msr_wait, &wait);
+ while (1) {
+ struct async_icount cnow;
+
+ cnow = info->icount;
+ if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) ||
+ ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) ||
+ ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) ||
+ ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts))) {
+ ret = 0;
+ break;
+ }
+
+ if (signal_pending(current)) {
+ ret = -ERESTARTSYS;
+ break;
+ }
+
+ cprev = cnow;
+ current->state = TASK_INTERRUPTIBLE;
+ schedule();
+ }
+
+ remove_wait_queue(&info->delta_msr_wait, &wait);
+ break;
+ }
+
+ case TIOCGICOUNT:{
+ struct async_icount cnow;
+
+ cnow = info->icount;
+ p_cuser = (struct serial_icounter_struct *)arg;
+
+ if (put_user(cnow.frame, &p_cuser->frame) || put_user(cnow.brk, &p_cuser->brk) ||
+ put_user(cnow.overrun, &p_cuser->overrun) ||
+ put_user(cnow.buf_overrun, &p_cuser->buf_overrun) ||
+ put_user(cnow.parity, &p_cuser->parity) ||
+ put_user(cnow.rx, &p_cuser->rx) ||
+ put_user(cnow.tx, &p_cuser->tx)) {
+ ret = -EFAULT;
+ break;
+ }
+
+ put_user(cnow.cts, &p_cuser->cts);
+ put_user(cnow.dsr, &p_cuser->dsr);
+ put_user(cnow.rng, &p_cuser->rng);
+ put_user(cnow.dcd, &p_cuser->dcd);
+ break;
+ }
+ case TCXONC:
+ ret = tty_check_change(tty);
+ if (!ret) {
+ switch (arg) {
+ case TCOOFF:
+ ret = npreal_set_generic_command_done(info, ASPP_CMD_SETXOFF);
+ break;
+
+ case TCOON:
+ ret = npreal_set_generic_command_done(info, ASPP_CMD_SETXON);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+ }
+ break;
+
+ default:
+ ret = -ENOIOCTLCMD;
+ }
+ return ret;
+}
+
+/**
+ * npreal_set_termios() - Allows tty driver notified when device's termios
+ * @tty: pointer for struct tty_struct
+ * @old_termios: pointer for struct ktermios
+ *
+ * This routine allows the tty driver to be notified when device's termios
+ * settings have changed and set tty port parameters.
+ *
+ * A well-designed tty driver should be prepared to accept the case
+ * where old == NULL, and try to do something rational.
+ */
+static void npreal_set_termios(struct tty_struct *tty,
+ struct ktermios *old_termios)
+{
+ struct npreal_struct *info = (struct npreal_struct *)tty->driver_data;
+
+ npreal_port_init(info, old_termios);
+}
+
+static void npreal_hangup(struct tty_struct *tty)
+{
+ struct npreal_struct *info = (struct npreal_struct *)tty->driver_data;
+
+ /* Prevent race condition on closing */
+ if (!info || (info->flags & ASYNC_CLOSING))
+ return;
+
+ info->flags |= ASYNC_CLOSING;
+ npreal_shutdown(info);
+}
+
+static int npreal_tiocmget(struct tty_struct *tty)
+{
+ struct npreal_struct *info = (struct npreal_struct *)tty->driver_data;
+
+ if (!info)
+ return -EINVAL;
+ if (tty->index == NPREAL_PORTS)
+ return -ENOIOCTLCMD;
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return -EIO;
+
+ return ((info->modem_control & UART_MCR_RTS) ? TIOCM_RTS : 0) |
+ ((info->modem_control & UART_MCR_DTR) ? TIOCM_DTR : 0) |
+ ((info->modem_status & UART_MSR_DCD) ? TIOCM_CAR : 0) |
+ ((info->modem_status & UART_MSR_RI) ? TIOCM_RNG : 0) |
+ ((info->modem_status & UART_MSR_DSR) ? TIOCM_DSR : 0) |
+ ((info->modem_status & UART_MSR_CTS) ? TIOCM_CTS : 0);
+}
+
+static int npreal_tiocmset(struct tty_struct *tty, unsigned int set,
+ unsigned int clear)
+{
+ struct npreal_struct *info = (struct npreal_struct *)tty->driver_data;
+ struct nd_struct *nd;
+
+ if (!info)
+ return -EINVAL;
+
+ nd = info->net_node;
+ if (!nd)
+ return -EINVAL;
+
+ if (tty->index == NPREAL_PORTS)
+ return -ENOIOCTLCMD;
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return -EIO;
+
+ if (set & TIOCM_RTS)
+ info->modem_control |= UART_MCR_RTS;
+ if (set & TIOCM_DTR)
+ info->modem_control |= UART_MCR_DTR;
+ if (clear & TIOCM_RTS)
+ info->modem_control &= ~UART_MCR_RTS;
+ if (clear & TIOCM_DTR)
+ info->modem_control &= ~UART_MCR_DTR;
+
+ return npreal_linectrl(nd, info->modem_control);
+}
+
+static const struct tty_operations mpvar_ops = {
+ .open = npreal_open,
+ .close = npreal_close,
+ .write = npreal_write,
+ .put_char = npreal_put_char,
+ .write_room = npreal_write_room,
+ .chars_in_buffer = npreal_chars_in_buffer,
+ .flush_buffer = npreal_flush_buffer,
+ .wait_until_sent = npreal_wait_until_sent,
+ .break_ctl = npreal_break,
+ .ioctl = npreal_ioctl,
+ .throttle = npreal_throttle,
+ .unthrottle = npreal_unthrottle,
+ .set_termios = npreal_set_termios,
+ .hangup = npreal_hangup,
+ .tiocmget = npreal_tiocmget,
+ .tiocmset = npreal_tiocmset,
+};
+
+static int npreal_net_open(struct inode *inode, struct file *file)
+{
+ struct nd_struct *nd;
+ int rtn = 0;
+
+ try_module_get(THIS_MODULE);
+ if (!capable(CAP_SYS_ADMIN)) {
+ rtn = -EPERM;
+ goto done;
+ }
+
+ if (file->private_data) {
+ rtn = -EINVAL;
+ goto done;
+ }
+
+ nd = (struct nd_struct *)PDE_DATA(inode);
+ if (!nd) {
+ rtn = -ENXIO;
+ goto done;
+ }
+
+ down(&nd->semaphore);
+
+ if (nd->flag & NPREAL_NET_NODE_OPENED) {
+ rtn = -EBUSY;
+ goto unlock;
+ }
+
+ nd->flag |= NPREAL_NET_NODE_OPENED;
+ nd->tx_ready = 0;
+ nd->rx_ready = 1;
+ nd->cmd_ready = 0;
+ tty_register_device(npvar_sdriver, nd->tty_node->port, NULL);
+
+unlock:
+ up(&nd->semaphore);
+ file->private_data = (void *)nd;
+done:
+ if (rtn)
+ module_put(THIS_MODULE);
+
+ return rtn;
+}
+
+static int npreal_net_close(struct inode *inode, struct file *file)
+{
+ struct nd_struct *nd;
+
+ nd = (struct nd_struct *)(file->private_data);
+ if (!nd)
+ goto done;
+
+ /* This flag will be checked when npreal_net_open() is called again. */
+ nd->flag &= ~NPREAL_NET_NODE_OPENED;
+ tty_unregister_device(npvar_sdriver, nd->tty_node->port);
+
+done:
+ file->private_data = NULL;
+ module_put(THIS_MODULE);
+ return 0;
+}
+
+static unsigned int npreal_net_select(struct file *file, struct poll_table_struct *table)
+{
+ struct nd_struct *nd = file->private_data;
+ unsigned int retval = 0;
+
+ if (!nd)
+ return retval;
+
+ poll_wait(file, &nd->select_in_wait, table);
+ poll_wait(file, &nd->select_out_wait, table);
+ poll_wait(file, &nd->select_ex_wait, table);
+
+ if (nd->tx_ready)
+ retval |= POLLIN | POLLRDNORM;
+ if (nd->rx_ready)
+ retval |= POLLOUT | POLLWRNORM;
+ if (nd->cmd_ready)
+ retval |= POLLPRI;
+
+ return retval;
+}
+
+static long npreal_net_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct nd_struct *nd = file->private_data;
+ int ret = 0;
+ int size, len;
+
+ if (!nd) {
+ ret = -ENXIO;
+ return ret;
+ }
+
+ size = _IOC_SIZE(cmd);
+
+ switch (_IOC_NR(cmd)) {
+ case NPREAL_NET_CMD_RETRIEVE:
+ if (!nd->cmd_ready) {
+ pr_err("(P%02d) NET_CMD_RETRIEVE Error @ %d\n", nd->tty_node->port,
+ __LINE__);
+ ret = -ENXIO;
+ break;
+ }
+
+ if (nd->flag & NPREAL_NET_DO_SESSION_RECOVERY)
+ len = nd->do_session_recovery_len;
+ else
+ len = (int)nd->cmd_buffer[2] + 3;
+
+ if (copy_to_user((void *)arg, (void *)nd->cmd_buffer, len)) {
+ pr_err("(P%02d) NET_CMD_RETRIEVE Error @ %d\n", nd->tty_node->port,
+ __LINE__);
+ ret = -EFAULT;
+ break;
+ }
+
+ nd->cmd_buffer[0] = 0;
+ ret = len;
+ nd->cmd_ready = 0;
+ break;
+
+ case NPREAL_NET_CMD_RESPONSE: {
+ unsigned char rsp_buffer[84];
+
+ if (size < 2)
+ break;
+
+ if (size > 84)
+ size = 84;
+
+ if (copy_from_user((void *)rsp_buffer, (void *)arg, size)) {
+ ret = -EFAULT;
+ break;
+ }
+
+ if (rsp_buffer[0] == NPREAL_LOCAL_COMMAND_SET) {
+ down(&nd->cmd_semaphore);
+ memcpy(nd->rsp_buffer, rsp_buffer, size);
+ nd->rsp_length = size;
+ nd->cmd_rsp_flag = 1;
+ up(&nd->cmd_semaphore);
+ /* used waitqueue_active() is safe because
+ * waitqueue_active() is executed before
+ * setting nd->cmd_rsp_flag
+ */
+ if (waitqueue_active(&nd->cmd_rsp_wait))
+ wake_up_interruptible(&nd->cmd_rsp_wait);
+
+ break;
+ }
+
+ down(&nd->cmd_semaphore);
+ if (nd->flag & NPREAL_NET_DO_SESSION_RECOVERY) {
+ if (rsp_buffer[1] == ASPP_CMD_LINECTRL) {
+ nd->flag &= ~NPREAL_NET_DO_SESSION_RECOVERY;
+ up(&nd->cmd_semaphore);
+ break;
+
+ } else if (rsp_buffer[1] == ASPP_CMD_PORT_INIT) {
+ int state = 0;
+ struct npreal_struct *info;
+
+ up(&nd->cmd_semaphore);
+ info = nd->tty_node;
+ if (!info || size != 6 || rsp_buffer[2] != 3)
+ break;
+
+ if (rsp_buffer[3])
+ state |= UART_MSR_DSR;
+ if (rsp_buffer[4])
+ state |= UART_MSR_CTS;
+ if (rsp_buffer[5])
+ state |= UART_MSR_DCD;
+ npreal_check_modem_status(info, state);
+ } else {
+ up(&nd->cmd_semaphore);
+ break;
+ }
+
+ } else {
+ up(&nd->cmd_semaphore);
+ }
+
+ if (rsp_buffer[1] == ASPP_NOTIFY) {
+ npreal_process_notify(nd, rsp_buffer, size);
+ } else if (rsp_buffer[1] == ASPP_CMD_WAIT_OQUEUE) {
+ if (size == 5) {
+ memcpy(nd->rsp_buffer, rsp_buffer, size);
+ nd->oqueue = rsp_buffer[4] * 16 + rsp_buffer[3];
+ nd->rsp_length = size;
+ nd->wait_oqueue_responsed = 1;
+ down(&nd->cmd_semaphore);
+ nd->cmd_rsp_flag = 1;
+ up(&nd->cmd_semaphore);
+ /* used waitqueue_active() is safe because
+ * waitqueue_active() is executed before
+ * setting nd->cmd_rsp_flag
+ */
+ if (waitqueue_active(&nd->cmd_rsp_wait))
+ wake_up_interruptible(&nd->cmd_rsp_wait);
+ }
+ } else {
+ down(&nd->cmd_semaphore);
+ memcpy(nd->rsp_buffer, rsp_buffer, size);
+ nd->rsp_length = size;
+ nd->cmd_rsp_flag = 1;
+ up(&nd->cmd_semaphore);
+ /* used waitqueue_active() is safe because
+ * waitqueue_active() is executed before
+ * setting nd->cmd_rsp_flag
+ */
+ if (waitqueue_active(&nd->cmd_rsp_wait))
+ wake_up_interruptible(&nd->cmd_rsp_wait);
+ }
+
+ break;
+ }
+
+ case NPREAL_NET_CONNECTED: {
+ struct npreal_struct *info;
+
+ info = nd->tty_node;
+ if (!info)
+ break;
+
+ if (nd->flag & NPREAL_NET_NODE_DISCONNECTED) {
+ nd->flag &= ~NPREAL_NET_NODE_DISCONNECTED;
+ nd->flag |= NPREAL_NET_NODE_CONNECTED;
+ npreal_do_session_recovery(info);
+ }
+
+ break;
+ }
+
+ case NPREAL_NET_DISCONNECTED: {
+ nd->flag &= ~NPREAL_NET_NODE_CONNECTED;
+ nd->flag |= NPREAL_NET_NODE_DISCONNECTED;
+ nd->flag &= ~NPREAL_NET_TTY_INUSED;
+ nd->wait_oqueue_responsed = 1;
+ down(&nd->cmd_semaphore);
+ nd->cmd_rsp_flag = 1;
+ up(&nd->cmd_semaphore);
+ /* used waitqueue_active() is safe because
+ * waitqueue_active() is executed before
+ * setting nd->cmd_rsp_flag
+ */
+ if (waitqueue_active(&nd->cmd_rsp_wait))
+ wake_up_interruptible(&nd->cmd_rsp_wait);
+ break;
+
+ }
+
+ case NPREAL_NET_GET_TTY_STATUS: {
+ int status;
+
+ if (size != sizeof(status))
+ break;
+
+ status = (nd->flag & NPREAL_NET_TTY_INUSED) ? 1 : 0;
+ if (copy_to_user((void *)arg, (void *)&status, size))
+ ret = -EFAULT;
+
+ break;
+ }
+
+ case NPREAL_NET_SETTING: {
+ struct server_setting_struct settings;
+ struct npreal_struct *info;
+
+ info = nd->tty_node;
+ if (!info || size != sizeof(struct server_setting_struct))
+ break;
+
+ if (copy_from_user((void *)&settings, (void *)arg, size)) {
+ ret = -EFAULT;
+ break;
+
+ }
+
+ if ((settings.server_type == DE311) || (settings.server_type == DE301) ||
+ (settings.server_type == DE302) || (settings.server_type == DE304) ||
+ (settings.server_type == DE331) || (settings.server_type == DE332) ||
+ (settings.server_type == DE334) || (settings.server_type == DE303) ||
+ (settings.server_type == DE308) || (settings.server_type == DE309) ||
+ (settings.server_type == CN2100) || (settings.server_type == CN2500))
+ nd->server_type = settings.server_type;
+
+ if (settings.disable_fifo)
+ info->type = PORT_16450;
+ else
+ info->type = PORT_16550A;
+
+ set_common_xmit_fifo_size(info, nd);
+
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * npreal_net_read() - This routine is called when NPreal daemon calls read
+ * @file: pointer for struct file
+ * @buf: buffer of user space
+ * @count: number of data in buf
+ * @ppos: long offset for start position the file
+ *
+ * This function is called when host wants to write data to tty device
+ * (NPreal server device) by /proc/npreal2, and put data to userpace from
+ * kernel space.
+ *
+ * (tty->ldisc->ops->write_wakeup)(tty) should be executed without soft
+ * irq.
+ *
+ * positive val is return if host write number of data successfully.
+ * Otheriwse, it's failed.
+ *
+ */
+static ssize_t npreal_net_read(struct file *file, char *buf, size_t count, loff_t *ppos)
+{
+ struct nd_struct *nd = file->private_data;
+ struct npreal_struct *info;
+ struct tty_struct *tty;
+ ssize_t rtn = 0;
+ int min_val;
+ unsigned long flags;
+
+
+ info = (struct npreal_struct *)nd->tty_node;
+ tty = info->tty;
+
+ if (!nd || !info || !tty) {
+ rtn = -ENXIO;
+ return rtn;
+ }
+
+ if (info->x_char) {
+ rtn = 1;
+ if (copy_to_user(buf, &info->x_char, rtn)) {
+ rtn = -EFAULT;
+ return rtn;
+ }
+
+ info->x_char = 0;
+ spin_lock_irqsave(&info->tx_lock, flags);
+ info->icount.tx++;
+ spin_unlock_irqrestore(&info->tx_lock, flags);
+ return rtn;
+ }
+
+ spin_lock_irqsave(&info->tx_lock, flags);
+ if (!info->xmit_buf || info->xmit_cnt <= 0) {
+ rtn = 0;
+ spin_unlock_irqrestore(&info->tx_lock, flags);
+ return rtn;
+ }
+
+ spin_unlock_irqrestore(&info->tx_lock, flags);
+
+ while (count) {
+ min_val = min_t(int, (int)count, min(info->xmit_cnt,
+ (int)(SERIAL_XMIT_SIZE - info->xmit_tail)));
+
+ if (min_val <= 0)
+ break;
+
+ if (copy_to_user(buf + rtn, info->xmit_buf + info->xmit_tail, min_val)) {
+ rtn = -EFAULT;
+ return rtn;
+ }
+
+ rtn += min_val;
+ count -= min_val;
+
+ spin_lock_irqsave(&info->tx_lock, flags);
+ info->xmit_cnt -= min_val;
+ info->xmit_tail += min_val;
+ info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE - 1);
+ info->icount.tx += min_val;
+ spin_unlock_irqrestore(&info->tx_lock, flags);
+ }
+
+ if (info->xmit_cnt <= 0) {
+ nd->tx_ready = 0;
+ } else {
+ nd->tx_ready = 1;
+ smp_mb(); /* use smp_mb() with waitqueue_active() */
+ /* used waitqueue_active() is safe because smp_mb() is used */
+ if (waitqueue_active(&nd->select_in_wait))
+ wake_up_interruptible(&nd->select_in_wait);
+ }
+
+ if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc->ops->write_wakeup)
+ (tty->ldisc->ops->write_wakeup)(tty);
+
+ wake_up_interruptible(&tty->write_wait);
+
+ return rtn;
+}
+
+/**
+ * npreal_net_write() - Called when NPreal daemon calls write
+ * @file: pointer for struct file
+ * @buf: buffer of user space
+ * @count: number of data in buf
+ * @ppos: long offset for start position the file
+ *
+ * This function is called when host reads data from tty device
+ * (NPreal server device) by /proc/npreal2, and put data put into kernel
+ * space. The receive buffer will overrun,as the TTY_THRESHOLD_THROTTLE is
+ * 128.
+ *
+ * A zero is returned if failure; positive val is return if host reads data
+ * from tty device.
+ *
+ */
+
+static ssize_t npreal_net_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
+{
+ struct nd_struct *nd = file->private_data;
+ struct npreal_struct *info;
+ struct tty_struct *tty;
+ unsigned char *k_buf = NULL;
+ ssize_t rtn = 0;
+ int cnt;
+ unsigned long tmp;
+
+ if (!buf) {
+ rtn = count;
+ goto done;
+ }
+
+ k_buf = kmalloc_array(count, sizeof(unsigned char), GFP_KERNEL);
+ info = (struct npreal_struct *)nd->tty_node;
+ tmp = copy_from_user(k_buf, buf, count);
+ if ((k_buf == NULL || tmp) || (!nd || !info) || (info->flags & ASYNC_CLOSING)) {
+ rtn = count;
+ goto done;
+ }
+
+
+ down(&info->rx_semaphore);
+ tty = info->tty;
+ if (!tty || !info->ttyPort.low_latency || test_bit(TTY_IO_ERROR, &tty->flags)) {
+ rtn = count;
+ up(&info->rx_semaphore);
+ goto done;
+ }
+
+ if (!nd->rx_ready) {
+ up(&info->rx_semaphore);
+ goto done;
+ }
+
+ cnt = tty_buffer_request_room(&info->ttyPort, count);
+ if (cnt <= 0) {
+ /* Doing throttle here,because that it will spent times
+ * for upper layer driver to throttle and the application
+ * may call write so many times but just can not write.
+ * If we are doing input canonicalization, and there are no
+ * pending newlines, let characters through without limit, so
+ * that erase characters will be handled. Other excess
+ * characters will be beeped.
+ */
+ up(&info->rx_semaphore);
+ goto done;
+ }
+
+ count = tty_insert_flip_string(&info->ttyPort, (unsigned char *)k_buf, cnt);
+ if (count) {
+ tty_flip_buffer_push(&info->ttyPort);
+ rtn = count;
+ up(&info->rx_semaphore);
+ goto done;
+ }
+
+ up(&info->rx_semaphore);
+ schedule_work(&info->process_flip_tqueue);
+done:
+ kfree(k_buf);
+ return rtn;
+}
+
+static const struct proc_ops npreal_net_fops = {
+ .proc_open = npreal_net_open,
+ .proc_read = npreal_net_read,
+ .proc_write = npreal_net_write,
+ .proc_ioctl = npreal_net_ioctl,
+ .proc_release = npreal_net_close,
+ .proc_poll = npreal_net_select,
+};
+
+static int __init npreal2_module_init(void)
+{
+ int i, retval;
+
+ npvar_sdriver = alloc_tty_driver(NPREAL_PORTS);
+ if (!npvar_sdriver)
+ return -ENOMEM;
+
+ npvar_sdriver->name = "ttyr";
+ npvar_sdriver->major = ttymajor;
+ npvar_sdriver->minor_start = 0;
+ npvar_sdriver->type = TTY_DRIVER_TYPE_SERIAL;
+ npvar_sdriver->subtype = SERIAL_TYPE_NORMAL;
+ npvar_sdriver->init_termios = tty_std_termios;
+ npvar_sdriver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ npvar_sdriver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
+
+ npvar_table = kmalloc_array(NPREAL_PORTS, sizeof(struct npreal_struct), GFP_KERNEL);
+ if (npvar_table == NULL)
+ return -1;
+
+ npvar_net_nodes = kmalloc_array(NPREAL_PORTS, sizeof(struct nd_struct), GFP_KERNEL);
+ if (npvar_net_nodes == NULL) {
+ kfree(npvar_table);
+ return -1;
+ }
+
+ tty_set_operations(npvar_sdriver, &mpvar_ops);
+ memset(npvar_table, 0, NPREAL_PORTS * sizeof(struct npreal_struct));
+
+ for (i = 0; i < NPREAL_PORTS; i++) {
+ tty_port_init(&npvar_table[i].ttyPort);
+ tty_port_link_device(&npvar_table[i].ttyPort, npvar_sdriver, i);
+ }
+
+ retval = tty_register_driver(npvar_sdriver);
+ if (retval) {
+ pr_err("Couldn't install MOXA Async/NPort server family driver !\n");
+ put_tty_driver(npvar_sdriver);
+ return -1;
+ }
+
+ retval = npreal_init(npvar_table, npvar_net_nodes);
+ if (retval) {
+ tty_unregister_driver(npvar_sdriver);
+ pr_err("Couldn't install MOXA Async/NPort server family Real TTY driver !\n");
+ return -1;
+ }
+
+ pr_info("MOXA Nport driver version %s\n", NPREAL_VERSION);
+ return 0;
+}
+
+static void __exit npreal2_module_exit(void)
+{
+ struct npreal_struct *info;
+ struct proc_dir_entry *de;
+ int i;
+
+ info = &npvar_table[0];
+ for (i = 0; i < NPREAL_PORTS && (info->net_node != NULL); i++, info++) {
+ if (info->net_node->flag & NPREAL_NET_NODE_OPENED)
+ tty_unregister_device(npvar_sdriver, i);
+
+ de = info->net_node->node_entry;
+ if (de)
+ npreal_remove_proc_entry(de, i);
+
+ info->net_node->node_entry = NULL;
+ }
+
+ if (npvar_proc_root) {
+ npreal_remove_proc_entry(npvar_proc_root, 404);
+ npvar_proc_root = NULL;
+ }
+
+ if (tty_unregister_driver(npvar_sdriver))
+ pr_err("Couldn't unregister MOXA Async/NPort server family Real TTY driver\n");
+
+ put_tty_driver(npvar_sdriver);
+ kfree(npvar_table);
+ kfree(npvar_net_nodes);
+}
+
+module_init(npreal2_module_init);
+module_exit(npreal2_module_exit);
diff --git a/drivers/tty/npreal2.h b/drivers/tty/npreal2.h
new file mode 100644
index 000000000000..523bcd5341ff
--- /dev/null
+++ b/drivers/tty/npreal2.h
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+#ifndef _NPREAL2_H
+#define _NPREAL2_H
+
+#define NPREAL_VERSION "v5.0"
+#define VERSION_CODE(ver, rel, seq) ((ver << 16) | (rel << 8) | seq)
+
+#define SERIAL_DO_RESTART 1
+#define OFFLINE_POLLING 0
+#define NPREAL_EVENT_TXLOW 1
+#define NPREAL_EVENT_HANGUP 2
+#define SERIAL_TYPE_NORMAL 1
+#define SERIAL_TYPE_CALLOUT 2
+#define WAKEUP_CHARS 256
+#ifndef MAX_SCHEDULE_TIMEOUT
+#define MAX_SCHEDULE_TIMEOUT ((long)(~0UL>>1))
+#endif
+/* dynamic allocation of the major device number */
+#define NPREALMAJOR 0
+#define NPREAL_PORTS 256
+
+#define DE211 211
+#define DE301 301
+#define DE302 302
+#define DE303 303
+#define DE304 304
+#define DE308 308
+#define DE309 309
+#define DE311 311
+#define DE331 331
+#define DE332 332
+#define DE334 334
+#define CN2100 2100
+#define CN2500 2500
+
+#ifndef B921600
+#define B921600 (B460800 + 1)
+#endif
+
+#define NPREAL_ASPP_COMMAND_SET 1
+#define NPREAL_LOCAL_COMMAND_SET 2
+
+/* local command set */
+#define LOCAL_CMD_TTY_USED 1
+#define LOCAL_CMD_TTY_UNUSED 2
+#define NPREAL_NET_CONNECTED 3
+#define NPREAL_NET_DISCONNECTED 4
+#define NPREAL_NET_SETTING 5
+#define NPREAL_NET_GET_TTY_STATUS 6
+
+#define NPREAL_CMD_TIMEOUT (10*HZ)
+#define NPREAL_CMD_TRY 10
+
+#define NPREAL_NET_CMD_RETRIEVE 1
+#define NPREAL_NET_CMD_RESPONSE 2
+
+/* proc node is opened */
+#define NPREAL_NET_NODE_OPENED 0x01
+#define NPREAL_NET_NODE_CONNECTED 0x02
+#define NPREAL_NET_NODE_DISCONNECTED 0x04
+#define NPREAL_NET_DO_SESSION_RECOVERY 0x08
+#define NPREAL_NET_DO_INITIALIZE 0x10
+/* tty port is opened */
+#define NPREAL_NET_TTY_INUSED 0x20
+
+/* ASPP command set */
+#define ASPP_NOTIFY 0x26
+#define ASPP_NOTIFY_PARITY 0x01
+#define ASPP_NOTIFY_FRAMING 0x02
+#define ASPP_NOTIFY_HW_OVERRUN 0x04
+#define ASPP_NOTIFY_SW_OVERRUN 0x08
+#define ASPP_NOTIFY_BREAK 0x10
+#define ASPP_NOTIFY_MSR_CHG 0x20
+
+#define ASPP_CMD_IOCTL 16
+#define ASPP_CMD_FLOWCTRL 17
+#define ASPP_CMD_LINECTRL 18
+#define ASPP_CMD_LSTATUS 19
+#define ASPP_CMD_FLUSH 20
+#define ASPP_CMD_IQUEUE 21
+#define ASPP_CMD_OQUEUE 22
+#define ASPP_CMD_SETBAUD 23
+#define ASPP_CMD_XONXOFF 24
+#define ASPP_CMD_PORT_RESET 32
+#define ASPP_CMD_START_BREAK 33
+#define ASPP_CMD_STOP_BREAK 34
+#define ASPP_CMD_START_NOTIFY 36
+#define ASPP_CMD_STOP_NOTIFY 37
+#define ASPP_CMD_HOST 43
+#define ASPP_CMD_PORT_INIT 44
+#define ASPP_CMD_RESENT_TIME 46
+#define ASPP_CMD_WAIT_OQUEUE 47
+#define ASPP_CMD_TX_FIFO 48
+#define ASPP_CMD_SETXON 51
+#define ASPP_CMD_SETXOFF 52
+
+#define ASPP_FLUSH_RX_BUFFER 0
+#define ASPP_FLUSH_TX_BUFFER 1
+#define ASPP_FLUSH_ALL_BUFFER 2
+
+#define ASPP_IOCTL_B300 0
+#define ASPP_IOCTL_B600 1
+#define ASPP_IOCTL_B1200 2
+#define ASPP_IOCTL_B2400 3
+#define ASPP_IOCTL_B4800 4
+#define ASPP_IOCTL_B7200 5
+#define ASPP_IOCTL_B9600 6
+#define ASPP_IOCTL_B19200 7
+#define ASPP_IOCTL_B38400 8
+#define ASPP_IOCTL_B57600 9
+#define ASPP_IOCTL_B115200 10
+#define ASPP_IOCTL_B230400 11
+#define ASPP_IOCTL_B460800 12
+#define ASPP_IOCTL_B921600 13
+#define ASPP_IOCTL_B150 14
+#define ASPP_IOCTL_B134 15
+#define ASPP_IOCTL_B110 16
+#define ASPP_IOCTL_B75 17
+#define ASPP_IOCTL_B50 18
+
+#define ASPP_IOCTL_BITS5 0
+#define ASPP_IOCTL_BITS6 1
+#define ASPP_IOCTL_BITS7 2
+#define ASPP_IOCTL_BITS8 3
+
+#define ASPP_IOCTL_STOP1 0
+#define ASPP_IOCTL_STOP2 4
+
+#define ASPP_IOCTL_NONE 0
+#define ASPP_IOCTL_EVEN 8
+#define ASPP_IOCTL_ODD 16
+#define ASPP_IOCTL_MARK 24
+#define ASPP_IOCTL_SPACE 32
+
+#define RSP_BUFFER_SIZE 8
+#ifndef ASYNC_CALLOUT_ACTIVE
+#define ASYNC_CALLOUT_ACTIVE 0
+#endif
+
+#endif
--
2.20.1
\
 
 \ /
  Last update: 2020-07-14 08:25    [W:0.425 / U:0.320 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site