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

From: Johnson CH Chen (陳昭勳)
Date: Fri Aug 07 2020 - 05:21:45 EST


Hi Jiri,

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

> From: Jiri Slaby <jirislaby@xxxxxxxxx>
> Sent: Tuesday, July 14, 2020 5:34 PM
> To: Johnson CH Chen (陳昭勳) <JohnsonCH.Chen@xxxxxxxx>; Greg
> Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx>
> Cc: linux-kernel@xxxxxxxxxxxxxxx; linux-serial@xxxxxxxxxxxxxxx
> Subject: Re: [PATCH] tty: Add MOXA NPort Real TTY Driver
>
> On 14. 07. 20, 8:24, 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.
> ...
> > Signed-off-by: Johnson Chen <johnsonch.chen@xxxxxxxx>
> > Signed-off-by: Jason Chen <jason.chen@xxxxxxxx>
> > Signed-off-by: Danny Lin <danny.lin@xxxxxxxx>
> > Signed-off-by: Victor Yu <victor.yu@xxxxxxxx>
> > ---
> ...
> > --- 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("<support@xxxxxxxx>");
> > +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