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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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




[Index of Archives]     [Kernel Newbies]     [Security]     [Netfilter]     [Bugtraq]     [Linux PPP]     [Linux FS]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Linmodem]     [Device Mapper]     [Linux Kernel for ARM]

  Powered by Linux