Subject: [PATCH] tty: Add MOXA NPort Real TTY Driver

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 <[email protected]>
Signed-off-by: Jason Chen <[email protected]>
Signed-off-by: Danny Lin <[email protected]>
Signed-off-by: Victor Yu <[email protected]>
---
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 ([email protected])
+ *
+ * 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("<[email protected]>");
+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


2020-07-14 07:12:24

by Greg KH

[permalink] [raw]
Subject: Re: [PATCH] tty: Add MOXA NPort Real TTY Driver

On Tue, Jul 14, 2020 at 06:24:42AM +0000, Johnson CH Chen (陳昭勳) wrote:
> 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.

A new serial driver, nice!

>
> 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 <[email protected]>
> Signed-off-by: Jason Chen <[email protected]>
> Signed-off-by: Danny Lin <[email protected]>
> Signed-off-by: Victor Yu <[email protected]>
> ---
> 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 ([email protected])
> + *
> + * 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;

Please do not do that, just use the normal dynamic debug logic that the
kernel has and that everyone uses. No per-driver type of debugging
functionality, as obviously that does not scale at all.

> +
> +MODULE_AUTHOR("<[email protected]>");

We need a real author name here if you want to be the maintainer.
Otherwise a support address can't be an author :)

> +MODULE_DESCRIPTION("MOXA Async/NPort Server Family Real TTY Driver");
> +module_param(ttymajor, int, 0);
> +module_param(verbose, int, 0644);

No module parameters please, they should not be needed at all.

> +MODULE_VERSION(NPREAL_VERSION);

No need for a driver version once the code is in the kernel tree, as the
kernel version is what matters.

> +MODULE_LICENSE("GPL");
> +
> +struct server_setting_struct {
> + int32_t server_type;
> + int32_t disable_fifo;

Please use kernel types, like u32, instead of userspace types like
these.

> +};
> +
> +struct npreal_struct {
> + struct tty_port ttyPort;
> + struct work_struct tqueue;
> + struct work_struct process_flip_tqueue;

None of these should be pointers?

> + 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 */

enumerated 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;

Do you really need these? Why not just use a ringbuffer structure from
the kernel?

> + 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;

2 locks in the same structure???

> + 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;

That's a lot of different queues a specific thing can be on at the same
time. Are you sure you want that to happen?

> + int32_t server_type;

Enumerated 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;

Serial port drivers should never create /proc files. If you want
debugging stuff, use debugfs.

> +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() */

Huh? Why is that needed?

> + /* used waitqueue_active() is safe because smp_mb() is used */

Why?

Are you _sure_ you need that barrier? If so, please document it really
really really well for you to be able to understand it in 10 years when
people have questions about it :)

> +static int npreal_chars_in_buffer(struct tty_struct *tty)
> +{
> + struct npreal_struct *info = (struct npreal_struct *)tty->driver_data;
> +
> + if (!info)
> + return -EIO;

How can that ever happen?

> +
> + return info->xmit_cnt;
> +}


> +/**

No need for kernel doc format for static functions.

> + * 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);

Did you run sparse on this code? Please do so and fix up the
errors/warnings it gives you for stuff like this.

> +static int npreal_net_open(struct inode *inode, struct file *file)
> +{
> + struct nd_struct *nd;
> + int rtn = 0;
> +
> + try_module_get(THIS_MODULE);

That is racy and not needed and even wrong here.

Do not do this, it is not the way to correctly do it at all.

> + if (!capable(CAP_SYS_ADMIN)) {
> + rtn = -EPERM;
> + goto done;
> + }
> +
> + if (file->private_data) {

How could that ever happen?

> + 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);

No need to cast.

> + if (!nd)
> + goto done;
> +
> + /* This flag will be checked when npreal_net_open() is called again. */
> + nd->flag &= ~NPREAL_NET_NODE_OPENED;

No locking?

> + tty_unregister_device(npvar_sdriver, nd->tty_node->port);
> +
> +done:
> + file->private_data = NULL;
> + module_put(THIS_MODULE);

again, not needed.

> + 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:

Do not make up custom ioctls for a single serial driver, just use the
default ones, that should be all that is needed, correct?

If not, what do you need to do that the serial layer does not provide
for you?

> +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);

No need to be noisy if all works properly, a driver should be silent.

But why are you doing all of this initialization without actually
checking if your hardware is present in the system? Please use the
bus-specific logic that your hardware is on to properly set things up in
the probe function, not all in the module init function, as this will
probably cause failures if loaded on a system without the hardware,
right?

And what causes the module to properly load automatically? I missed
that logic somewhere in here, where is it?

> --- /dev/null
> +++ b/drivers/tty/npreal2.h

No need for a .h file for a single .c file.

thanks,

greg k-h

2020-07-14 07:38:56

by Greg KH

[permalink] [raw]
Subject: Re: [PATCH] tty: Add MOXA NPort Real TTY Driver

On Tue, Jul 14, 2020 at 06:24:42AM +0000, Johnson CH Chen (陳昭勳) wrote:
> 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 <[email protected]>
> Signed-off-by: Jason Chen <[email protected]>
> Signed-off-by: Danny Lin <[email protected]>
> Signed-off-by: Victor Yu <[email protected]>
> ---
> 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 ([email protected])
> + *
> + * 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("<[email protected]>");
> +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];

You seem to have two "static" buffers here, for your device, that you
semi-randomly write to all over the place, but I can't find any locking
or coordination between things that prevents multiple commands from not
just overwritting each other.

Also, how does the data get sent to the hardware at all? I see
cmd_buffer[] being written to, but what reads from it and how does the
hardware get the data?

What am I missing here?

thanks,

greg k-h

2020-07-14 09:35:25

by Jiri Slaby

[permalink] [raw]
Subject: Re: [PATCH] tty: Add MOXA NPort Real TTY Driver

On 14. 07. 20, 8:24, Johnson CH Chen (???L??) wrote:
> 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.
...
> Signed-off-by: Johnson Chen <[email protected]>
> Signed-off-by: Jason Chen <[email protected]>
> Signed-off-by: Danny Lin <[email protected]>
> Signed-off-by: Victor Yu <[email protected]>
> ---
...
> --- 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"

MOXA_SMARTIO is lexicographically after MOXA_NPORT_REAL_TTY. So move
this before MOXA_SMARTIO.

> + 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

The same.

> 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 @@
...
> +#include <linux/delay.h>
> +#include <linux/errno.h>
> +#include <linux/fcntl.h>
> +#include <linux/version.h>

What do you need version.h for? Are you sure, you need all these headers?

> +#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;

You want dynamic major anyway. So kill this all.

> +static int verbose = 1;
> +
> +MODULE_AUTHOR("<[email protected]>");
> +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;

callout in 2020?

> + /* kernel counters for the 4 input interrupts */
> + struct async_icount icount;
> + struct semaphore rx_semaphore;

semaphores in new code? You have to explain why are you not using
simpler and faset mutexes.

> + struct nd_struct *net_node;
> + struct tty_struct *tty;
> + struct pid *session;
> + struct pid *pgrp;

Why does a driver need to care about pgrp? And session? You should kill
all these set, but unused fields. Note that you should also use fields
from struct tty_port instead of the duplicates here.

> + 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;

ringbuf (as Greg suggests) or kfifo.

> + /*
> + * We use spin_lock_irqsave instead of semaphonre here.

"semaphonre"?

> + * 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

"interrpute"?

> + * 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;

Could you reorder the code, so that you don't need these forward
declarations?

> +static void npreal_do_softint(struct work_struct *work)
> +{

Well, this is the old way of doing things.

> + 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)) {

Do you ever set that flag?

> + 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);

Have you checked what tty_hangup actually does? Drop this whole function.

> + }
> + }
> +}
> +
> +/**
> + * 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)
> +{

Ugh, the same as above, drop this and call flip directly.

> + 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);

This is very bad.

This calls for wait_event_interruptible or alike. "jiffies - et >= 0"
is broken in any case. time_after() is your friend.

> + } 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)) {

You should break the loop here and do the processing below after the
loop. Making thuse the loop minimalistic.

> + 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;

wait_event_interruptible_timeout already returns what you compute here
in a complicated way, IIUC.

> + } 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;

Or simply:
nd->cmd_buffer[5] = !!(info->modem_control & UART_MCR_DTR);
In all of them below:

> + 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;

What is this cmd_buffer good for actually? Only to let the user know?
Then -- drop it.

> + 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_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'))

Too many parentheses in all these functions.

> + 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);

Why not tty_wakeup?

> +
> + 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;

Again this beast.

> + } else {
> + return nd->oqueue;
> + }
> + } while (timeout > 0);
> +
> + return -EIO;
> +}
...
> +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;

Why those L (long) suffixes when you assign to an int? Why is not the
int unsigned?

> + baudIndex = ASPP_IOCTL_B921600;
> + break;
> +
> + case B460800:
> + baud = 460800;
> + baudIndex = ASPP_IOCTL_B460800;
> + break;
> +
> + case B230400:
> + baud = 230400L;
> + baudIndex = ASPP_IOCTL_B230400;
> + 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)

Why are you not using tty_port_block_til_ready?

> +{
> + 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;

Do I have deja-vu? Why all this duplicated code?

> + case B460800:
> + baud = 460800;
> + baudIndex = ASPP_IOCTL_B460800;
> + break;
...
> +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;

And this duplicated code?

> + 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();

Nah. wait_event and friends.

> + }
> +
> + 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);

All these random schedules needs to go.

> + }
> + }
> +
> + 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);

So msleep((NPREAL_CMD_TRY-retry)*500)?

> + 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))

Why do you actully do these racy waitqueue_active checks all over the code?

> + wake_up_interruptible(&nd->select_out_wait);
> +}
...
> +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);

You already know what, right?

> + }
> +
> + 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;

0 is not a pointer.

> + 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);
> +}
...
> +static int npreal_open(struct tty_struct *tty, struct file *filp)
> +{

Why not to use tty_port_open?

> +}
...
> +static void npreal_close(struct tty_struct *tty, struct file *filp)
> +{

And tty_port_close?

> +}
...
> +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)));

Casts here only mean you have wrong types somewhere. If it must be (not
in this case), use min_t.

> + 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);

If you used modulo (which is what you want here), you need not decrement
by one, right?

> + 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_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:

You should not handle ioctl like these in the driver, should you?

> + 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;
...
> +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 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)) {

I.e. put_user.

...
> +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);

Is buf a user buffer? It is not marked as such. Ah, this is the proc
interface you should drop.

> + if ((k_buf == NULL || tmp) || (!nd || !info) || (info->flags & ASYNC_CLOSING)) {
> + rtn = count;
> + goto done;
> + }



Overall, you shall deduplicate the code and use tty_port helpers
wherever possible. This will simplify the code a lot. I wonder where you
get from this submission with "3194 insertions".

thanks,
--
js

2020-07-15 09:05:23

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH] tty: Add MOXA NPort Real TTY Driver

Hi "Johnson,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on tty/tty-testing]
[also build test WARNING on v5.8-rc5 next-20200714]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url: https://github.com/0day-ci/linux/commits/Johnson-CH-Chen/tty-Add-MOXA-NPort-Real-TTY-Driver/20200714-142712
base: https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty.git tty-testing
config: sparc64-randconfig-s032-20200715 (attached as .config)
compiler: sparc64-linux-gcc (GCC) 9.3.0
reproduce:
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# apt-get install sparse
# sparse version: v0.6.2-49-g707c5017-dirty
# save the attached .config to linux build tree
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-9.3.0 make.cross C=1 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__' ARCH=sparc64

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <[email protected]>


sparse warnings: (new ones prefixed by >>)

>> drivers/tty/npreal2.c:1107:26: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected void [noderef] __user *to @@ got struct serial_struct *retinfo @@
>> drivers/tty/npreal2.c:1107:26: sparse: expected void [noderef] __user *to
>> drivers/tty/npreal2.c:1107:26: sparse: got struct serial_struct *retinfo
>> drivers/tty/npreal2.c:1122:56: sparse: sparse: incorrect type in argument 2 (different address spaces) @@ expected void const [noderef] __user *from @@ got struct serial_struct *new_info @@
>> drivers/tty/npreal2.c:1122:56: sparse: expected void const [noderef] __user *from
>> drivers/tty/npreal2.c:1122:56: sparse: got struct serial_struct *new_info
>> drivers/tty/npreal2.c:1149:57: sparse: sparse: Using plain integer as NULL pointer
>> drivers/tty/npreal2.c:1186:9: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected void const volatile [noderef] __user * @@ got unsigned int *value @@
>> drivers/tty/npreal2.c:1186:9: sparse: expected void const volatile [noderef] __user *
>> drivers/tty/npreal2.c:1186:9: sparse: got unsigned int *value
drivers/tty/npreal2.c:1624:38: sparse: sparse: Using plain integer as NULL pointer
drivers/tty/npreal2.c:1897:34: sparse: sparse: Using plain integer as NULL pointer
drivers/tty/npreal2.c:1914:21: sparse: sparse: Using plain integer as NULL pointer
drivers/tty/npreal2.c:1984:46: sparse: sparse: Using plain integer as NULL pointer
>> drivers/tty/npreal2.c:2261:17: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected void const volatile [noderef] __user * @@ got unsigned long * @@
drivers/tty/npreal2.c:2261:17: sparse: expected void const volatile [noderef] __user *
>> drivers/tty/npreal2.c:2261:17: sparse: got unsigned long *
drivers/tty/npreal2.c:2265:17: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected void const volatile [noderef] __user * @@ got unsigned long * @@
drivers/tty/npreal2.c:2265:17: sparse: expected void const volatile [noderef] __user *
drivers/tty/npreal2.c:2265:17: sparse: got unsigned long *
>> drivers/tty/npreal2.c:2319:21: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected void const volatile [noderef] __user * @@ got int * @@
drivers/tty/npreal2.c:2319:21: sparse: expected void const volatile [noderef] __user *
>> drivers/tty/npreal2.c:2319:21: sparse: got int *
drivers/tty/npreal2.c:2319:62: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected void const volatile [noderef] __user * @@ got int * @@
drivers/tty/npreal2.c:2319:62: sparse: expected void const volatile [noderef] __user *
drivers/tty/npreal2.c:2319:62: sparse: got int *
drivers/tty/npreal2.c:2320:25: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected void const volatile [noderef] __user * @@ got int * @@
drivers/tty/npreal2.c:2320:25: sparse: expected void const volatile [noderef] __user *
drivers/tty/npreal2.c:2320:25: sparse: got int *
drivers/tty/npreal2.c:2321:25: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected void const volatile [noderef] __user * @@ got int * @@
drivers/tty/npreal2.c:2321:25: sparse: expected void const volatile [noderef] __user *
drivers/tty/npreal2.c:2321:25: sparse: got int *
drivers/tty/npreal2.c:2322:25: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected void const volatile [noderef] __user * @@ got int * @@
drivers/tty/npreal2.c:2322:25: sparse: expected void const volatile [noderef] __user *
drivers/tty/npreal2.c:2322:25: sparse: got int *
drivers/tty/npreal2.c:2323:25: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected void const volatile [noderef] __user * @@ got int * @@
drivers/tty/npreal2.c:2323:25: sparse: expected void const volatile [noderef] __user *
drivers/tty/npreal2.c:2323:25: sparse: got int *
drivers/tty/npreal2.c:2324:25: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected void const volatile [noderef] __user * @@ got int * @@
drivers/tty/npreal2.c:2324:25: sparse: expected void const volatile [noderef] __user *
drivers/tty/npreal2.c:2324:25: sparse: got int *
drivers/tty/npreal2.c:2329:17: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected void const volatile [noderef] __user * @@ got int * @@
drivers/tty/npreal2.c:2329:17: sparse: expected void const volatile [noderef] __user *
drivers/tty/npreal2.c:2329:17: sparse: got int *
drivers/tty/npreal2.c:2330:17: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected void const volatile [noderef] __user * @@ got int * @@
drivers/tty/npreal2.c:2330:17: sparse: expected void const volatile [noderef] __user *
drivers/tty/npreal2.c:2330:17: sparse: got int *
drivers/tty/npreal2.c:2331:17: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected void const volatile [noderef] __user * @@ got int * @@
drivers/tty/npreal2.c:2331:17: sparse: expected void const volatile [noderef] __user *
drivers/tty/npreal2.c:2331:17: sparse: got int *
drivers/tty/npreal2.c:2332:17: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected void const volatile [noderef] __user * @@ got int * @@
drivers/tty/npreal2.c:2332:17: sparse: expected void const volatile [noderef] __user *
drivers/tty/npreal2.c:2332:17: sparse: got int *
>> drivers/tty/npreal2.c:2570:35: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected void [noderef] __user *to @@ got void * @@
drivers/tty/npreal2.c:2570:35: sparse: expected void [noderef] __user *to
>> drivers/tty/npreal2.c:2570:35: sparse: got void *
>> drivers/tty/npreal2.c:2591:57: sparse: sparse: incorrect type in argument 2 (different address spaces) @@ expected void const [noderef] __user *from @@ got void * @@
drivers/tty/npreal2.c:2591:57: sparse: expected void const [noderef] __user *from
drivers/tty/npreal2.c:2591:57: sparse: got void *
drivers/tty/npreal2.c:2720:35: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected void [noderef] __user *to @@ got void * @@
drivers/tty/npreal2.c:2720:35: sparse: expected void [noderef] __user *to
drivers/tty/npreal2.c:2720:35: sparse: got void *
drivers/tty/npreal2.c:2734:56: sparse: sparse: incorrect type in argument 2 (different address spaces) @@ expected void const [noderef] __user *from @@ got void * @@
drivers/tty/npreal2.c:2734:56: sparse: expected void const [noderef] __user *from
drivers/tty/npreal2.c:2734:56: sparse: got void *
>> drivers/tty/npreal2.c:2803:34: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected void [noderef] __user *to @@ got char *buf @@
drivers/tty/npreal2.c:2803:34: sparse: expected void [noderef] __user *to
>> drivers/tty/npreal2.c:2803:34: sparse: got char *buf
>> drivers/tty/npreal2.c:2831:38: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected void [noderef] __user *to @@ got char * @@
drivers/tty/npreal2.c:2831:38: sparse: expected void [noderef] __user *to
>> drivers/tty/npreal2.c:2831:38: sparse: got char *
>> drivers/tty/npreal2.c:2899:38: sparse: sparse: incorrect type in argument 2 (different address spaces) @@ expected void const [noderef] __user *from @@ got char const *buf @@
drivers/tty/npreal2.c:2899:38: sparse: expected void const [noderef] __user *from
>> drivers/tty/npreal2.c:2899:38: sparse: got char const *buf
>> drivers/tty/npreal2.c:2950:22: sparse: sparse: incorrect type in initializer (incompatible argument 2 (different address spaces)) @@ expected long ( *proc_read )( ... ) @@ got long ( * )( ... ) @@
>> drivers/tty/npreal2.c:2950:22: sparse: expected long ( *proc_read )( ... )
>> drivers/tty/npreal2.c:2950:22: sparse: got long ( * )( ... )
>> drivers/tty/npreal2.c:2951:23: sparse: sparse: incorrect type in initializer (incompatible argument 2 (different address spaces)) @@ expected long ( *proc_write )( ... ) @@ got long ( * )( ... ) @@
>> drivers/tty/npreal2.c:2951:23: sparse: expected long ( *proc_write )( ... )
drivers/tty/npreal2.c:2951:23: sparse: got long ( * )( ... )
>> drivers/tty/npreal2.c:2954:22: sparse: sparse: incorrect type in initializer (different base types) @@ expected restricted __poll_t ( *proc_poll )( ... ) @@ got unsigned int ( * )( ... ) @@
>> drivers/tty/npreal2.c:2954:22: sparse: expected restricted __poll_t ( *proc_poll )( ... )
>> drivers/tty/npreal2.c:2954:22: sparse: got unsigned int ( * )( ... )

vim +1107 drivers/tty/npreal2.c

1089
1090 static int npreal_get_serial_info(struct npreal_struct *info, struct serial_struct *retinfo)
1091 {
1092 struct serial_struct tmp;
1093
1094 if (!retinfo)
1095 return -EFAULT;
1096
1097 memset(&tmp, 0, sizeof(tmp));
1098 tmp.type = info->type;
1099 tmp.line = info->port;
1100 tmp.flags = info->flags;
1101 tmp.close_delay = info->close_delay;
1102 tmp.closing_wait = info->closing_wait;
1103 tmp.custom_divisor = info->custom_divisor;
1104 tmp.baud_base = info->baud_base;
1105 tmp.hub6 = 0;
1106
> 1107 if (copy_to_user(retinfo, &tmp, sizeof(*retinfo)))
1108 return -EFAULT;
1109 else
1110 return 0;
1111 }
1112
1113 static int npreal_set_serial_info(struct npreal_struct *info, struct serial_struct *new_info)
1114 {
1115 struct serial_struct new_serial;
1116 int rsp_length = RSP_BUFFER_SIZE;
1117 int retval = 0;
1118 unsigned int flags;
1119 char rsp_buffer[RSP_BUFFER_SIZE];
1120
1121
> 1122 if ((!new_info) || copy_from_user(&new_serial, new_info, sizeof(new_serial)))
1123 return -EFAULT;
1124
1125 flags = info->flags & ASYNC_SPD_MASK;
1126
1127 if (!capable(CAP_SYS_ADMIN)) {
1128 if ((new_serial.close_delay != info->close_delay) ||
1129 ((new_serial.flags & ~ASYNC_USR_MASK) != (info->flags & ~ASYNC_USR_MASK)))
1130 return -EPERM;
1131
1132 info->flags = ((info->flags & ~ASYNC_USR_MASK) |
1133 (new_serial.flags & ASYNC_USR_MASK));
1134 } else {
1135 info->flags = ((info->flags & ~ASYNC_FLAGS) | (new_serial.flags & ASYNC_FLAGS));
1136 info->close_delay = new_serial.close_delay * HZ / 100;
1137
1138 if (new_serial.closing_wait == ASYNC_CLOSING_WAIT_NONE)
1139 info->closing_wait = ASYNC_CLOSING_WAIT_NONE;
1140 else
1141 info->closing_wait = new_serial.closing_wait * HZ / 100;
1142 }
1143
1144 info->type = new_serial.type;
1145 set_common_xmit_fifo_size(info, info->net_node);
1146
1147 if (info->flags & ASYNC_INITIALIZED) {
1148 if (flags != (info->flags & ASYNC_SPD_MASK))
> 1149 retval = npreal_port_init(info, 0);
1150
1151 if (info->net_node)
1152 npreal_set_tx_fifo_command_done(info, info->net_node, rsp_buffer,
1153 &rsp_length);
1154
1155 }
1156
1157 info->custom_divisor = new_serial.custom_divisor;
1158
1159 if (info->custom_divisor == 0)
1160 info->baud_base = 921600L;
1161 else
1162 info->baud_base = new_serial.baud_base;
1163
1164 return retval;
1165 }
1166
1167 /**
1168 * npreal_get_lsr_info() - get line status register info
1169 *
1170 * Let user call ioctl() to get info when the UART physically is emptied.
1171 * On bus types like RS485, the transmitter must release the bus after
1172 * transmitting. This must be done when the transmit shift register is
1173 * empty, not be done when the transmit holding register is empty.
1174 * This functionality allows an RS485 driver to be written in user space.
1175 *
1176 * Always return 0 when function is ended.
1177 */
1178 static int npreal_get_lsr_info(struct npreal_struct *info,
1179 unsigned int *value)
1180 {
1181 unsigned int result = 0;
1182
1183 if (npreal_wait_oqueue(info, 0) == 0)
1184 result = TIOCSER_TEMT;
1185
> 1186 put_user(result, value);
1187
1188 return 0;
1189 }
1190

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/[email protected]


Attachments:
(No filename) (13.86 kB)
.config.gz (28.81 kB)
Download all attachments
Subject: RE: [PATCH] tty: Add MOXA NPort Real TTY Driver

Hi Greg,

Thanks for detailed and good suggestions!

> From: [email protected]
> <[email protected]> On Behalf Of Greg Kroah-Hartman
> Sent: Tuesday, July 14, 2020 3:36 PM
> To: Johnson CH Chen (陳昭勳) <[email protected]>
> Cc: Jiri Slaby <[email protected]>; [email protected];
> [email protected]
> Subject: Re: [PATCH] tty: Add MOXA NPort Real TTY Driver
>
> On Tue, Jul 14, 2020 at 06:24:42AM +0000, Johnson CH Chen (陳昭勳) wrote:
> > 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 <[email protected]>
> > Signed-off-by: Jason Chen <[email protected]>
> > Signed-off-by: Danny Lin <[email protected]>
> > Signed-off-by: Victor Yu <[email protected]>
> > ---
> > 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 ([email protected])
> > + *
> > + * 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("<[email protected]>");
> > +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];
>
> You seem to have two "static" buffers here, for your device, that you
> semi-randomly write to all over the place, but I can't find any locking or
> coordination between things that prevents multiple commands from not just
> overwritting each other.
>
For cmd_buffer[], we use npreal_wait_and_set_command() to make sure cmd_buffer[] is safe to be written by checking "cmd_buffer[0] == 0".

For rsp_buffer[], we use npreal_wait_command_completed() to make sure rsp_buffer[] is desired by checking rsp_buffer[0] and rsp_buffer[1]. Command_set and command should be checked. Besides, rsp_buffer[] is got from user space by "NPREAL_NET_CMD_RESPONSE" in npreal_net_ioctl().

> Also, how does the data get sent to the hardware at all? I see cmd_buffer[]
> being written to, but what reads from it and how does the hardware get the
> data?

Actually we need to both NPort driver (this driver) and Npreal daemon (userspace) to let HW work. Npreal daemon can communicate with HW by socket, and Npreal deamon communicates with Nport driver by "npreal_net_fops". When commands are ready for driver part, it will wake up poll event to let Nport daemon know.

Best regards,
Johnson
>
> What am I missing here?
>
> thanks,
>
> greg k-h

2020-07-16 07:23:51

by Greg KH

[permalink] [raw]
Subject: Re: [PATCH] tty: Add MOXA NPort Real TTY Driver

On Thu, Jul 16, 2020 at 07:19:02AM +0000, Johnson CH Chen (陳昭勳) wrote:
> Hi Greg,
>
> Thanks for detailed and good suggestions!
>
> > From: [email protected]
> > <[email protected]> On Behalf Of Greg Kroah-Hartman
> > Sent: Tuesday, July 14, 2020 3:36 PM
> > To: Johnson CH Chen (陳昭勳) <[email protected]>
> > Cc: Jiri Slaby <[email protected]>; [email protected];
> > [email protected]
> > Subject: Re: [PATCH] tty: Add MOXA NPort Real TTY Driver
> >
> > On Tue, Jul 14, 2020 at 06:24:42AM +0000, Johnson CH Chen (陳昭勳) wrote:
> > > 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 <[email protected]>
> > > Signed-off-by: Jason Chen <[email protected]>
> > > Signed-off-by: Danny Lin <[email protected]>
> > > Signed-off-by: Victor Yu <[email protected]>
> > > ---
> > > 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 ([email protected])
> > > + *
> > > + * 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("<[email protected]>");
> > > +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];
> >
> > You seem to have two "static" buffers here, for your device, that you
> > semi-randomly write to all over the place, but I can't find any locking or
> > coordination between things that prevents multiple commands from not just
> > overwritting each other.
> >
> For cmd_buffer[], we use npreal_wait_and_set_command() to make sure
> cmd_buffer[] is safe to be written by checking "cmd_buffer[0] == 0".

And what locks are protecting you there?

> For rsp_buffer[], we use npreal_wait_command_completed() to make sure
> rsp_buffer[] is desired by checking rsp_buffer[0] and rsp_buffer[1].
> Command_set and command should be checked. Besides, rsp_buffer[] is
> got from user space by "NPREAL_NET_CMD_RESPONSE" in
> npreal_net_ioctl().

Again, what locking is really handling this?

> > Also, how does the data get sent to the hardware at all? I see cmd_buffer[]
> > being written to, but what reads from it and how does the hardware get the
> > data?
>
> Actually we need to both NPort driver (this driver) and Npreal daemon
> (userspace) to let HW work. Npreal daemon can communicate with HW by
> socket, and Npreal deamon communicates with Nport driver by
> "npreal_net_fops". When commands are ready for driver part, it will
> wake up poll event to let Nport daemon know.

That is not obvious at all, and needs to be really really really
documented here. Why not put the userspace chunk in the tree too? At
the least, you need to point at it.

And why is a userspace part needed? We have tty-over-ethernet drivers
that do not require such a thing in the tree somewhere...

thanks,

greg k-h

Subject: RE: [PATCH] tty: Add MOXA NPort Real TTY Driver

Hi Greg,

Thanks for your response!

> > > > + unsigned long flag;
> > > > + unsigned char cmd_buffer[84];
> > > > + unsigned char rsp_buffer[84];
> > >
> > > You seem to have two "static" buffers here, for your device, that
> > > you semi-randomly write to all over the place, but I can't find
> > > any locking or coordination between things that prevents multiple
> > > commands from not just overwritting each other.
> > >
> > For cmd_buffer[], we use npreal_wait_and_set_command() to make sure
> > cmd_buffer[] is safe to be written by checking "cmd_buffer[0] == 0".
>
> And what locks are protecting you there?
>
> > For rsp_buffer[], we use npreal_wait_command_completed() to make
> > sure rsp_buffer[] is desired by checking rsp_buffer[0] and rsp_buffer[1].
> > Command_set and command should be checked. Besides, rsp_buffer[] is
> > got from user space by "NPREAL_NET_CMD_RESPONSE" in
> > npreal_net_ioctl().
>
> Again, what locking is really handling this?
>

It's better to protect cmd_buffer[84] and rsp_buffer[84] by locking completely. They are safe because NPort driver should be worked with NPort daemon before, and NPort daemon is designed to be simple.

> > > Also, how does the data get sent to the hardware at all? I see
> > > cmd_buffer[] being written to, but what reads from it and how does
> > > the hardware get the data?
> >
> > Actually we need to both NPort driver (this driver) and Npreal
> > daemon
> > (userspace) to let HW work. Npreal daemon can communicate with HW by
> > socket, and Npreal deamon communicates with Nport driver by
> > "npreal_net_fops". When commands are ready for driver part, it will
> > wake up poll event to let Nport daemon know.
>
> That is not obvious at all, and needs to be really really really documented here.
> Why not put the userspace chunk in the tree too? At the least, you
> need to point at it.
>
> And why is a userspace part needed? We have tty-over-ethernet drivers
> that do not require such a thing in the tree somewhere...
>

Because we need hardware serial to Ethernet converter (NPort device server) to manage some serial devices, so we still need to use MOXA Nport's commands and responses between host computer and converter. We will have an internal discussion about release of Nport daemon and related document, or using free tty to Ethernet daemon such as (ser2net/ socat/ remtty) and improved nport driver later. Thanks a lot!

Best regards,
Johnson

2020-07-22 07:12:10

by Greg KH

[permalink] [raw]
Subject: Re: [PATCH] tty: Add MOXA NPort Real TTY Driver

On Wed, Jul 22, 2020 at 07:04:00AM +0000, Johnson CH Chen (陳昭勳) wrote:
> Hi Greg,
>
> Thanks for your response!
>
> > > > > + unsigned long flag;
> > > > > + unsigned char cmd_buffer[84];
> > > > > + unsigned char rsp_buffer[84];
> > > >
> > > > You seem to have two "static" buffers here, for your device, that
> > > > you semi-randomly write to all over the place, but I can't find
> > > > any locking or coordination between things that prevents multiple
> > > > commands from not just overwritting each other.
> > > >
> > > For cmd_buffer[], we use npreal_wait_and_set_command() to make sure
> > > cmd_buffer[] is safe to be written by checking "cmd_buffer[0] == 0".
> >
> > And what locks are protecting you there?
> >
> > > For rsp_buffer[], we use npreal_wait_command_completed() to make
> > > sure rsp_buffer[] is desired by checking rsp_buffer[0] and rsp_buffer[1].
> > > Command_set and command should be checked. Besides, rsp_buffer[] is
> > > got from user space by "NPREAL_NET_CMD_RESPONSE" in
> > > npreal_net_ioctl().
> >
> > Again, what locking is really handling this?
> >
>
> It's better to protect cmd_buffer[84] and rsp_buffer[84] by locking completely. They are safe because NPort driver should be worked with NPort daemon before, and NPort daemon is designed to be simple.

I'm sorry, but I do not understand this answer at all. Something can be
"simple" and still be totally wrong :)

Without locking, this code is broken.

thanks,

greg k-h

Subject: RE: [PATCH] tty: Add MOXA NPort Real TTY Driver

Hi Greg,

Thanks for your detailed good review!

> From: Greg Kroah-Hartman <[email protected]>
> Sent: Tuesday, July 14, 2020 3:12 PM
> To: Johnson CH Chen (陳昭勳) <[email protected]>
> Cc: Jiri Slaby <[email protected]>; [email protected];
> [email protected]
> Subject: Re: [PATCH] tty: Add MOXA NPort Real TTY Driver
>
> On Tue, Jul 14, 2020 at 06:24:42AM +0000, Johnson CH Chen (陳昭勳) wrote:
> > 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.
>
> A new serial driver, nice!
>
> >
> > 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 <[email protected]>
> > Signed-off-by: Jason Chen <[email protected]>
> > Signed-off-by: Danny Lin <[email protected]>
> > Signed-off-by: Victor Yu <[email protected]>
> > ---
> > 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 ([email protected])
> > + *
> > + * 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;
>
> Please do not do that, just use the normal dynamic debug logic that the kernel
> has and that everyone uses. No per-driver type of debugging functionality, as
> obviously that does not scale at all.
>
> > +
> > +MODULE_AUTHOR("<[email protected]>");
>
> We need a real author name here if you want to be the maintainer.
> Otherwise a support address can't be an author :)
>
After discussion, I will put my email, thanks!

> > +MODULE_DESCRIPTION("MOXA Async/NPort Server Family Real TTY
> Driver");
> > +module_param(ttymajor, int, 0); module_param(verbose, int, 0644);
>
> No module parameters please, they should not be needed at all.
>
> > +MODULE_VERSION(NPREAL_VERSION);
>
> No need for a driver version once the code is in the kernel tree, as the kernel
> version is what matters.
>
> > +MODULE_LICENSE("GPL");
> > +
> > +struct server_setting_struct {
> > + int32_t server_type;
> > + int32_t disable_fifo;
>
> Please use kernel types, like u32, instead of userspace types like these.
>
> > +};
> > +
> > +struct npreal_struct {
> > + struct tty_port ttyPort;
> > + struct work_struct tqueue;
> > + struct work_struct process_flip_tqueue;
>
> None of these should be pointers?

tqueue and process_flip_tqueue could be not used, like Jiri refers.
>
> > + 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 */
>
> enumerated 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;
>
> Do you really need these? Why not just use a ringbuffer structure from the
> kernel?
>
> > + 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;
>
> 2 locks in the same structure???
>
I think " struct semaphore semaphore" can be removed, just use cmd_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;
>
> That's a lot of different queues a specific thing can be on at the same time.
> Are you sure you want that to happen?

Above some queue can be decreased, I will try to do that.
>
> > + int32_t server_type;
>
> Enumerated 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;
>
> Serial port drivers should never create /proc files. If you want debugging stuff,
> use debugfs.
>
> > +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() */
>
> Huh? Why is that needed?
>
> > + /* used waitqueue_active() is safe because smp_mb() is used */
>
> Why?
>
> Are you _sure_ you need that barrier? If so, please document it really really
> really well for you to be able to understand it in 10 years when people have
> questions about it :)
>
It seems it's good to avoid usage of waitqueue_active() that Jiri refers, too. So smp_mb() is not needed because of warning of checkpatch.py.

> > +static int npreal_chars_in_buffer(struct tty_struct *tty) {
> > + struct npreal_struct *info = (struct npreal_struct
> > +*)tty->driver_data;
> > +
> > + if (!info)
> > + return -EIO;
>
> How can that ever happen?
>
It seems a to protect a situation that reading/writing while upload driver unexpectedly. I think this kind of protections can be decreased, I'll try to do this.

> > +
> > + return info->xmit_cnt;
> > +}
>
>
> > +/**
>
> No need for kernel doc format for static functions.
>
> > + * 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);
>
> Did you run sparse on this code? Please do so and fix up the errors/warnings
> it gives you for stuff like this.
>
Thanks your nice remind, I'll use sparse to check code, thanks!

> > +static int npreal_net_open(struct inode *inode, struct file *file) {
> > + struct nd_struct *nd;
> > + int rtn = 0;
> > +
> > + try_module_get(THIS_MODULE);
>
> That is racy and not needed and even wrong here.
>
> Do not do this, it is not the way to correctly do it at all.
>
> > + if (!capable(CAP_SYS_ADMIN)) {
> > + rtn = -EPERM;
> > + goto done;
> > + }
> > +
> > + if (file->private_data) {
>
> How could that ever happen?
>
This protects a situation that npreal_net_close() isn't called but npreal_net_open() is called again.

> > + 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);
>
> No need to cast.
>
> > + if (!nd)
> > + goto done;
> > +
> > + /* This flag will be checked when npreal_net_open() is called again. */
> > + nd->flag &= ~NPREAL_NET_NODE_OPENED;
>
> No locking?
>
It should be locked.

> > + tty_unregister_device(npvar_sdriver, nd->tty_node->port);
> > +
> > +done:
> > + file->private_data = NULL;
> > + module_put(THIS_MODULE);
>
> again, not needed.
>
> > + 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:
>
> Do not make up custom ioctls for a single serial driver, just use the default
> ones, that should be all that is needed, correct?
>
> If not, what do you need to do that the serial layer does not provide for you?
>
I will try to use default ioctl, thanks!

> > +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);
>
> No need to be noisy if all works properly, a driver should be silent.
>
> But why are you doing all of this initialization without actually checking if your
> hardware is present in the system? Please use the bus-specific logic that your
> hardware is on to properly set things up in the probe function, not all in the
> module init function, as this will probably cause failures if loaded on a system
> without the hardware, right?
>
NPort driver creates virtual COM and communicates with Nport daemon, and let Nport daemon talks with HW,
so probe() for this driver may not be needed.

> And what causes the module to properly load automatically? I missed that
> logic somewhere in here, where is it?
>
It doesn't load automatically for original design. We load this driver by init.d or other daemons.

> > --- /dev/null
> > +++ b/drivers/tty/npreal2.h
>
> No need for a .h file for a single .c file.
>
I will try to follow above good suggestions, thanks!

Best regards,
Johnson

2020-07-24 05:34:44

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH] tty: Add MOXA NPort Real TTY Driver

Hi "Johnson,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on tty/tty-testing]
[also build test WARNING on v5.8-rc6 next-20200723]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url: https://github.com/0day-ci/linux/commits/Johnson-CH-Chen/tty-Add-MOXA-NPort-Real-TTY-Driver/20200714-142712
base: https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty.git tty-testing
config: mips-randconfig-s032-20200723 (attached as .config)
compiler: mipsel-linux-gcc (GCC) 9.3.0
reproduce:
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# apt-get install sparse
# sparse version: v0.6.2-93-g4c6cbe55-dirty
# save the attached .config to linux build tree
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-9.3.0 make.cross C=1 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__' ARCH=mips

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <[email protected]>


sparse warnings: (new ones prefixed by >>)

drivers/tty/npreal2.c:1107:26: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected void [noderef] __user *to @@ got struct serial_struct *retinfo @@
drivers/tty/npreal2.c:1107:26: sparse: expected void [noderef] __user *to
drivers/tty/npreal2.c:1107:26: sparse: got struct serial_struct *retinfo
drivers/tty/npreal2.c:1122:56: sparse: sparse: incorrect type in argument 2 (different address spaces) @@ expected void const [noderef] __user *from @@ got struct serial_struct *new_info @@
drivers/tty/npreal2.c:1122:56: sparse: expected void const [noderef] __user *from
drivers/tty/npreal2.c:1122:56: sparse: got struct serial_struct *new_info
drivers/tty/npreal2.c:1149:57: sparse: sparse: Using plain integer as NULL pointer
drivers/tty/npreal2.c:1186:9: sparse: sparse: incorrect type in initializer (different address spaces) @@ expected unsigned int [noderef] __user *__pu_addr @@ got unsigned int *value @@
drivers/tty/npreal2.c:1186:9: sparse: expected unsigned int [noderef] __user *__pu_addr
drivers/tty/npreal2.c:1186:9: sparse: got unsigned int *value
drivers/tty/npreal2.c:1624:38: sparse: sparse: Using plain integer as NULL pointer
drivers/tty/npreal2.c:1897:34: sparse: sparse: Using plain integer as NULL pointer
drivers/tty/npreal2.c:1914:21: sparse: sparse: Using plain integer as NULL pointer
drivers/tty/npreal2.c:1984:46: sparse: sparse: Using plain integer as NULL pointer
drivers/tty/npreal2.c:2261:17: sparse: sparse: incorrect type in initializer (different address spaces) @@ expected unsigned long [noderef] __user *__pu_addr @@ got unsigned long * @@
drivers/tty/npreal2.c:2261:17: sparse: expected unsigned long [noderef] __user *__pu_addr
drivers/tty/npreal2.c:2261:17: sparse: got unsigned long *
>> drivers/tty/npreal2.c:2265:17: sparse: sparse: incorrect type in initializer (different address spaces) @@ expected unsigned long const [noderef] __user *__gu_ptr @@ got unsigned long * @@
>> drivers/tty/npreal2.c:2265:17: sparse: expected unsigned long const [noderef] __user *__gu_ptr
drivers/tty/npreal2.c:2265:17: sparse: got unsigned long *
drivers/tty/npreal2.c:2319:21: sparse: sparse: incorrect type in initializer (different address spaces) @@ expected int [noderef] __user *__pu_addr @@ got int * @@
drivers/tty/npreal2.c:2319:21: sparse: expected int [noderef] __user *__pu_addr
drivers/tty/npreal2.c:2319:21: sparse: got int *
drivers/tty/npreal2.c:2319:62: sparse: sparse: incorrect type in initializer (different address spaces) @@ expected int [noderef] __user *__pu_addr @@ got int * @@
drivers/tty/npreal2.c:2319:62: sparse: expected int [noderef] __user *__pu_addr
drivers/tty/npreal2.c:2319:62: sparse: got int *
drivers/tty/npreal2.c:2320:25: sparse: sparse: incorrect type in initializer (different address spaces) @@ expected int [noderef] __user *__pu_addr @@ got int * @@
drivers/tty/npreal2.c:2320:25: sparse: expected int [noderef] __user *__pu_addr
drivers/tty/npreal2.c:2320:25: sparse: got int *
drivers/tty/npreal2.c:2321:25: sparse: sparse: incorrect type in initializer (different address spaces) @@ expected int [noderef] __user *__pu_addr @@ got int * @@
drivers/tty/npreal2.c:2321:25: sparse: expected int [noderef] __user *__pu_addr
drivers/tty/npreal2.c:2321:25: sparse: got int *
drivers/tty/npreal2.c:2322:25: sparse: sparse: incorrect type in initializer (different address spaces) @@ expected int [noderef] __user *__pu_addr @@ got int * @@
drivers/tty/npreal2.c:2322:25: sparse: expected int [noderef] __user *__pu_addr
drivers/tty/npreal2.c:2322:25: sparse: got int *
drivers/tty/npreal2.c:2323:25: sparse: sparse: incorrect type in initializer (different address spaces) @@ expected int [noderef] __user *__pu_addr @@ got int * @@
drivers/tty/npreal2.c:2323:25: sparse: expected int [noderef] __user *__pu_addr
drivers/tty/npreal2.c:2323:25: sparse: got int *
drivers/tty/npreal2.c:2324:25: sparse: sparse: incorrect type in initializer (different address spaces) @@ expected int [noderef] __user *__pu_addr @@ got int * @@
drivers/tty/npreal2.c:2324:25: sparse: expected int [noderef] __user *__pu_addr
drivers/tty/npreal2.c:2324:25: sparse: got int *
drivers/tty/npreal2.c:2329:17: sparse: sparse: incorrect type in initializer (different address spaces) @@ expected int [noderef] __user *__pu_addr @@ got int * @@
drivers/tty/npreal2.c:2329:17: sparse: expected int [noderef] __user *__pu_addr
drivers/tty/npreal2.c:2329:17: sparse: got int *
drivers/tty/npreal2.c:2330:17: sparse: sparse: incorrect type in initializer (different address spaces) @@ expected int [noderef] __user *__pu_addr @@ got int * @@
drivers/tty/npreal2.c:2330:17: sparse: expected int [noderef] __user *__pu_addr
drivers/tty/npreal2.c:2330:17: sparse: got int *
drivers/tty/npreal2.c:2331:17: sparse: sparse: incorrect type in initializer (different address spaces) @@ expected int [noderef] __user *__pu_addr @@ got int * @@
drivers/tty/npreal2.c:2331:17: sparse: expected int [noderef] __user *__pu_addr
drivers/tty/npreal2.c:2331:17: sparse: got int *
drivers/tty/npreal2.c:2332:17: sparse: sparse: incorrect type in initializer (different address spaces) @@ expected int [noderef] __user *__pu_addr @@ got int * @@
drivers/tty/npreal2.c:2332:17: sparse: expected int [noderef] __user *__pu_addr
drivers/tty/npreal2.c:2332:17: sparse: got int *
drivers/tty/npreal2.c:2570:35: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected void [noderef] __user *to @@ got void * @@
drivers/tty/npreal2.c:2570:35: sparse: expected void [noderef] __user *to
drivers/tty/npreal2.c:2570:35: sparse: got void *
drivers/tty/npreal2.c:2591:57: sparse: sparse: incorrect type in argument 2 (different address spaces) @@ expected void const [noderef] __user *from @@ got void * @@
drivers/tty/npreal2.c:2591:57: sparse: expected void const [noderef] __user *from
drivers/tty/npreal2.c:2591:57: sparse: got void *
drivers/tty/npreal2.c:2720:35: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected void [noderef] __user *to @@ got void * @@
drivers/tty/npreal2.c:2720:35: sparse: expected void [noderef] __user *to
drivers/tty/npreal2.c:2720:35: sparse: got void *
drivers/tty/npreal2.c:2734:56: sparse: sparse: incorrect type in argument 2 (different address spaces) @@ expected void const [noderef] __user *from @@ got void * @@
drivers/tty/npreal2.c:2734:56: sparse: expected void const [noderef] __user *from
drivers/tty/npreal2.c:2734:56: sparse: got void *
drivers/tty/npreal2.c:2803:34: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected void [noderef] __user *to @@ got char *buf @@
drivers/tty/npreal2.c:2803:34: sparse: expected void [noderef] __user *to
drivers/tty/npreal2.c:2803:34: sparse: got char *buf
drivers/tty/npreal2.c:2831:38: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected void [noderef] __user *to @@ got char * @@
drivers/tty/npreal2.c:2831:38: sparse: expected void [noderef] __user *to
drivers/tty/npreal2.c:2831:38: sparse: got char *
drivers/tty/npreal2.c:2899:38: sparse: sparse: incorrect type in argument 2 (different address spaces) @@ expected void const [noderef] __user *from @@ got char const *buf @@
drivers/tty/npreal2.c:2899:38: sparse: expected void const [noderef] __user *from
drivers/tty/npreal2.c:2899:38: sparse: got char const *buf
>> drivers/tty/npreal2.c:2950:22: sparse: sparse: incorrect type in initializer (incompatible argument 2 (different address spaces)) @@ expected int ( *proc_read )( ... ) @@ got int ( * )( ... ) @@
>> drivers/tty/npreal2.c:2950:22: sparse: expected int ( *proc_read )( ... )
>> drivers/tty/npreal2.c:2950:22: sparse: got int ( * )( ... )
>> drivers/tty/npreal2.c:2951:23: sparse: sparse: incorrect type in initializer (incompatible argument 2 (different address spaces)) @@ expected int ( *proc_write )( ... ) @@ got int ( * )( ... ) @@
>> drivers/tty/npreal2.c:2951:23: sparse: expected int ( *proc_write )( ... )
drivers/tty/npreal2.c:2951:23: sparse: got int ( * )( ... )
drivers/tty/npreal2.c:2954:22: sparse: sparse: incorrect type in initializer (different base types) @@ expected restricted __poll_t ( *proc_poll )( ... ) @@ got unsigned int ( * )( ... ) @@
drivers/tty/npreal2.c:2954:22: sparse: expected restricted __poll_t ( *proc_poll )( ... )
drivers/tty/npreal2.c:2954:22: sparse: got unsigned int ( * )( ... )

vim +2265 drivers/tty/npreal2.c

2201
2202 static int npreal_ioctl(struct tty_struct *tty, unsigned int cmd,
2203 unsigned long arg)
2204 {
2205 struct npreal_struct *info = (struct npreal_struct *)tty->driver_data;
2206 struct serial_icounter_struct *p_cuser; /* user space */
2207 unsigned long templ;
2208 int ret = 0;
2209
2210 if (!info)
2211 return -ENODEV;
2212
2213 if ((cmd != TIOCGSERIAL) && (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT) &&
2214 test_bit(TTY_IO_ERROR, &tty->flags))
2215 return -EIO;
2216
2217 switch (cmd) {
2218 case TCFLSH:
2219 ret = tty_check_change(tty);
2220 if (!ret) {
2221 switch (arg) {
2222 case TCIFLUSH:
2223 if (tty->ldisc->ops->flush_buffer)
2224 tty->ldisc->ops->flush_buffer(tty);
2225 break;
2226
2227 case TCIOFLUSH:
2228 if (tty->ldisc->ops->flush_buffer)
2229 tty->ldisc->ops->flush_buffer(tty);
2230 npreal_flush_buffer(tty);
2231 break;
2232
2233 case TCOFLUSH:
2234 npreal_flush_buffer(tty);
2235 break;
2236
2237 default:
2238 ret = -EINVAL;
2239 }
2240 }
2241 break;
2242
2243 case TCSBRK: /* SVID version: non-zero arg --> no break */
2244 ret = tty_check_change(tty);
2245 if (!ret) {
2246 tty_wait_until_sent(tty, 0);
2247 if (!arg)
2248 npreal_send_break(info, HZ / 4);
2249 }
2250 break;
2251
2252 case TCSBRKP: /* support for POSIX tcsendbreak() */
2253 ret = tty_check_change(tty);
2254 if (!ret) {
2255 tty_wait_until_sent(tty, 0);
2256 npreal_send_break(info, arg ? arg * (HZ / 10) : HZ / 4);
2257 }
2258 break;
2259
2260 case TIOCGSOFTCAR:
2261 put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long *)arg);
2262 break;
2263
2264 case TIOCSSOFTCAR:
> 2265 get_user(templ, (unsigned long *)arg);
2266 tty->termios.c_cflag = ((tty->termios.c_cflag & ~CLOCAL) | (arg ? CLOCAL : 0));
2267 break;
2268
2269 case TIOCGSERIAL:
2270 ret = (npreal_get_serial_info(info, (struct serial_struct *)arg));
2271 break;
2272
2273 case TIOCSSERIAL:
2274 ret = (npreal_set_serial_info(info, (struct serial_struct *)arg));
2275 break;
2276
2277 case TIOCSERGETLSR: /* Get line status register */
2278 ret = (npreal_get_lsr_info(info, (unsigned int *)arg));
2279 break;
2280
2281 case TIOCMIWAIT: {
2282 struct async_icount cprev;
2283 DECLARE_WAITQUEUE(wait, current);
2284
2285 cprev = info->icount;
2286 add_wait_queue(&info->delta_msr_wait, &wait);
2287 while (1) {
2288 struct async_icount cnow;
2289
2290 cnow = info->icount;
2291 if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) ||
2292 ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) ||
2293 ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) ||
2294 ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts))) {
2295 ret = 0;
2296 break;
2297 }
2298
2299 if (signal_pending(current)) {
2300 ret = -ERESTARTSYS;
2301 break;
2302 }
2303
2304 cprev = cnow;
2305 current->state = TASK_INTERRUPTIBLE;
2306 schedule();
2307 }
2308
2309 remove_wait_queue(&info->delta_msr_wait, &wait);
2310 break;
2311 }
2312
2313 case TIOCGICOUNT:{
2314 struct async_icount cnow;
2315
2316 cnow = info->icount;
2317 p_cuser = (struct serial_icounter_struct *)arg;
2318
2319 if (put_user(cnow.frame, &p_cuser->frame) || put_user(cnow.brk, &p_cuser->brk) ||
2320 put_user(cnow.overrun, &p_cuser->overrun) ||
2321 put_user(cnow.buf_overrun, &p_cuser->buf_overrun) ||
2322 put_user(cnow.parity, &p_cuser->parity) ||
2323 put_user(cnow.rx, &p_cuser->rx) ||
2324 put_user(cnow.tx, &p_cuser->tx)) {
2325 ret = -EFAULT;
2326 break;
2327 }
2328
2329 put_user(cnow.cts, &p_cuser->cts);
2330 put_user(cnow.dsr, &p_cuser->dsr);
2331 put_user(cnow.rng, &p_cuser->rng);
2332 put_user(cnow.dcd, &p_cuser->dcd);
2333 break;
2334 }
2335 case TCXONC:
2336 ret = tty_check_change(tty);
2337 if (!ret) {
2338 switch (arg) {
2339 case TCOOFF:
2340 ret = npreal_set_generic_command_done(info, ASPP_CMD_SETXOFF);
2341 break;
2342
2343 case TCOON:
2344 ret = npreal_set_generic_command_done(info, ASPP_CMD_SETXON);
2345 break;
2346
2347 default:
2348 ret = -EINVAL;
2349 }
2350 }
2351 break;
2352
2353 default:
2354 ret = -ENOIOCTLCMD;
2355 }
2356 return ret;
2357 }
2358

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/[email protected]


Attachments:
(No filename) (14.96 kB)
.config.gz (24.38 kB)
Download all attachments

2020-07-28 20:42:12

by Pavel Machek

[permalink] [raw]
Subject: Re: [PATCH] tty: Add MOXA NPort Real TTY Driver

Hi!

> + This driver can also be built as a module. The module will be called
> + npreal2 by setting M.

Odd wording...
Pavel
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

Subject: RE: [PATCH] tty: Add MOXA NPort Real TTY Driver

Hi Jiri,

Thanks for your good coding review for this patch, it helps us a lot!

> From: Jiri Slaby <[email protected]>
> Sent: Tuesday, July 14, 2020 5:34 PM
> To: Johnson CH Chen (???L??) <[email protected]>; Greg
> Kroah-Hartman <[email protected]>
> Cc: [email protected]; [email protected]
> Subject: Re: [PATCH] tty: Add MOXA NPort Real TTY Driver
>
> On 14. 07. 20, 8:24, Johnson CH Chen (???L??) wrote:
> > 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.
> ...
> > Signed-off-by: Johnson Chen <[email protected]>
> > Signed-off-by: Jason Chen <[email protected]>
> > Signed-off-by: Danny Lin <[email protected]>
> > Signed-off-by: Victor Yu <[email protected]>
> > ---
> ...
> > --- 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"
>
> MOXA_SMARTIO is lexicographically after MOXA_NPORT_REAL_TTY. So move
> this before MOXA_SMARTIO.
>
> > + 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
>
> The same.
>
> > 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 @@
> ...
> > +#include <linux/delay.h>
> > +#include <linux/errno.h>
> > +#include <linux/fcntl.h>
> > +#include <linux/version.h>
>
> What do you need version.h for? Are you sure, you need all these headers?
>
> > +#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;
>
> You want dynamic major anyway. So kill this all.
>
> > +static int verbose = 1;
> > +
> > +MODULE_AUTHOR("<[email protected]>");
> > +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;
>
> callout in 2020?
>
> > + /* kernel counters for the 4 input interrupts */
> > + struct async_icount icount;
> > + struct semaphore rx_semaphore;
>
> semaphores in new code? You have to explain why are you not using simpler
> and faset mutexes.
>
> > + struct nd_struct *net_node;
> > + struct tty_struct *tty;
> > + struct pid *session;
> > + struct pid *pgrp;
>
> Why does a driver need to care about pgrp? And session? You should kill all
> these set, but unused fields. Note that you should also use fields from struct
> tty_port instead of the duplicates here.
>
> > + 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;
>
> ringbuf (as Greg suggests) or kfifo.
>
> > + /*
> > + * We use spin_lock_irqsave instead of semaphonre here.
>
> "semaphonre"?
>
> > + * 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
>
> "interrpute"?
>
> > + * 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;
>
> Could you reorder the code, so that you don't need these forward declarations?
>
> > +static void npreal_do_softint(struct work_struct *work) {
>
> Well, this is the old way of doing things.
>
> > + 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)) {
>
> Do you ever set that flag?
>
> > + 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);
>
> Have you checked what tty_hangup actually does? Drop this whole function.
>
> > + }
> > + }
> > +}
> > +
> > +/**
> > + * 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) {
>
> Ugh, the same as above, drop this and call flip directly.
>
> > + 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);
>
> This is very bad.
>
> This calls for wait_event_interruptible or alike. "jiffies - et >= 0"
> is broken in any case. time_after() is your friend.
>
> > + } 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)) {
>
> You should break the loop here and do the processing below after the loop.
> Making thuse the loop minimalistic.
>
> > + 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;
>
> wait_event_interruptible_timeout already returns what you compute here in a
> complicated way, IIUC.
>
> > + } 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;
>
> Or simply:
> nd->cmd_buffer[5] = !!(info->modem_control & UART_MCR_DTR);
> In all of them below:
>
> > + 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;
>
> What is this cmd_buffer good for actually? Only to let the user know?
> Then -- drop it.

Because detailed iterations for cmd_buffer and cmd_rsp with Nport server device are regarded proprietary for our company, is it good to reveal value of cmd_buffer[] with macros only for upstream this driver?

Best regards,
Johnson

2020-08-07 09:49:47

by Greg KH

[permalink] [raw]
Subject: Re: [PATCH] tty: Add MOXA NPort Real TTY Driver

On Fri, Aug 07, 2020 at 09:18:57AM +0000, Johnson CH Chen (陳昭勳) wrote:
> > > + 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;
> >
> > What is this cmd_buffer good for actually? Only to let the user know?
> > Then -- drop it.
>
> Because detailed iterations for cmd_buffer and cmd_rsp with Nport
> server device are regarded proprietary for our company, is it good to
> reveal value of cmd_buffer[] with macros only for upstream this
> driver?

There is nothing "proprietary" for Linux kernel code, sorry. Please
document this properly so we can understand, review, and maintain it
over time correctly.

thanks,

greg k-h