linux-serial.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: rishi gupta <gupt21@gmail.com>
To: Greg KH <gregkh@linuxfoundation.org>, robh+dt@kernel.org
Cc: jslaby@suse.com, linux-serial@vger.kernel.org,
	lkml <linux-kernel@vger.kernel.org>
Subject: Re: [PATCH v2 2/4] tty/serial: ttvys: add null modem driver for emulation
Date: Wed, 8 Apr 2020 21:46:15 +0530	[thread overview]
Message-ID: <CALUj-gvM+mupR6qhh=ruyFzY7VL8zGHpcJ9N0oLk4mT-UG_r6A@mail.gmail.com> (raw)
In-Reply-To: <1581789069-5232-2-git-send-email-gupt21@gmail.com>

Humble ping for feedback please!

Regards,
Rishi

On Sat, Feb 15, 2020 at 11:21 PM Rishi Gupta <gupt21@gmail.com> wrote:
>
> The ttyvs driver creates virtual tty devices that can be
> used with standard POSIX APIs for serial port based applications.
> The driver is used mainly for testing user space applications.
>
> Devices can be created through device tree and through configfs.
> Various serial port events are emulated through a sysfs file.
>
> Signed-off-by: Rishi Gupta <gupt21@gmail.com>
> ---
> Changes in v2:
> - Used configFS instead of misc subsystem
> - Added 'depends on CONFIGFS_FS' in Kconfig
> - Used IDR instead of associative array
> - Used tty_register_device_attr() to create sysfs with race with user-space
> - Used u16 wherever applicable instead of ushort
> - Use small x while defining hexadecimal numbers
> - Merged faulty cable sysfs into event sysfs node itself
> - Removed #define <linux/kernel.h>
> - Added #define <linux/uaccess.h>
> - Removed initial loopback & null-modem module parameters
> - Removed variables for device accounting (not needed due to configfs)
> - Used ATTRIBUTE_GROUPS macro
>
>  MAINTAINERS          |    8 +
>  drivers/tty/Kconfig  |   17 +
>  drivers/tty/Makefile |    1 +
>  drivers/tty/ttyvs.c  | 2010 ++++++++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 2036 insertions(+)
>  create mode 100644 drivers/tty/ttyvs.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index a0d8649..dedccfd 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16973,6 +16973,14 @@ F:     include/uapi/linux/serial_core.h
>  F:     include/uapi/linux/serial.h
>  F:     include/uapi/linux/tty.h
>
> +TTYVS VIRTUAL SERIAL DRIVER
> +M:     Rishi Gupta <gupt21@gmail.com>
> +L:     linux-serial@vger.kernel.org
> +L:     linux-kernel@vger.kernel.org
> +S:     Maintained
> +F:     Documentation/devicetree/bindings/serial/ttyvs.yaml
> +F:     drivers/tty/ttyvs.c
> +
>  TUA9001 MEDIA DRIVER
>  M:     Antti Palosaari <crope@iki.fi>
>  L:     linux-media@vger.kernel.org
> diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig
> index a312cb3..3acb6d6 100644
> --- a/drivers/tty/Kconfig
> +++ b/drivers/tty/Kconfig
> @@ -477,4 +477,21 @@ config LDISC_AUTOLOAD
>           dev.tty.ldisc_autoload sysctl, this configuration option will
>           only set the default value of this functionality.
>
> +config TTY_VS
> +       tristate "Virtual serial null modem emulation"
> +       depends on CONFIGFS_FS
> +       help
> +         This driver creates virtual serial port devices (loopback and
> +         null modem style) that can be used in the same way as real serial
> +         port devices. Parity, frame, overflow, ring indicator, baudrate
> +         mismatch, hardware and software flow control can be emulated.
> +
> +         For information about how to create/delete devices, exchange data
> +         and emulate events, please read:
> +         <file:Documentation/devicetree/bindings/serial/ttyvs.yaml>.
> +
> +         To compile this driver as a module, choose M here: the
> +         module will be called ttyvs.ko. If you want to compile this driver
> +         into the kernel, say Y here.
> +
>  endif # TTY
> diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile
> index 020b1cd..9d727ac 100644
> --- a/drivers/tty/Makefile
> +++ b/drivers/tty/Makefile
> @@ -33,6 +33,7 @@ obj-$(CONFIG_SYNCLINK)                += synclink.o
>  obj-$(CONFIG_PPC_EPAPR_HV_BYTECHAN) += ehv_bytechan.o
>  obj-$(CONFIG_GOLDFISH_TTY)     += goldfish.o
>  obj-$(CONFIG_MIPS_EJTAG_FDC_TTY) += mips_ejtag_fdc.o
> +obj-$(CONFIG_TTY_VS)   += ttyvs.o
>  obj-$(CONFIG_VCC)              += vcc.o
>
>  obj-y += ipwireless/
> diff --git a/drivers/tty/ttyvs.c b/drivers/tty/ttyvs.c
> new file mode 100644
> index 0000000..3104900
> --- /dev/null
> +++ b/drivers/tty/ttyvs.c
> @@ -0,0 +1,2010 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Serial port null modem emulation driver
> + *
> + * Copyright (c) 2020, Rishi Gupta <gupt21@gmail.com>
> + */
> +
> +/*
> + * Virtual multi-port serial card:
> + *
> + * This driver implements a virtual multi-port serial card in such a
> + * way that the card can have 0 to N number of virtual serial ports
> + * (tty devices). These devices can be used using standard termios
> + * and Linux/Posix APIs.
> + *
> + * DT bindings: Documentation/devicetree/bindings/serial/ttyvs.yaml
> + * Usage: Documentation/virtual/tty-ttyvs.rst
> + */
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/errno.h>
> +#include <linux/init.h>
> +#include <linux/idr.h>
> +#include <linux/module.h>
> +#include <linux/moduleparam.h>
> +#include <linux/slab.h>
> +#include <linux/wait.h>
> +#include <linux/tty.h>
> +#include <linux/tty_driver.h>
> +#include <linux/tty_flip.h>
> +#include <linux/serial.h>
> +#include <linux/sched.h>
> +#include <linux/version.h>
> +#include <linux/mutex.h>
> +#include <linux/device.h>
> +#include <linux/uaccess.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/configfs.h>
> +
> +/* Pin out configurations definitions */
> +#define VS_CON_CTS    0x0001
> +#define VS_CON_DCD    0x0002
> +#define VS_CON_DSR    0x0004
> +#define VS_CON_RI     0x0008
> +
> +/* Modem control register definitions */
> +#define VS_MCR_DTR    0x0001
> +#define VS_MCR_RTS    0x0002
> +#define VS_MCR_LOOP   0x0004
> +
> +/* Modem status register definitions */
> +#define VS_MSR_CTS    0x0008
> +#define VS_MSR_DCD    0x0010
> +#define VS_MSR_RI     0x0020
> +#define VS_MSR_DSR    0x0040
> +
> +/* UART frame structure definitions */
> +#define VS_CRTSCTS       0x0001
> +#define VS_XON           0x0002
> +#define VS_NONE          0x0004
> +#define VS_DATA_5        0x0008
> +#define VS_DATA_6        0x0010
> +#define VS_DATA_7        0x0020
> +#define VS_DATA_8        0x0040
> +#define VS_PARITY_NONE   0x0080
> +#define VS_PARITY_ODD    0x0100
> +#define VS_PARITY_EVEN   0x0200
> +#define VS_PARITY_MARK   0x0400
> +#define VS_PARITY_SPACE  0x0800
> +#define VS_STOP_1        0x1000
> +#define VS_STOP_2        0x2000
> +
> +/* Constants for the device type (odevtyp) */
> +#define VS_SNM 0x0001
> +#define VS_CNM 0x0002
> +#define VS_SLB 0x0003
> +#define VS_CLB 0x0004
> +
> +/* Attributes associated with a configfs item (folder/device) */
> +struct vs_cfs_dev_info {
> +       struct config_group grp;
> +       char *devtype;
> +       int ownidx;
> +       int peeridx;
> +       u8 ortsmap;
> +       u8 odtrmap;
> +       u8 odtratopn;
> +       u8 prtsmap;
> +       u8 pdtrmap;
> +       u8 pdtratopn;
> +};
> +
> +/* Represents a virtual tty device in this virtual card */
> +struct vs_dev {
> +       /* index for this device in tty core */
> +       unsigned int own_index;
> +       /* index of the device to which this device is connected */
> +       unsigned int peer_index;
> +       /* shadow modem status register */
> +       int msr_reg;
> +       /* shadow modem control register */
> +       int mcr_reg;
> +       /* rts line connections for this device */
> +       int rts_mappings;
> +       /* dtr line connections for this device */
> +       int dtr_mappings;
> +       int set_odtr_at_open;
> +       int set_pdtr_at_open;
> +       int odevtyp;
> +       /* mutual exclusion at device level */
> +       struct mutex lock;
> +       int is_break_on;
> +       /* currently active baudrate */
> +       int baud;
> +       int uart_frame;
> +       int waiting_msr_chg;
> +       int tx_paused;
> +       int faulty_cable;
> +       struct tty_struct *own_tty;
> +       struct tty_struct *peer_tty;
> +       struct serial_struct serial;
> +       struct async_icount icount;
> +       struct device *device;
> +};
> +
> +/*
> + * Index radix tree based database of all devices managed by
> + * this driver.
> + */
> +static DEFINE_IDR(db);
> +
> +/* Used to create and destroy devices atomically/serially */
> +static DEFINE_MUTEX(card_lock);
> +
> +/* Describes this driver */
> +static struct tty_driver *ttyvs_driver;
> +
> +/* Maximum number of devices supported by this driver */
> +static ushort max_num_vs_devs = 64;
> +
> +/*
> + * Notifies tty core that a framing/parity/overrun error has happend
> + * while receiving data on serial port. When frame or parity error
> + * happens, -7/-8 (randomly selected number by this driver) is sent as
> + * byte that got corrupted to tty core. For emulation purpose 0 can
> + * not be taken as corrupted byte because parity and break both will
> + * have same sequence (octal \377 \0 \0) and therefore application
> + * will not be able to differentiate between these two.
> + *
> + * This is also used for asserting/de-asserting ring event on line and
> + * notifies tty core when a break condition has been detected on line.
> + *
> + * 1. Emulate framing error:
> + * $ echo "1" > /sys/class/tty/ttyvsN/event
> + *
> + * 2. Emulate parity error:
> + * $ echo "2" > /sys/class/tty/ttyvsN/event
> + *
> + * 3. Emulate overrun error:
> + * $ echo "3" > /sys/class/tty/ttyvsN/event
> + *
> + * 4. Emulate ring indicator (set RI signal):
> + * $ echo "4" > /sys/class/tty/ttyvsN/event
> + *
> + * 5. Emulate ring indicator (unset RI signal):
> + * $ echo "5" > /sys/class/tty/ttyvsN/event
> + *
> + * 6. Emulate break received:
> + * $ echo "6" > /sys/class/tty/ttyvsN/event
> + *
> + * 7. Emulate cable is faulty (data sent but not received):
> + * $ echo "7" > /sys/class/tty/ttyvsN/event
> + *
> + * 8. Remove faulty cable condition:
> + * $ echo "8" > /sys/class/tty/ttyvsN/event
> + */
> +static ssize_t event_store(struct device *dev,
> +               struct device_attribute *attr, const char *buf, size_t count)
> +{
> +       int ret, push = 1;
> +       struct vs_dev *local_vsdev = dev_get_drvdata(dev);
> +       struct tty_struct *tty_to_write = local_vsdev->own_tty;
> +
> +       if (!buf || (count <= 0))
> +               return -EINVAL;
> +
> +       /*
> +        * Ensure required structure has been allocated, initialized and
> +        * port has been opened.
> +        */
> +       if (!tty_to_write || (tty_to_write->port == NULL)
> +                       || (tty_to_write->port->count <= 0))
> +               return -EIO;
> +       if (!test_bit(ASYNCB_INITIALIZED, &tty_to_write->port->flags))
> +               return -EIO;
> +
> +       mutex_lock(&local_vsdev->lock);
> +
> +       switch (buf[0]) {
> +       case '1':
> +               ret = tty_insert_flip_char(tty_to_write->port, -7, TTY_FRAME);
> +               if (ret < 0)
> +                       goto fail;
> +               local_vsdev->icount.frame++;
> +               break;
> +       case '2':
> +               ret = tty_insert_flip_char(tty_to_write->port, -8, TTY_PARITY);
> +               if (ret < 0)
> +                       goto fail;
> +               local_vsdev->icount.parity++;
> +               break;
> +       case '3':
> +               ret = tty_insert_flip_char(tty_to_write->port, 0, TTY_OVERRUN);
> +               if (ret < 0)
> +                       goto fail;
> +               local_vsdev->icount.overrun++;
> +               break;
> +       case '4':
> +               local_vsdev->msr_reg |= VS_MSR_RI;
> +               local_vsdev->icount.rng++;
> +               push = -1;
> +               break;
> +       case '5':
> +               local_vsdev->msr_reg &= ~VS_MSR_RI;
> +               local_vsdev->icount.rng++;
> +               push = -1;
> +               break;
> +       case '6':
> +               ret = tty_insert_flip_char(tty_to_write->port, 0, TTY_BREAK);
> +               if (ret < 0)
> +                       goto fail;
> +               local_vsdev->icount.brk++;
> +               break;
> +       case '7':
> +               local_vsdev->faulty_cable = 1;
> +               push = -1;
> +               break;
> +       case '8':
> +               local_vsdev->faulty_cable = 0;
> +               push = -1;
> +               break;
> +       default:
> +               mutex_unlock(&local_vsdev->lock);
> +               return -EINVAL;
> +       }
> +
> +       if (push)
> +               tty_flip_buffer_push(tty_to_write->port);
> +       ret = count;
> +
> +fail:
> +       mutex_unlock(&local_vsdev->lock);
> +       return ret;
> +}
> +static DEVICE_ATTR_WO(event);
> +
> +static struct attribute *ttyvs_attrs[] = {
> +       &dev_attr_event.attr,
> +       NULL,
> +};
> +ATTRIBUTE_GROUPS(ttyvs);
> +
> +/*
> + * Checks if the given serial port has received its carrier detect
> + * line raised or not. Return 1 if the carrier is raised otherwise 0.
> + */
> +static int vs_port_carrier_raised(struct tty_port *port)
> +{
> +       struct vs_dev *local_vsdev = idr_find(&db, port->tty->index);
> +
> +       return (local_vsdev->msr_reg & VS_MSR_DCD) ? 1 : 0;
> +}
> +
> +/* Shutdown the given serial port */
> +static void vs_port_shutdown(struct tty_port *port)
> +{
> +       pr_debug("shutting down the port!\n");
> +}
> +
> +/*
> + * Invoked when tty is going to be destroyed and driver should
> + * release resources.
> + */
> +static void vs_port_destruct(struct tty_port *port)
> +{
> +       pr_debug("destroying the port!\n");
> +}
> +
> +/* Activate the given serial port as opposed to shutdown */
> +static int vs_port_activate(struct tty_port *port, struct tty_struct *tty)
> +{
> +       return 0;
> +}
> +
> +static const struct tty_port_operations vs_port_ops = {
> +       .carrier_raised = vs_port_carrier_raised,
> +       .shutdown       = vs_port_shutdown,
> +       .activate       = vs_port_activate,
> +       .destruct       = vs_port_destruct,
> +};
> +
> +/*
> + * Update modem control and status registers according to the bit
> + * mask(s) provided. The RTS and DTR values can be set only if the
> + * current handshaking state of the tty device allows direct control
> + * of the modem control lines. The pin mappings are honoured.
> + *
> + * Caller holds lock of thegiven virtual tty device.
> + */
> +static int vs_update_modem_lines(struct tty_struct *tty,
> +                       unsigned int set, unsigned int clear)
> +{
> +       int ctsint = 0;
> +       int dcdint = 0;
> +       int dsrint = 0;
> +       int rngint = 0;
> +       int mcr_ctrl_reg = 0;
> +       int wakeup_blocked_open = 0;
> +       int rts_mappings, dtr_mappings, msr_state_reg;
> +       struct async_icount *evicount;
> +       struct vs_dev *vsdev, *local_vsdev, *remote_vsdev;
> +
> +       local_vsdev = idr_find(&db, tty->index);
> +
> +       /* Read modify write MSR register */
> +       if (tty->index != local_vsdev->peer_index) {
> +               remote_vsdev = idr_find(&db, local_vsdev->peer_index);
> +               msr_state_reg = remote_vsdev->msr_reg;
> +               vsdev = remote_vsdev;
> +       } else {
> +               msr_state_reg = local_vsdev->msr_reg;
> +               vsdev = local_vsdev;
> +       }
> +
> +       rts_mappings = local_vsdev->rts_mappings;
> +       dtr_mappings = local_vsdev->dtr_mappings;
> +
> +       if (set & TIOCM_RTS) {
> +               mcr_ctrl_reg |= VS_MCR_RTS;
> +               if ((rts_mappings & VS_CON_CTS) == VS_CON_CTS) {
> +                       msr_state_reg |= VS_MSR_CTS;
> +                       ctsint++;
> +               }
> +               if ((rts_mappings & VS_CON_DCD) == VS_CON_DCD) {
> +                       msr_state_reg |= VS_MSR_DCD;
> +                       dcdint++;
> +                       wakeup_blocked_open = 1;
> +               }
> +               if ((rts_mappings & VS_CON_DSR) == VS_CON_DSR) {
> +                       msr_state_reg |= VS_MSR_DSR;
> +                       dsrint++;
> +               }
> +               if ((rts_mappings & VS_CON_RI) == VS_CON_RI) {
> +                       msr_state_reg |= VS_MSR_RI;
> +                       rngint++;
> +               }
> +       }
> +
> +       if (set & TIOCM_DTR) {
> +               mcr_ctrl_reg |= VS_MCR_DTR;
> +               if ((dtr_mappings & VS_CON_CTS) == VS_CON_CTS) {
> +                       msr_state_reg |= VS_MSR_CTS;
> +                       ctsint++;
> +               }
> +               if ((dtr_mappings & VS_CON_DCD) == VS_CON_DCD) {
> +                       msr_state_reg |= VS_MSR_DCD;
> +                       dcdint++;
> +                       wakeup_blocked_open = 1;
> +               }
> +               if ((dtr_mappings & VS_CON_DSR) == VS_CON_DSR) {
> +                       msr_state_reg |= VS_MSR_DSR;
> +                       dsrint++;
> +               }
> +               if ((dtr_mappings & VS_CON_RI) == VS_CON_RI) {
> +                       msr_state_reg |= VS_MSR_RI;
> +                       rngint++;
> +               }
> +       }
> +
> +       if (clear & TIOCM_RTS) {
> +               mcr_ctrl_reg &= ~VS_MCR_RTS;
> +               if ((rts_mappings & VS_CON_CTS) == VS_CON_CTS) {
> +                       msr_state_reg &= ~VS_MSR_CTS;
> +                       ctsint++;
> +               }
> +               if ((rts_mappings & VS_CON_DCD) == VS_CON_DCD) {
> +                       msr_state_reg &= ~VS_MSR_DCD;
> +                       dcdint++;
> +               }
> +               if ((rts_mappings & VS_CON_DSR) == VS_CON_DSR) {
> +                       msr_state_reg &= ~VS_MSR_DSR;
> +                       dsrint++;
> +               }
> +               if ((rts_mappings & VS_CON_RI) == VS_CON_RI) {
> +                       msr_state_reg &= ~VS_MSR_RI;
> +                       rngint++;
> +               }
> +       }
> +
> +       if (clear & TIOCM_DTR) {
> +               mcr_ctrl_reg &= ~VS_MCR_DTR;
> +               if ((dtr_mappings & VS_CON_CTS) == VS_CON_CTS) {
> +                       msr_state_reg &= ~VS_MSR_CTS;
> +                       ctsint++;
> +               }
> +               if ((dtr_mappings & VS_CON_DCD) == VS_CON_DCD) {
> +                       msr_state_reg &= ~VS_MSR_DCD;
> +                       dcdint++;
> +               }
> +               if ((dtr_mappings & VS_CON_DSR) == VS_CON_DSR) {
> +                       msr_state_reg &= ~VS_MSR_DSR;
> +                       dsrint++;
> +               }
> +               if ((dtr_mappings & VS_CON_RI) == VS_CON_RI) {
> +                       msr_state_reg &= ~VS_MSR_RI;
> +                       rngint++;
> +               }
> +       }
> +
> +       local_vsdev->mcr_reg = mcr_ctrl_reg;
> +       vsdev->msr_reg = msr_state_reg;
> +
> +       evicount = &vsdev->icount;
> +       evicount->cts += ctsint;
> +       evicount->dsr += dsrint;
> +       evicount->dcd += dcdint;
> +       evicount->rng += rngint;
> +
> +       if (vsdev->own_tty && vsdev->own_tty->port) {
> +               /* Wake up process blocked on TIOCMIWAIT ioctl */
> +               if ((vsdev->waiting_msr_chg == 1) &&
> +                               (vsdev->own_tty->port->count > 0)) {
> +                       wake_up_interruptible(
> +                                       &vsdev->own_tty->port->delta_msr_wait);
> +               }
> +
> +               /* Wake up application blocked on carrier detect signal */
> +               if ((wakeup_blocked_open == 1) &&
> +                               (vsdev->own_tty->port->blocked_open > 0)) {
> +                       wake_up_interruptible(&vsdev->own_tty->port->open_wait);
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +/*
> + * Invoked when user space process opens a serial port. The tty core
> + * calls this to install tty and initialize the required resources.
> + */
> +static int vs_install(struct tty_driver *drv, struct tty_struct *tty)
> +{
> +       int ret;
> +       struct tty_port *port;
> +
> +       port = kcalloc(1, sizeof(struct tty_port), GFP_KERNEL);
> +       if (!port)
> +               return -ENOMEM;
> +
> +       /* First initialize and then set port operations */
> +       tty_port_init(port);
> +       port->ops = &vs_port_ops;
> +
> +       ret = tty_port_install(port, drv, tty);
> +       if (ret) {
> +               kfree(port);
> +               return ret;
> +       }
> +
> +       return 0;
> +}
> +
> +/*
> + * Invoked when there exist no user process or tty is to be
> + * released explicitly for whatever reason.
> + */
> +static void vs_cleanup(struct tty_struct *tty)
> +{
> +       tty_port_put(tty->port);
> +}
> +
> +/*
> + * Called when open system call is called on virtual tty device node.
> + * The tty core allocates 'struct tty_struct' for this device and
> + * set up various resources, sets up line discipline and call this
> + * function. For first time allocation happens and from next time
> + * onwards only re-opening happens.
> + *
> + * The tty core finds the tty driver serving this device node and the
> + * index of this tty device as registered by this driver with tty core.
> + * From this inded we retrieve the virtual tty device to work on.
> + *
> + * If the same serial port is opened more than once, the tty structure
> + * passed to this function will be same but filp structure will be
> + * different every time. Caller holds tty lock.
> + *
> + * This driver does not set CLOCAL by default. This means that the
> + * open() system call will block until it find its carrier detect
> + * line raised. Application should use O_NONBLOCK/O_NDELAY flag if
> + * it does not want to wait for DCD line change.
> + */
> +static int vs_open(struct tty_struct *tty, struct file *filp)
> +{
> +       int ret;
> +       struct vs_dev *remote_vsdev;
> +       struct vs_dev *local_vsdev = idr_find(&db, tty->index);
> +
> +       local_vsdev->own_tty = tty;
> +
> +       /*
> +        * If this device is one end of a null modem connection,
> +        * provide its address to remote end.
> +        */
> +       if (tty->index != local_vsdev->peer_index) {
> +               remote_vsdev = idr_find(&db, local_vsdev->peer_index);
> +               remote_vsdev->peer_tty = tty;
> +       }
> +
> +       memset(&local_vsdev->serial, 0, sizeof(struct serial_struct));
> +       memset(&local_vsdev->icount, 0, sizeof(struct async_icount));
> +
> +       /*
> +        * Handle DTR raising logic ourselve instead of tty_port helpers
> +        * doing it. Locking virtual tty is not required here.
> +        */
> +       if (local_vsdev->set_odtr_at_open == 1)
> +               vs_update_modem_lines(tty, TIOCM_DTR | TIOCM_RTS, 0);
> +
> +       /* Associate tty with port and do port level opening. */
> +       ret = tty_port_open(tty->port, tty, filp);
> +       if (ret)
> +               return ret;
> +
> +       tty->port->close_delay  = 0;
> +       tty->port->closing_wait = ASYNC_CLOSING_WAIT_NONE;
> +       tty->port->drain_delay  = 0;
> +
> +       return ret;
> +}
> +
> +/*
> + * Invoked by tty layer when release() is called on the file pointer
> + * that was previously created with a call to open().
> + */
> +static void vs_close(struct tty_struct *tty, struct file *filp)
> +{
> +       if (test_bit(TTY_IO_ERROR, &tty->flags))
> +               return;
> +
> +       if (tty && filp && tty->port && (tty->port->count > 0))
> +               tty_port_close(tty->port, tty, filp);
> +
> +       if (tty && C_HUPCL(tty) && tty->port && (tty->port->count < 1))
> +               vs_update_modem_lines(tty, 0, TIOCM_DTR | TIOCM_RTS);
> +}
> +
> +/*
> + * Invoked when write() system call is invoked on device node.
> + * This function constructs evry byte as per the current uart
> + * frame settings. Finally, the data is inserted into the tty
> + * buffer of the receiver tty device.
> + */
> +static int vs_write(struct tty_struct *tty,
> +                       const unsigned char *buf, int count)
> +{
> +       int x;
> +       unsigned char *data = NULL;
> +       struct tty_struct *tty_to_write = NULL;
> +       struct vs_dev *rx_vsdev = NULL;
> +       struct vs_dev *tx_vsdev = idr_find(&db, tty->index);
> +
> +       if (tx_vsdev->tx_paused || !tty || tty->stopped
> +                       || (count < 1) || !buf || tty->hw_stopped)
> +               return 0;
> +
> +       if (tx_vsdev->is_break_on == 1) {
> +               pr_debug("break condition is on!\n");
> +               return -EIO;
> +       }
> +
> +       if (tx_vsdev->faulty_cable == 1)
> +               return count;
> +
> +       if (tty->index != tx_vsdev->peer_index) {
> +               /* Null modem */
> +               tty_to_write = tx_vsdev->peer_tty;
> +               rx_vsdev = idr_find(&db, tx_vsdev->peer_index);
> +
> +               if ((tx_vsdev->baud != rx_vsdev->baud) ||
> +                       (tx_vsdev->uart_frame != rx_vsdev->uart_frame)) {
> +                       /*
> +                        * Emulate data sent but not received due to
> +                        * mismatched baudrate/framing.
> +                        */
> +                       pr_debug("mismatched serial port settings!\n");
> +                       tx_vsdev->icount.tx++;
> +                       return count;
> +               }
> +       } else {
> +               /* Loop back */
> +               tty_to_write = tty;
> +               rx_vsdev = tx_vsdev;
> +       }
> +
> +       if (tty_to_write) {
> +               if ((tty_to_write->termios.c_cflag & CSIZE) == CS8) {
> +                       data = (unsigned char *)buf;
> +               } else {
> +                       data = kcalloc(count, sizeof(char), GFP_KERNEL);
> +                       if (!data)
> +                               return -ENOMEM;
> +
> +                       /* Emulate correct number of data bits */
> +                       switch (tty_to_write->termios.c_cflag & CSIZE) {
> +                       case CS7:
> +                               for (x = 0; x < count; x++)
> +                                       data[x] = buf[x] & 0x7F;
> +                               break;
> +                       case CS6:
> +                               for (x = 0; x < count; x++)
> +                                       data[x] = buf[x] & 0x3F;
> +                               break;
> +                       case CS5:
> +                               for (x = 0; x < count; x++)
> +                                       data[x] = buf[x] & 0x1F;
> +                               break;
> +                       default:
> +                               data = (unsigned char *)buf;
> +                       }
> +               }
> +
> +               tty_insert_flip_string(tty_to_write->port, data, count);
> +               tty_flip_buffer_push(tty_to_write->port);
> +               tx_vsdev->icount.tx++;
> +               rx_vsdev->icount.rx++;
> +
> +               if (data != buf)
> +                       kfree(data);
> +       } else {
> +               /*
> +                * Other end is still not opened, emulate transmission from
> +                * local end but don't make other end receive it as is the
> +                * case in real world.
> +                */
> +               tx_vsdev->icount.tx++;
> +       }
> +
> +       return count;
> +}
> +
> +/* Invoked by tty core to transmit single data byte. */
> +static int vs_put_char(struct tty_struct *tty, unsigned char ch)
> +{
> +       unsigned char data;
> +       struct tty_struct *tty_to_write;
> +       struct vs_dev *rx_vsdev;
> +       struct vs_dev *tx_vsdev = idr_find(&db, tty->index);
> +
> +       if (tx_vsdev->tx_paused || !tty || tty->stopped || tty->hw_stopped)
> +               return 0;
> +
> +       if (tx_vsdev->is_break_on == 1)
> +               return -EIO;
> +
> +       if (tx_vsdev->faulty_cable == 1)
> +               return 1;
> +
> +       if (tty->index != tx_vsdev->peer_index) {
> +               tty_to_write = tx_vsdev->peer_tty;
> +               rx_vsdev = idr_find(&db, tx_vsdev->peer_index);
> +               if ((tx_vsdev->baud != rx_vsdev->baud) ||
> +                       (tx_vsdev->uart_frame != rx_vsdev->uart_frame)) {
> +                       tx_vsdev->icount.tx++;
> +                       return 1;
> +               }
> +       } else {
> +               tty_to_write = tty;
> +               rx_vsdev = tx_vsdev;
> +       }
> +
> +       if (tty_to_write != NULL) {
> +               switch (tty_to_write->termios.c_cflag & CSIZE) {
> +               case CS8:
> +                       data = ch;
> +                       break;
> +               case CS7:
> +                       data = ch & 0x7F;
> +                       break;
> +               case CS6:
> +                       data = ch & 0x3F;
> +                       break;
> +               case CS5:
> +                       data = ch & 0x1F;
> +                       break;
> +               default:
> +                       data = ch;
> +               }
> +               tty_insert_flip_string(tty_to_write->port, &data, 1);
> +               tty_flip_buffer_push(tty_to_write->port);
> +               tx_vsdev->icount.tx++;
> +               rx_vsdev->icount.rx++;
> +       } else {
> +               tx_vsdev->icount.tx++;
> +       }
> +
> +       return 1;
> +}
> +
> +/*
> + * Flush the data out of serial port. This driver immediately
> + * pushes data into receiver's tty buffer hence do nothing here.
> + */
> +static void vs_flush_chars(struct tty_struct *tty)
> +{
> +       pr_debug("flushing the chars!\n");
> +}
> +
> +/*
> + * Discard the internal output buffer for this tty device. Typically
> + * it may be called when executing IOCTL TCOFLUSH, closing the
> + * serial port, when break is received in input stream (flushing
> + * is configured) or when hangup occurs.
> + *
> + * On the other hand, when TCIFLUSH IOCTL is invoked, tty flip buffer
> + * and line discipline queue gets emptied without involvement of tty
> + * driver. The driver is generally expected not to keep data but send
> + * it to tty layer as soon as possible when it receives data.
> + *
> + * As this driver immediately pushes data into receiver's tty buffer
> + * hence do nothing here.
> + */
> +static void vs_flush_buffer(struct tty_struct *tty)
> +{
> +       pr_debug("flushing the buffer!\n");
> +}
> +
> +/* Provides information as a repsonse to TIOCGSERIAL IOCTL */
> +static int vs_get_serinfo(struct tty_struct *tty, unsigned long arg)
> +{
> +       int ret;
> +       struct serial_struct info;
> +       struct vs_dev *local_vsdev = idr_find(&db, tty->index);
> +       struct serial_struct serial = local_vsdev->serial;
> +
> +       if (!arg)
> +               return -EFAULT;
> +
> +       memset(&info, 0, sizeof(info));
> +
> +       info.type                   = PORT_UNKNOWN;
> +       info.line                   = serial.line;
> +       info.port                   = tty->index;
> +       info.irq                        = 0;
> +       info.flags                  = tty->port->flags;
> +       info.xmit_fifo_size = 0;
> +       info.baud_base      = 0;
> +       info.close_delay        = tty->port->close_delay;
> +       info.closing_wait   = tty->port->closing_wait;
> +       info.custom_divisor = 0;
> +       info.hub6                   = 0;
> +       info.io_type            = SERIAL_IO_MEM;
> +
> +       ret = copy_to_user((void __user *)arg, &info,
> +                               sizeof(struct serial_struct));
> +
> +       return ret ? -EFAULT : 0;
> +}
> +
> +/* Returns number of bytes that can be queued to this device now */
> +static int vs_write_room(struct tty_struct *tty)
> +{
> +       struct vs_dev *tx_vsdev = idr_find(&db, tty->index);
> +
> +       if (tx_vsdev->tx_paused || !tty ||
> +                       tty->stopped || tty->hw_stopped)
> +               return 0;
> +
> +       return 2048;
> +}
> +
> +/*
> + * Invoked when serial terminal settings are chaged. The old_termios
> + * contains currently active settings and tty->termios contains new
> + * settings to be applied.
> + */
> +static void vs_set_termios(struct tty_struct *tty,
> +                               struct ktermios *old_termios)
> +{
> +       u32 baud;
> +       int uart_frame_settings;
> +       unsigned int mask = TIOCM_DTR;
> +       struct vs_dev *local_vsdev = idr_find(&db, tty->index);
> +
> +       mutex_lock(&local_vsdev->lock);
> +
> +       /*
> +        * Typically B0 is used to terminate the connection.
> +        * Drop RTS and DTR.
> +        */
> +       if ((tty->termios.c_cflag & CBAUD) == B0) {
> +               vs_update_modem_lines(tty, 0, TIOCM_DTR | TIOCM_RTS);
> +               mutex_unlock(&local_vsdev->lock);
> +               return;
> +       }
> +
> +       /* If coming out of B0, raise DTR and RTS. This might get
> +        * overridden in next steps. Applications like minicom when
> +        * opens a serial port, may drop speed to B0 and then back
> +        * to normal speed again.
> +        */
> +       if (!old_termios || (old_termios->c_cflag & CBAUD) == B0) {
> +               if (!(tty->termios.c_cflag & CRTSCTS) ||
> +                               !test_bit(TTY_THROTTLED, &tty->flags)) {
> +                       mask |= TIOCM_RTS;
> +                       vs_update_modem_lines(tty, mask, 0);
> +               }
> +       }
> +
> +       baud = tty_get_baud_rate(tty);
> +       if (!baud)
> +               baud = 9600;
> +
> +       tty_encode_baud_rate(tty, baud, baud);
> +
> +       local_vsdev->baud = baud;
> +
> +       uart_frame_settings = 0;
> +       if (tty->termios.c_cflag & CRTSCTS) {
> +               uart_frame_settings |= VS_CRTSCTS;
> +       } else if ((tty->termios.c_iflag & IXON) ||
> +                                       (tty->termios.c_iflag & IXOFF)) {
> +               uart_frame_settings |= VS_XON;
> +       } else {
> +               uart_frame_settings |= VS_NONE;
> +       }
> +
> +       switch (tty->termios.c_cflag & CSIZE) {
> +       case CS8:
> +               uart_frame_settings |= VS_DATA_8;
> +               break;
> +       case CS7:
> +               uart_frame_settings |= VS_DATA_7;
> +               break;
> +       case CS6:
> +               uart_frame_settings |= VS_DATA_6;
> +               break;
> +       case CS5:
> +               uart_frame_settings |= VS_DATA_5;
> +               break;
> +       default:
> +               uart_frame_settings |= VS_DATA_8;
> +       }
> +
> +       if (tty->termios.c_cflag & CSTOPB)
> +               uart_frame_settings |= VS_STOP_2;
> +       else
> +               uart_frame_settings |= VS_STOP_1;
> +
> +       if (tty->termios.c_cflag & PARENB) {
> +               if (tty->termios.c_cflag & CMSPAR) {
> +                       if (tty->termios.c_cflag & PARODD)
> +                               uart_frame_settings |= VS_PARITY_MARK;
> +                       else
> +                               uart_frame_settings |= VS_PARITY_SPACE;
> +               } else {
> +                       if (tty->termios.c_cflag & PARODD)
> +                               uart_frame_settings |= VS_PARITY_ODD;
> +                       else
> +                               uart_frame_settings |= VS_PARITY_EVEN;
> +               }
> +       } else {
> +               uart_frame_settings |= VS_PARITY_NONE;
> +       }
> +
> +       local_vsdev->uart_frame = uart_frame_settings;
> +
> +       mutex_unlock(&local_vsdev->lock);
> +}
> +
> +/*
> + * Returns the number of bytes in device's output queue. This is
> + * invoked when TIOCOUTQ IOCTL is executed or by tty core as and
> + * when required. Because we all push all data into receiver's
> + * end tty buffer, always return 0 here.
> + */
> +static int vs_chars_in_buffer(struct tty_struct *tty)
> +{
> +       return 0;
> +}
> +
> +/*
> + * Based on the number od interrupts check if any of the signal
> + * line has changed.
> + */
> +static int vs_check_msr_delta(struct tty_struct *tty,
> +               struct vs_dev *local_vsdev, unsigned long mask,
> +               struct async_icount *prev)
> +{
> +       int delta;
> +       struct async_icount now;
> +
> +       /*
> +        * Use tty-port initialised flag to detect all hangups
> +        * including the disconnect(device destroy) event.
> +        */
> +       if (!test_bit(ASYNCB_INITIALIZED, &tty->port->flags))
> +               return 1;
> +
> +       mutex_lock(&local_vsdev->lock);
> +       now = local_vsdev->icount;
> +       mutex_unlock(&local_vsdev->lock);
> +       delta = ((mask & TIOCM_RNG && prev->rng != now.rng) ||
> +                        (mask & TIOCM_DSR && prev->dsr != now.dsr) ||
> +                        (mask & TIOCM_CAR && prev->dcd != now.dcd) ||
> +                        (mask & TIOCM_CTS && prev->cts != now.cts));
> +
> +       *prev = now;
> +       return delta;
> +}
> +
> +/* Sleeps until at-least one of the modem lines changes */
> +static int vs_wait_change(struct tty_struct *tty, unsigned long mask)
> +{
> +       int ret;
> +       struct async_icount prev;
> +       struct vs_dev *local_vsdev = idr_find(&db, tty->index);
> +
> +       mutex_lock(&local_vsdev->lock);
> +
> +       local_vsdev->waiting_msr_chg = 1;
> +       prev = local_vsdev->icount;
> +
> +       mutex_unlock(&local_vsdev->lock);
> +
> +       ret = wait_event_interruptible(tty->port->delta_msr_wait,
> +                       vs_check_msr_delta(tty, local_vsdev, mask, &prev));
> +
> +       local_vsdev->waiting_msr_chg = 0;
> +
> +       if (!ret && !test_bit(ASYNCB_INITIALIZED, &tty->port->flags))
> +               ret = -EIO;
> +
> +       return ret;
> +}
> +
> +/* Execute IOCTL commands */
> +static int vs_ioctl(struct tty_struct *tty,
> +                               unsigned int cmd, unsigned long arg)
> +{
> +       switch (cmd) {
> +       case TIOCGSERIAL:
> +               return vs_get_serinfo(tty, arg);
> +       case TIOCMIWAIT:
> +               return vs_wait_change(tty, arg);
> +       }
> +
> +       return -ENOIOCTLCMD;
> +}
> +
> +/*
> + * Invoked when tty layer's input buffers are about to get full.
> + *
> + * When using RTS/CTS flow control, when RTS line is de-asserted,
> + * interrupt will be generated in hardware. The interrupt handler
> + * will raise a flag to indicate transmission should be stopped.
> + * This is achieved in this driver through tx_paused variable.
> + */
> +static void vs_throttle(struct tty_struct *tty)
> +{
> +       struct vs_dev *local_vsdev = idr_find(&db, tty->index);
> +       struct vs_dev *remote_vsdev = idr_find(&db, local_vsdev->peer_index);
> +
> +       if (tty->termios.c_cflag & CRTSCTS) {
> +               mutex_lock(&local_vsdev->lock);
> +               remote_vsdev->tx_paused = 1;
> +               vs_update_modem_lines(tty, 0, TIOCM_RTS);
> +               mutex_unlock(&local_vsdev->lock);
> +       } else if ((tty->termios.c_iflag & IXON) ||
> +                               (tty->termios.c_iflag & IXOFF)) {
> +               vs_put_char(tty, STOP_CHAR(tty));
> +       } else {
> +               /* do nothing */
> +       }
> +}
> +
> +/*
> + * Invoked when the tty layer's input buffers have been emptied out,
> + * and it now can accept more data. Throttle/Unthrottle is about
> + * notifying remote end to start or stop data as per the currently
> + * active flow control. On the other hand, Start/Stop is about what
> + * action to take at local end itself to start or stop data as per
> + * the currently active flow control.
> + */
> +static void vs_unthrottle(struct tty_struct *tty)
> +{
> +       struct vs_dev *local_vsdev = idr_find(&db, tty->index);
> +       struct vs_dev *remote_vsdev = idr_find(&db, local_vsdev->peer_index);
> +
> +       if (tty->termios.c_cflag & CRTSCTS) {
> +               /* hardware (RTS/CTS) flow control */
> +               mutex_lock(&local_vsdev->lock);
> +               remote_vsdev->tx_paused = 0;
> +               vs_update_modem_lines(tty, TIOCM_RTS, 0);
> +               mutex_unlock(&local_vsdev->lock);
> +
> +               if (remote_vsdev->own_tty && remote_vsdev->own_tty->port)
> +                       tty_port_tty_wakeup(remote_vsdev->own_tty->port);
> +       } else if ((tty->termios.c_iflag & IXON) ||
> +                               (tty->termios.c_iflag & IXOFF)) {
> +               /* software flow control */
> +               vs_put_char(tty, START_CHAR(tty));
> +       } else {
> +               /* do nothing */
> +       }
> +}
> +
> +/*
> + * Invoked when this driver should stop sending data for example
> + * as a part of flow control mechanism.
> + *
> + * Line discipline n_tty calls this function if this device uses
> + * software flow control and an XOFF character is received from
> + * other end.
> + */
> +static void vs_stop(struct tty_struct *tty)
> +{
> +       struct vs_dev *local_vsdev = idr_find(&db, tty->index);
> +
> +       mutex_lock(&local_vsdev->lock);
> +       local_vsdev->tx_paused = 1;
> +       mutex_unlock(&local_vsdev->lock);
> +}
> +
> +/*
> + * Invoked when this driver should start sending data for example
> + * as a part of flow control mechanism.
> + *
> + * Line discipline n_tty calls this function if this device uses
> + * software flow control and an XON character is received from
> + * other end.
> + */
> +static void vs_start(struct tty_struct *tty)
> +{
> +       struct vs_dev *local_vsdev = idr_find(&db, tty->index);
> +
> +       mutex_lock(&local_vsdev->lock);
> +       local_vsdev->tx_paused = 0;
> +       mutex_unlock(&local_vsdev->lock);
> +
> +       if (tty && tty->port)
> +               tty_port_tty_wakeup(tty->port);
> +}
> +
> +/*
> + * Obtain the modem status bits for the given tty device. Invoked
> + * typically when TIOCMGET IOCTL is executed on the given
> + * tty device.
> + */
> +static int vs_tiocmget(struct tty_struct *tty)
> +{
> +       int status, msr_reg, mcr_reg;
> +       struct vs_dev *local_vsdev = idr_find(&db, tty->index);
> +
> +       mutex_lock(&local_vsdev->lock);
> +       mcr_reg = local_vsdev->mcr_reg;
> +       msr_reg = local_vsdev->msr_reg;
> +       mutex_unlock(&local_vsdev->lock);
> +
> +       status = ((mcr_reg & VS_MCR_DTR)  ? TIOCM_DTR  : 0) |
> +                        ((mcr_reg & VS_MCR_RTS)  ? TIOCM_RTS  : 0) |
> +                        ((mcr_reg & VS_MCR_LOOP) ? TIOCM_LOOP : 0) |
> +                        ((msr_reg & VS_MSR_DCD)  ? TIOCM_CAR  : 0) |
> +                        ((msr_reg & VS_MSR_RI)   ? TIOCM_RI   : 0) |
> +                        ((msr_reg & VS_MSR_CTS)  ? TIOCM_CTS  : 0) |
> +                        ((msr_reg & VS_MSR_DSR)  ? TIOCM_DSR  : 0);
> +
> +       return status;
> +}
> +
> +/*
> + * Set the modem status bits. Invoked typically when TIOCMSET IOCTL
> + * is executed on the given tty device.
> + */
> +static int vs_tiocmset(struct tty_struct *tty,
> +                               unsigned int set, unsigned int clear)
> +{
> +       int ret;
> +       struct vs_dev *local_vsdev = idr_find(&db, tty->index);
> +
> +       mutex_lock(&local_vsdev->lock);
> +       ret = vs_update_modem_lines(tty, set, clear);
> +       mutex_unlock(&local_vsdev->lock);
> +
> +       return ret;
> +}
> +
> +/*
> + * Unconditionally assert/de-assert break condition of the given
> + * tty device.
> + */
> +static int vs_break_ctl(struct tty_struct *tty, int break_state)
> +{
> +       struct tty_struct *tty_to_write;
> +       struct vs_dev *brk_rx_vsdev;
> +       struct vs_dev *brk_tx_vsdev = idr_find(&db, tty->index);
> +
> +       if (tty->index != brk_tx_vsdev->peer_index) {
> +               tty_to_write = brk_tx_vsdev->peer_tty;
> +               brk_rx_vsdev = idr_find(&db, brk_tx_vsdev->peer_index);
> +       } else {
> +               tty_to_write = tty;
> +               brk_rx_vsdev = brk_tx_vsdev;
> +       }
> +
> +       mutex_lock(&brk_tx_vsdev->lock);
> +
> +       if (break_state != 0) {
> +               if (brk_tx_vsdev->is_break_on == 1)
> +                       return 0;
> +
> +               brk_tx_vsdev->is_break_on = 1;
> +               if (tty_to_write != NULL) {
> +                       tty_insert_flip_char(tty_to_write->port, 0, TTY_BREAK);
> +                       tty_flip_buffer_push(tty_to_write->port);
> +                       brk_rx_vsdev->icount.brk++;
> +               }
> +       } else {
> +               brk_tx_vsdev->is_break_on = 0;
> +       }
> +
> +       mutex_unlock(&brk_tx_vsdev->lock);
> +       return 0;
> +}
> +
> +/*
> + * Invoked by tty layer to inform this driver that it should hangup
> + * the tty device (lower modem control lines after last process
> + * using tty devices closes the device or exited).
> + *
> + * Drop DTR/RTS if HUPCL is set. This causes any attached modem to
> + * hang up the line.
> + *
> + * On the receiving end, if CLOCAL bit is set, DCD will be ignored
> + * otherwise SIGHUP may be generated to indicate a line disconnect
> + * event.
> + */
> +static void vs_hangup(struct tty_struct *tty)
> +{
> +       struct vs_dev *local_vsdev = idr_find(&db, tty->index);
> +
> +       mutex_lock(&local_vsdev->lock);
> +
> +       /* Drops reference to tty */
> +       tty_port_hangup(tty->port);
> +
> +       if (tty && C_HUPCL(tty))
> +               vs_update_modem_lines(tty, 0, TIOCM_DTR | TIOCM_RTS);
> +
> +       mutex_unlock(&local_vsdev->lock);
> +       pr_debug("hanged up!\n");
> +}
> +
> +/*
> + * Return number of interrupts as response to TIOCGICOUNT IOCTL.
> + * Both 1->0 and 0->1 transitions are counted, except for RI;
> + * where only 0->1 transitions are accounted.
> + */
> +static int vs_get_icount(struct tty_struct *tty,
> +                               struct serial_icounter_struct *icount)
> +{
> +       struct async_icount cnow;
> +       struct vs_dev *local_vsdev = idr_find(&db, tty->index);
> +
> +       mutex_lock(&local_vsdev->lock);
> +       cnow = local_vsdev->icount;
> +       mutex_unlock(&local_vsdev->lock);
> +
> +       icount->cts = cnow.cts;
> +       icount->dsr = cnow.dsr;
> +       icount->rng = cnow.rng;
> +       icount->dcd = cnow.dcd;
> +       icount->tx = cnow.tx;
> +       icount->rx = cnow.rx;
> +       icount->frame = cnow.frame;
> +       icount->parity = cnow.parity;
> +       icount->overrun = cnow.overrun;
> +       icount->brk = cnow.brk;
> +       icount->buf_overrun = cnow.buf_overrun;
> +
> +       return 0;
> +}
> +
> +/*
> + * Invoked by tty layer to execute TCIOFF and TCION IOCTL commands
> + * generally because user space process called tcflow() function.
> + * It send a high priority character to the tty device end even if
> + * stopped.
> + *
> + * If this function (send_xchar) is defined by tty device driver,
> + * tty core will call this function. If it is not specified then
> + * tty core will first instruct this driver to start transmission
> + * (start()) and then invoke write() of this driver passing character
> + * to be written and then it will call stop() of this driver.
> + */
> +static void vs_send_xchar(struct tty_struct *tty, char ch)
> +{
> +       int was_paused;
> +       struct vs_dev *local_vsdev = idr_find(&db, tty->index);
> +
> +       was_paused = local_vsdev->tx_paused;
> +       if (was_paused)
> +               local_vsdev->tx_paused = 0;
> +
> +       vs_put_char(tty, ch);
> +       if (was_paused)
> +               local_vsdev->tx_paused = 1;
> +}
> +
> +/*
> + * Invoked by tty core in response to tcdrain() call. As this driver
> + * drains on write() itself, we return immediately from here.
> + */
> +static void vs_wait_until_sent(struct tty_struct *tty, int timeout)
> +{
> +       pr_debug("returned wait until sent!\n");
> +}
> +
> +/*
> + * Unregister tty device specified by minor number ownidx
> + * and remove sysfs files associate with it. Caller must
> + * hold card lock. First tty must be released and then port.
> + *
> + * It is common to reset environment before launching new test
> + * suite during automated testing. To support this we allow
> + * removing devices even when it was created using DT as of
> + * now till we find any valid reason not do so.
> + */
> +static void vs_unreg_one_dev(int ownidx, struct vs_dev *vsdev)
> +{
> +       struct tty_struct *tty;
> +
> +       if (vsdev->own_tty && vsdev->own_tty->port) {
> +               tty = tty_port_tty_get(vsdev->own_tty->port);
> +               if (tty) {
> +                       tty_vhangup(tty);
> +                       tty_kref_put(tty);
> +               }
> +       }
> +
> +       tty_unregister_device(ttyvs_driver, ownidx);
> +}
> +
> +/*
> + * Destroy a virtual tty device specified by the given index.
> + * Whether IDR id will be freed or not is specified by the
> + * caller through free_idr.
> + */
> +static int vs_del_specific_devs(int ownidx, int free_idr)
> +{
> +       struct vs_dev *vsdev1, *vsdev2;
> +
> +       /*
> +        * If user just created configfs item but did not populated valid
> +        * index, device will not exist, so bail out early.
> +        */
> +       vsdev1 = idr_find(&db, ownidx);
> +       if (!vsdev1)
> +               return 0;
> +
> +       vs_unreg_one_dev(ownidx, vsdev1);
> +
> +       /* If this device is part of a null modem, delete peer also */
> +       if (vsdev1->own_index != vsdev1->peer_index) {
> +               vsdev2 = idr_find(&db, vsdev1->peer_index);
> +               if (vsdev2) {
> +                       vs_unreg_one_dev(vsdev2->own_index, vsdev2);
> +                       if (free_idr)
> +                               idr_remove(&db, vsdev2->own_index);
> +                       kfree(vsdev2);
> +               }
> +       }
> +
> +       if (free_idr)
> +               idr_remove(&db, ownidx);
> +       kfree(vsdev1);
> +
> +       return 0;
> +}
> +
> +/*
> + * Destroy all tty devices created, mark all the indexes as
> + * available for allocation; reset IDR for re-use.
> + */
> +static void vs_del_all_devs(void)
> +{
> +       int x;
> +       struct vs_dev *vsdev;
> +
> +       mutex_lock(&card_lock);
> +
> +       idr_for_each_entry(&db, vsdev, x)
> +               vs_del_specific_devs(vsdev->own_index, 0);
> +
> +       idr_destroy(&db);
> +
> +       mutex_unlock(&card_lock);
> +}
> +
> +/*
> + * Allocate per device private data (vsdev) for this driver, register
> + * with tty core and create custom sysfs nodes for emulating serial
> + * port events. Caller should hold card lock.
> + */
> +static int vs_alloc_reg_one_dev(int oidx, int pidx, int rtsmap,
> +                       int dtrmap, int dtropn)
> +{
> +       int ret, id;
> +       struct vs_dev *vsdev;
> +       struct device *dev;
> +
> +       /* Allocate and init virtual tty device's private data */
> +       vsdev = kcalloc(1, sizeof(struct vs_dev), GFP_KERNEL);
> +       if (!vsdev)
> +               return -ENOMEM;
> +
> +       id = idr_alloc(&db, vsdev, oidx, oidx + 1, GFP_KERNEL);
> +       if (id < 0) {
> +               ret = id;
> +               goto fail_id;
> +       }
> +
> +       vsdev->own_tty = NULL;
> +       vsdev->peer_tty = NULL;
> +       vsdev->own_index = oidx;
> +       vsdev->peer_index =  pidx;
> +       vsdev->rts_mappings = rtsmap;
> +       vsdev->dtr_mappings = dtrmap;
> +       vsdev->set_odtr_at_open = dtropn;
> +       vsdev->msr_reg = 0;
> +       vsdev->mcr_reg = 0;
> +       vsdev->waiting_msr_chg = 0;
> +       vsdev->tx_paused = 0;
> +       vsdev->faulty_cable = 0;
> +       mutex_init(&vsdev->lock);
> +
> +       /*
> +        * Register with tty core with a specific minor number.
> +        * Driver core itself will create sysfs nodes (ttyvs_groups).
> +        */
> +       dev = tty_register_device_attr(ttyvs_driver, oidx, NULL,
> +                               vsdev, ttyvs_groups);
> +       if (!dev) {
> +               ret = -ENOMEM;
> +               goto fail_reg;
> +       }
> +
> +       vsdev->device = dev;
> +       return 0;
> +
> +fail_reg:
> +       idr_remove(&db, id);
> +fail_id:
> +       kfree(vsdev);
> +       return ret;
> +}
> +
> +/*
> + * Extract pin mappings from local to remote tty devices.
> + * The map contains bits setted by user. Returns 0 on success
> + * or negative error code on error. The *mapping will contain
> + * pin connections (bit map as used by this driver) when this
> + * function returns.
> + */
> +static int vs_extract_pin_mapping(int usrval, int *mapping)
> +{
> +       if (usrval > (VS_CON_CTS | VS_CON_DCD | VS_CON_DSR | VS_CON_RI))
> +               return -EINVAL;
> +
> +       /* No pin connections by-default */
> +       *mapping = 0;
> +
> +       if ((usrval & VS_CON_CTS) == VS_CON_CTS)
> +               *mapping |= VS_CON_CTS;
> +
> +       if ((usrval & VS_CON_DCD) == VS_CON_DCD)
> +               *mapping |= VS_CON_DCD;
> +
> +       if ((usrval & VS_CON_DSR) == VS_CON_DSR)
> +               *mapping |= VS_CON_DSR;
> +
> +       if ((usrval & VS_CON_RI) == VS_CON_RI)
> +               *mapping |= VS_CON_RI;
> +
> +       return 0;
> +}
> +
> +/*
> + * The devtyp is 1 for null modem and 0 for loop-back. We extract
> + * user supplied information, validate it and convert it as
> + * required by this driver to create a device.
> + */
> +static int vs_extract_dev_param_cfs(const struct vs_cfs_dev_info *di,
> +                       unsigned int *idx, int *rtsmap, int *dtrmap,
> +                       int *dtratopen, int devtyp)
> +{
> +       int ret;
> +
> +       if (devtyp) {
> +               if (di->peeridx >= max_num_vs_devs)
> +                       return -EINVAL;
> +
> +               *idx = di->peeridx;
> +
> +               ret = vs_extract_pin_mapping(di->prtsmap, rtsmap);
> +               if (ret)
> +                       return ret;
> +
> +               ret = vs_extract_pin_mapping(di->pdtrmap, rtsmap);
> +               if (ret)
> +                       return ret;
> +
> +               *dtratopen = di->pdtratopn ? 1 : 0;
> +       } else {
> +               if (di->ownidx >= max_num_vs_devs)
> +                       return -EINVAL;
> +
> +               *idx = di->ownidx;
> +
> +               ret = vs_extract_pin_mapping(di->ortsmap, rtsmap);
> +               if (ret)
> +                       return ret;
> +
> +               ret = vs_extract_pin_mapping(di->odtrmap, rtsmap);
> +               if (ret)
> +                       return ret;
> +
> +               *dtratopen = di->odtratopn  ? 1 : 0;
> +       }
> +
> +       return 0;
> +}
> +
> +/* Converts pin mappings from dt node to this driver specific bit map */
> +static int vs_parse_dt_get_map(const struct device_node *np,
> +                               const char *prop, int *mapping)
> +{
> +       int x, ret, num_map;
> +       int val[4];
> +
> +       /*
> +        * If the RTS/DTR pin is unconnected (property doesn't exist)
> +        * set mapping to 0 and return success.
> +        */
> +       ret = of_property_count_u32_elems(np, prop);
> +       if (ret < 0) {
> +               if (ret == -EINVAL) {
> +                       *mapping = 0;
> +                       return 0;
> +               }
> +               return ret;
> +       }
> +
> +       /*
> +        * A given pin can be connected to 1,6,8,9 pins. Therefore if
> +        * more then 4 mappings are defined in DT, ignore it.
> +        */
> +       num_map = ret;
> +       if (ret > 4)
> +               num_map = 4;
> +
> +       ret = of_property_read_u32_array(np, prop, val, num_map);
> +       if (ret < 0)
> +               return ret;
> +
> +       *mapping = 0;
> +       for (x = 0; x < num_map; x++) {
> +               switch (val[x]) {
> +               case 8:
> +                       *mapping |= VS_CON_CTS;
> +                       break;
> +               case 1:
> +                       *mapping |= VS_CON_DCD;
> +                       break;
> +               case 6:
> +                       *mapping |= VS_CON_DSR;
> +                       break;
> +               case 9:
> +                       *mapping |= VS_CON_RI;
> +                       break;
> +               default:
> +                       return -EINVAL;
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +/*
> + * Extract index of device, RTS mappings, DTR mappings and
> + * whether to assert DTR at device open or not from dt node.
> + */
> +static int vs_extract_dev_param_dt(const struct device_node *np,
> +                       unsigned int *idx, int *rtsmap, int *dtrmap,
> +                       int *dtratopen, int exclude)
> +{
> +       int ret;
> +
> +       ret = of_property_read_u32(np, "dev-num", idx);
> +       if (ret)
> +               return ret;
> +
> +       if (*idx >= max_num_vs_devs)
> +               return -EINVAL;
> +
> +       ret = vs_parse_dt_get_map(np, "rtsmap", rtsmap);
> +       if (ret)
> +               return ret;
> +
> +       ret = vs_parse_dt_get_map(np, "dtrmap", dtrmap);
> +       if (ret)
> +               return ret;
> +
> +       *dtratopen = of_property_read_bool(np,
> +                                               "set-dtr-at-open") ? 1 : 0;
> +
> +       return 0;
> +}
> +
> +/*
> + * Create a loop-back style device:
> + *
> + * 0. Information about device parameters can come through either
> + *    configfs node or device-tree node.
> + * 1. Decide index to use; the number is specified by user. If the
> + *    given index is used already through error.
> + * 2. Extract RTS and DTR mappings. A pin can map to pin numbers
> + *    1,6,8,9 only or might be un-connected. Through error if
> + *    invalid mapping is given.
> + * 3. Find if DTR should be asserted when tty device is opened or
> + *    not.
> + * 4. Allocate and initialize 'struct vs_dev' instance with info
> + *    from steps 1,2 & 3.
> + * 5. Register one tty device with tty core and associate this tty
> + *    device with vsdev instance from step 4.
> + * 6. Create custom sysfs nodes to emulate serial port events for
> + *    this device.
> + */
> +static int vs_add_lb(const struct vs_cfs_dev_info *di,
> +                               const struct device_node *np)
> +{
> +       int ret, rtsmap, dtrmap, dtratopen;
> +       unsigned int idx;
> +
> +       mutex_lock(&card_lock);
> +
> +       if (di) {
> +               ret = vs_extract_dev_param_cfs(di, &idx, &rtsmap,
> +                                       &dtrmap, &dtratopen, 0);
> +       } else {
> +               ret = vs_extract_dev_param_dt(np, &idx, &rtsmap,
> +                                       &dtrmap, &dtratopen, -1);
> +       }
> +       if (ret)
> +               goto fail;
> +
> +       ret = vs_alloc_reg_one_dev(idx, idx, rtsmap, dtrmap, dtratopen);
> +       if (ret)
> +               goto fail;
> +
> +fail:
> +       mutex_unlock(&card_lock);
> +       return ret;
> +}
> +
> +/*
> + * Create a null-modem style pair of devices:
> + *
> + * Steps are same as for creating loop-back style device except,
> + * we create both the devices on success or none of them on error.
> + */
> +static int vs_add_nm(const struct vs_cfs_dev_info *di,
> +                               const struct device_node *np1,
> +                               const struct device_node *np2)
> +{
> +       int ret, rtsmap1, dtrmap1, dtratopen1;
> +       int rtsmap2, dtrmap2, dtratopen2;
> +       unsigned int idx1, idx2;
> +
> +       mutex_lock(&card_lock);
> +
> +       if (di) {
> +               ret = vs_extract_dev_param_cfs(di, &idx1, &rtsmap1, &dtrmap1,
> +                               &dtratopen1, 0);
> +               if (ret)
> +                       goto out;
> +
> +               ret = vs_extract_dev_param_cfs(di, &idx2, &rtsmap2, &dtrmap2,
> +                               &dtratopen2, 1);
> +       } else {
> +               ret = vs_extract_dev_param_dt(np1, &idx1, &rtsmap1,
> +                                       &dtrmap1, &dtratopen1, -1);
> +               if (ret)
> +                       goto out;
> +
> +               ret = vs_extract_dev_param_dt(np2, &idx2, &rtsmap2,
> +                                       &dtrmap2, &dtratopen2, idx1);
> +       }
> +       if (ret)
> +               goto out;
> +
> +       ret = vs_alloc_reg_one_dev(idx1, idx2, rtsmap1, dtrmap1, dtratopen1);
> +       if (ret)
> +               goto out;
> +
> +       ret = vs_alloc_reg_one_dev(idx2, idx1, rtsmap2, dtrmap2, dtratopen2);
> +       if (ret)
> +               vs_del_specific_devs(idx1, 1);
> +
> +out:
> +       mutex_unlock(&card_lock);
> +       return ret;
> +}
> +
> +static const struct tty_operations vs_serial_ops = {
> +       .install             = vs_install,
> +       .cleanup             = vs_cleanup,
> +       .open            = vs_open,
> +       .close           = vs_close,
> +       .write           = vs_write,
> +       .put_char            = vs_put_char,
> +       .flush_chars     = vs_flush_chars,
> +       .write_room      = vs_write_room,
> +       .chars_in_buffer = vs_chars_in_buffer,
> +       .ioctl           = vs_ioctl,
> +       .set_termios     = vs_set_termios,
> +       .throttle            = vs_throttle,
> +       .unthrottle      = vs_unthrottle,
> +       .stop            = vs_stop,
> +       .start           = vs_start,
> +       .hangup          = vs_hangup,
> +       .break_ctl       = vs_break_ctl,
> +       .flush_buffer    = vs_flush_buffer,
> +       .wait_until_sent = vs_wait_until_sent,
> +       .send_xchar      = vs_send_xchar,
> +       .tiocmget            = vs_tiocmget,
> +       .tiocmset            = vs_tiocmset,
> +       .get_icount      = vs_get_icount,
> +};
> +
> +static int vs_register_with_tty_core(void)
> +{
> +       int ret;
> +
> +       /* Initialize and register this driver with tty core */
> +       ttyvs_driver = tty_alloc_driver(max_num_vs_devs, 0);
> +       if (IS_ERR(ttyvs_driver))
> +               return PTR_ERR(ttyvs_driver);
> +
> +       ttyvs_driver->owner = THIS_MODULE;
> +       ttyvs_driver->driver_name = "ttyvs";
> +       ttyvs_driver->name = "ttyvs";
> +       ttyvs_driver->major = 0;
> +       ttyvs_driver->minor_start = 0;
> +       ttyvs_driver->type = TTY_DRIVER_TYPE_SERIAL;
> +       ttyvs_driver->subtype = SERIAL_TYPE_NORMAL;
> +       ttyvs_driver->flags = TTY_DRIVER_REAL_RAW
> +                               | TTY_DRIVER_RESET_TERMIOS
> +                               | TTY_DRIVER_DYNAMIC_DEV;
> +       ttyvs_driver->init_termios = tty_std_termios;
> +       ttyvs_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL;
> +       ttyvs_driver->init_termios.c_ispeed = 9600;
> +       ttyvs_driver->init_termios.c_ospeed = 9600;
> +
> +       tty_set_operations(ttyvs_driver, &vs_serial_ops);
> +
> +       ret = tty_register_driver(ttyvs_driver);
> +       if (ret)
> +               put_tty_driver(ttyvs_driver);
> +
> +       return ret;
> +}
> +
> +/*
> + * Information passed through device tree is given more preference
> + * then through module params. This parses all device nodes and
> + * creates loop-back and null-modem ttyvsX devices in the process.
> + */
> +static int ttyvs_device_probe(struct platform_device *pdev)
> +{
> +       int ret;
> +       u32 max_num;
> +       struct device_node *child, *peer_node;
> +       phandle peer;
> +       struct device *dev = &pdev->dev;
> +       struct device_node *np = dev->of_node;
> +
> +       /*
> +        * We register with tty core again only if maximum number of
> +        * devices registered during module_init is changed by device
> +        * tree.
> +        */
> +       max_num = 0;
> +       ret = of_property_read_u32(np, "max-num-vs-devs", &max_num);
> +       if (!ret && (max_num != max_num_vs_devs)) {
> +               tty_unregister_driver(ttyvs_driver);
> +               put_tty_driver(ttyvs_driver);
> +
> +               max_num_vs_devs = max_num;
> +               ret = vs_register_with_tty_core();
> +               if (ret)
> +                       return ret;
> +       }
> +
> +       /*
> +        * If we fail to create any device emit error log and move to
> +        * the next dt node.
> +        */
> +       for_each_available_child_of_node(np, child) {
> +               if (of_node_test_and_set_flag(child, OF_POPULATED))
> +                       continue;
> +
> +               if (of_property_read_u32(child, "peer-dev", &peer)) {
> +                       ret = vs_add_lb(NULL, child);
> +                       if (ret) {
> +                               pr_err("can't create lb %s %d\n",
> +                                               child->name, ret);
> +                               continue;
> +                       }
> +               } else {
> +                       peer_node = of_find_node_by_phandle(peer);
> +                       if (peer_node) {
> +                               of_node_set_flag(peer_node, OF_POPULATED);
> +                               ret = vs_add_nm(NULL, child, peer_node);
> +                               if (ret) {
> +                                       pr_err("can't create nm %s <-> %s %d\n",
> +                                               child->name, peer_node->name,
> +                                               ret);
> +                                       continue;
> +                               }
> +                       } else {
> +                               pr_err("can't find peer for %s %d\n",
> +                                               child->name, ret);
> +                       }
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +static inline struct vs_cfs_dev_info *to_vs_dinfo(
> +                               struct config_item *item)
> +{
> +       return container_of(to_config_group(item),
> +                               struct vs_cfs_dev_info, grp);
> +}
> +
> +#define VS_DEV_ATTR_WR_U8(_name) \
> +static ssize_t vs_dev_##_name##_store(struct config_item *item, \
> +               const char *page, size_t len) \
> +{                                     \
> +       u8 val;                           \
> +       int ret;                          \
> +       ret = kstrtou8(page, 0, &val);    \
> +       if (ret)                          \
> +               return ret;                   \
> +       to_vs_dinfo(item)->_name = val;   \
> +       return len;                       \
> +}                                     \
> +static ssize_t vs_dev_##_name##_show(struct config_item *item, \
> +               char *buf)                    \
> +{                                     \
> +       return snprintf(buf, PAGE_SIZE, "%u\n", to_vs_dinfo(item)->_name); \
> +}
> +
> +#define VS_DEV_ATTR_WR_U16(_name)     \
> +static ssize_t vs_dev_##_name##_store(struct config_item *item, \
> +               const char *page, size_t len) \
> +{                                     \
> +       u16 val;                          \
> +       int ret;                          \
> +       ret = kstrtou16(page, 0, &val);   \
> +       if (ret)                          \
> +               return ret;                   \
> +       to_vs_dinfo(item)->_name = val;   \
> +       return len;                       \
> +}                                     \
> +static ssize_t vs_dev_##_name##_show(struct config_item *item, \
> +               char *buf)                     \
> +{                                      \
> +       return snprintf(buf, PAGE_SIZE, "%u\n", to_vs_dinfo(item)->_name); \
> +}
> +
> +#define VS_DEV_ATTR_WR_STR(_name) \
> +static ssize_t vs_dev_##_name##_store(struct config_item *item, \
> +               const char *page, size_t len)     \
> +{                                         \
> +       char *devtype;                        \
> +       devtype = kstrdup(page, GFP_KERNEL);  \
> +       if (!devtype)                         \
> +               return -ENOMEM;                   \
> +       if (devtype[len - 1] == '\n')         \
> +               devtype[len - 1] = '\0';          \
> +       to_vs_dinfo(item)->devtype = devtype; \
> +       return len;                           \
> +}                                         \
> +static ssize_t vs_dev_##_name##_show(struct config_item *item, \
> +               char *buf)                        \
> +{                                         \
> +       return snprintf(buf, PAGE_SIZE, "%s\n", to_vs_dinfo(item)->devtype); \
> +}
> +
> +/*
> + * Once all parameters for the device has been set, this finally
> + * creates the device.
> + */
> +static ssize_t vs_dev_create_store(struct config_item *item,
> +               const char *page, size_t len)
> +{
> +       u8 val;
> +       int ret;
> +       struct vs_cfs_dev_info *di;
> +
> +       ret = kstrtou8(page, 0, &val);
> +       if (ret)
> +               return ret;
> +
> +       /* User must write 1 to this node create device */
> +       if (val != 1)
> +               return -EINVAL;
> +
> +       di = to_vs_dinfo(item);
> +
> +       /* devtype must be defined to proceed further */
> +       if (!di->devtype)
> +               return -EINVAL;
> +
> +       if (strncmp(di->devtype, "lb", 2) == 0)
> +               ret = vs_add_lb(di, NULL);
> +       else if (strncmp(di->devtype, "nm", 2) == 0)
> +               ret = vs_add_nm(di, NULL, NULL);
> +       else
> +               return -EINVAL;
> +
> +       if (ret)
> +               return ret;
> +       return len;
> +}
> +
> +VS_DEV_ATTR_WR_STR(devtype)
> +VS_DEV_ATTR_WR_U16(ownidx)
> +VS_DEV_ATTR_WR_U16(peeridx)
> +VS_DEV_ATTR_WR_U8(ortsmap)
> +VS_DEV_ATTR_WR_U8(odtrmap)
> +VS_DEV_ATTR_WR_U8(odtratopn)
> +VS_DEV_ATTR_WR_U8(prtsmap)
> +VS_DEV_ATTR_WR_U8(pdtrmap)
> +VS_DEV_ATTR_WR_U8(pdtratopn)
> +
> +CONFIGFS_ATTR(vs_dev_, devtype);
> +CONFIGFS_ATTR(vs_dev_, ownidx);
> +CONFIGFS_ATTR(vs_dev_, ortsmap);
> +CONFIGFS_ATTR(vs_dev_, odtrmap);
> +CONFIGFS_ATTR(vs_dev_, odtratopn);
> +CONFIGFS_ATTR(vs_dev_, peeridx);
> +CONFIGFS_ATTR(vs_dev_, prtsmap);
> +CONFIGFS_ATTR(vs_dev_, pdtrmap);
> +CONFIGFS_ATTR(vs_dev_, pdtratopn);
> +CONFIGFS_ATTR_WO(vs_dev_, create);
> +
> +static struct configfs_attribute *vs_dev_attrs[] = {
> +       &vs_dev_attr_devtype,
> +       &vs_dev_attr_ownidx,
> +       &vs_dev_attr_ortsmap,
> +       &vs_dev_attr_odtrmap,
> +       &vs_dev_attr_odtratopn,
> +       &vs_dev_attr_peeridx,
> +       &vs_dev_attr_prtsmap,
> +       &vs_dev_attr_pdtrmap,
> +       &vs_dev_attr_pdtratopn,
> +       &vs_dev_attr_create,
> +       NULL,
> +};
> +
> +static const struct config_item_type vs_cfs_root_type = {
> +       .ct_attrs = vs_dev_attrs,
> +       .ct_owner = THIS_MODULE,
> +};
> +
> +static struct config_group *vs_cfs_grp_make(
> +                       struct config_group *group,
> +                       const char *name)
> +{
> +       struct vs_cfs_dev_info *di;
> +
> +       di = kzalloc(sizeof(*di), GFP_KERNEL);
> +       if (!di)
> +               return ERR_PTR(-ENOMEM);
> +
> +       config_group_init_type_name(&di->grp, name, &vs_cfs_root_type);
> +
> +       return &di->grp;
> +}
> +
> +static void vs_cfs_grp_drop(struct config_group *group,
> +                               struct config_item *item)
> +{
> +       struct vs_cfs_dev_info *di = to_vs_dinfo(item);
> +
> +       mutex_lock(&card_lock);
> +       vs_del_specific_devs(di->ownidx, 1);
> +       mutex_unlock(&card_lock);
> +
> +       kfree(di);
> +       config_item_put(item);
> +}
> +
> +static struct configfs_group_operations vs_cfs_grp_ops = {
> +       .make_group     = &vs_cfs_grp_make,
> +       .drop_item      = &vs_cfs_grp_drop,
> +};
> +
> +static const struct config_item_type vs_cfs_grp_type = {
> +       .ct_group_ops = &vs_cfs_grp_ops,
> +       .ct_owner = THIS_MODULE,
> +};
> +
> +struct configfs_subsystem vs_cfs_subsys = {
> +       .su_group = {
> +               .cg_item = {
> +                       .ci_namebuf = "ttyvs",
> +                       .ci_type = &vs_cfs_grp_type,
> +               },
> +       },
> +       .su_mutex = __MUTEX_INITIALIZER(vs_cfs_subsys.su_mutex),
> +};
> +
> +static const struct of_device_id ttyvs_dev_match_tbl[] = {
> +       { .compatible = "ttyvs,virtual-uart-card" },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(of, ttyvs_dev_match_tbl);
> +
> +static struct platform_driver ttyvs_platform_drv = {
> +       .probe          = ttyvs_device_probe,
> +       .driver         = {
> +               .name   = "ttyvs",
> +               .of_match_table = ttyvs_dev_match_tbl,
> +       },
> +};
> +
> +static int __init ttyvs_init(void)
> +{
> +       int ret;
> +
> +       config_group_init(&vs_cfs_subsys.su_group);
> +
> +       ret = configfs_register_subsystem(&vs_cfs_subsys);
> +       if (ret)
> +               return ret;
> +
> +       ret = vs_register_with_tty_core();
> +       if (ret)
> +               goto fail_drv;
> +
> +       /* Register as platform driver to handle device tree nodes */
> +       ret = platform_driver_register(&ttyvs_platform_drv);
> +       if (ret)
> +               goto fail_plat;
> +
> +       pr_info("serial port null modem emulation driver\n");
> +       return 0;
> +
> +fail_plat:
> +       tty_unregister_driver(ttyvs_driver);
> +       put_tty_driver(ttyvs_driver);
> +
> +fail_drv:
> +       configfs_unregister_subsystem(&vs_cfs_subsys);
> +
> +       return ret;
> +}
> +
> +static void __exit ttyvs_exit(void)
> +{
> +       vs_del_all_devs();
> +
> +       configfs_unregister_subsystem(&vs_cfs_subsys);
> +       platform_driver_unregister(&ttyvs_platform_drv);
> +
> +       tty_unregister_driver(ttyvs_driver);
> +       put_tty_driver(ttyvs_driver);
> +}
> +
> +module_init(ttyvs_init);
> +module_exit(ttyvs_exit);
> +
> +/*
> + * By default this driver supports upto 64 virtual devices. This
> + * can be overridden through max_num_vs_devs module parameter or
> + * through max-num-vs-devs device tree property.
> + */
> +module_param(max_num_vs_devs, ushort, 0);
> +MODULE_PARM_DESC(max_num_vs_devs,
> +               "Maximum virtual tty devices to be supported");
> +
> +MODULE_AUTHOR("Rishi Gupta <gupt21@gmail.com>");
> +MODULE_DESCRIPTION("Serial port null modem emulation driver");
> +MODULE_LICENSE("GPL v2");
> --
> 2.7.4
>

  reply	other threads:[~2020-04-08 16:16 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-02-15 17:51 [PATCH v2 1/4] dt-bindings: ttyvs: document serial null modem driver bindings Rishi Gupta
2020-02-15 17:51 ` [PATCH v2 2/4] tty/serial: ttvys: add null modem driver for emulation Rishi Gupta
2020-04-08 16:16   ` rishi gupta [this message]
2020-04-13  9:23     ` Andy Shevchenko
2020-02-15 17:51 ` [PATCH v2 3/4] tty: documentation: abi: add ttyvs null modem driver sysfs nodes Rishi Gupta
2020-02-15 17:51 ` [PATCH v2 4/4] tty: documentation: document how to use ttyvs driver Rishi Gupta

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to='CALUj-gvM+mupR6qhh=ruyFzY7VL8zGHpcJ9N0oLk4mT-UG_r6A@mail.gmail.com' \
    --to=gupt21@gmail.com \
    --cc=gregkh@linuxfoundation.org \
    --cc=jslaby@suse.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-serial@vger.kernel.org \
    --cc=robh+dt@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).