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

From: Johnson CH Chen (éæå)
Date: Thu Jul 23 2020 - 06:55:18 EST


Hi Greg,

Thanks for your detailed good review!

> From: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx>
> Sent: Tuesday, July 14, 2020 3:12 PM
> To: Johnson CH Chen (éæå) <JohnsonCH.Chen@xxxxxxxx>
> Cc: Jiri Slaby <jirislaby@xxxxxxxxx>; linux-kernel@xxxxxxxxxxxxxxx;
> linux-serial@xxxxxxxxxxxxxxx
> 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 <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>
> > ---
> > drivers/tty/Kconfig | 11 +
> > drivers/tty/Makefile | 1 +
> > drivers/tty/npreal2.c | 3042
> > +++++++++++++++++++++++++++++++++++++++++
> > drivers/tty/npreal2.h | 140 ++
> > 4 files changed, 3194 insertions(+)
> > create mode 100644 drivers/tty/npreal2.c create mode 100644
> > drivers/tty/npreal2.h
> >
> > diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig index
> > 93fd984eb2f5..79b545269b71 100644
> > --- a/drivers/tty/Kconfig
> > +++ b/drivers/tty/Kconfig
> > @@ -259,6 +259,17 @@ config MOXA_SMARTIO
> > This driver can also be built as a module. The module will be called
> > mxser. If you want to do that, say M here.
> >
> > +config MOXA_NPORT_REAL_TTY
> > + tristate "Moxa NPort Real TTY support v5.0"
> > + help
> > + Say Y here if you have a Moxa NPort serial device server.
> > +
> > + The purpose of this driver is to map NPort serial port to host tty
> > + port. Using this driver, you can use NPort serial port as local tty port.
> > +
> > + This driver can also be built as a module. The module will be called
> > + npreal2 by setting M.
> > +
> > config SYNCLINK
> > tristate "Microgate SyncLink card support"
> > depends on SERIAL_NONSTANDARD && PCI && ISA_DMA_API diff --git
> > a/drivers/tty/Makefile b/drivers/tty/Makefile index
> > 020b1cd9294f..6d07985d6962 100644
> > --- a/drivers/tty/Makefile
> > +++ b/drivers/tty/Makefile
> > @@ -24,6 +24,7 @@ obj-$(CONFIG_CYCLADES) += cyclades.o
> > obj-$(CONFIG_ISI) += isicom.o
> > obj-$(CONFIG_MOXA_INTELLIO) += moxa.o
> > obj-$(CONFIG_MOXA_SMARTIO) += mxser.o
> > +obj-$(CONFIG_MOXA_NPORT_REAL_TTY) += npreal2.o
> > obj-$(CONFIG_NOZOMI) += nozomi.o
> > obj-$(CONFIG_NULL_TTY) += ttynull.o
> > obj-$(CONFIG_ROCKETPORT) += rocket.o
> > diff --git a/drivers/tty/npreal2.c b/drivers/tty/npreal2.c new file
> > mode 100644 index 000000000000..65c773420755
> > --- /dev/null
> > +++ b/drivers/tty/npreal2.c
> > @@ -0,0 +1,3042 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/*
> > + * npreal2.c -- MOXA NPort Server family Real TTY driver.
> > + *
> > + * Copyright (c) 1999-2020 Moxa Technologies (support@xxxxxxxx)
> > + *
> > + * 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("<support@xxxxxxxx>");
>
> 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