rewrite line discipline to be compatible with PARMRK
authorMiroslav Ondra <ondra@faster.cz>
Thu, 2 Aug 2018 10:03:41 +0000 (12:03 +0200)
committerMiroslav Ondra <ondra@faster.cz>
Thu, 2 Aug 2018 10:03:41 +0000 (12:03 +0200)
fixed ioctl timeout settings for 32b calling on 64bit system (compat_ioctl)

modules/unipi/src/unipi_common.h
modules/unipi/src/unipi_spi.c
modules/unipi/src/unipi_tty.c
modules/unipi/src/unipi_tty.h
modules/unipi/src/unipi_uart.c

index 20d26098eba6d9a578cf0ed5b0e732372965eb7a..a09f806e1f2e6e06cc1e055b9d4ab5d403c883a3 100644 (file)
@@ -41,7 +41,7 @@
 #include <linux/leds.h>
 #include <linux/uaccess.h>
 #include <asm/termbits.h>
-#include <asm/gpio.h>
+//#include <asm/gpio.h>
 
 /***************
  * Definitions *
index 1dc42456ff0e653395d93bcbeed96fcacfc99057..9557df7108ad6bca6fa9ceffb217636e64a1cbf5 100644 (file)
@@ -1550,7 +1550,7 @@ static s32 __init neuronspi_init(void)
        if (neuronspi_invalidate_thread != NULL) {
                wake_up_process(neuronspi_invalidate_thread);
        }
-       neuronspi_tty_init();
+       unipi_tty_init();
        return ret;
 }
 
index d26c26b0bb03fa0f7b00fcb0caa7c482e3a8165c..a18dbd044262594b24afde2382fc2e29b803c7ce 100644 (file)
 /*
- * UniPi PLC device driver - Copyright (C) 2018 UniPi Technology
- * Author: Tomas Knot <tomasknot@gmail.com>
+ * Implements line discpline for using with Neuron/Axon.
  *
- *  Based on the SC16IS7xx driver by Jon Ringle <jringle@gridpoint.com>,
- *  which was in turn based on max310x.c, by Alexander Shiyan <shc_work@mail.ru>
+ * Derivated from n_tty.c --- implements the N_TTY line discipline.
  *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * This code used to be in tty_io.c, but things are getting hairy
+ * enough that it made sense to split things off.  (The N_TTY
+ * processing has changed so much that it's hardly recognizable,
+ * anyway...)
  *
+ * Note that the open routine for N_TTY is guaranteed never to return
+ * an error.  This is because Linux will fall back to setting a line
+ * to N_TTY if it can not switch to any other line discipline.
+ *
+ * Written by Theodore Ts'o, Copyright 1994.
+ *
+ * This file also contains code originally written by Linus Torvalds,
+ * Copyright 1991, 1992, 1993, and by Julian Cowley, Copyright 1994.
+ *
+ * This file may be redistributed under the terms of the GNU General Public
+ * License.
+ *
+ * Reduced memory usage for older ARM systems  - Russell King.
+ *
+ * 2000/01/20   Fixed SMP locking on put_tty_queue using bits of
+ *             the patch by Andrew J. Kroll <ag784@freenet.buffalo.edu>
+ *             who actually finally proved there really was a race.
+ *
+ * 2002/03/18   Implemented unipi_tty_wakeup to send SIGIO POLL_OUTs to
+ *             waiting writing processes-Sapan Bhatia <sapan@corewars.org>.
+ *             Also fixed a bug in BLOCKING mode where unipi_tty_write returns
+ *             EAGAIN
  */
 
-#include "unipi_tty.h"
+#include <linux/types.h>
+#include <linux/major.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/fcntl.h>
+#include <linux/sched.h>
 #include <linux/interrupt.h>
 #include <linux/tty.h>
 #include <linux/timer.h>
 #include <linux/ctype.h>
 #include <linux/mm.h>
 #include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/bitops.h>
+#include <linux/audit.h>
+#include <linux/file.h>
+#include <linux/uaccess.h>
+#include <linux/module.h>
+#include <linux/ratelimit.h>
+#include <linux/vmalloc.h>
+
+#include "unipi_tty.h"
+
+/* number of characters left in xmit buffer before select has we have room */
+#define WAKEUP_CHARS 256
+
+/*
+ * This defines the low- and high-watermarks for throttling and
+ * unthrottling the TTY driver.  These watermarks are used for
+ * controlling the space in the read buffer.
+ */
+#define TTY_THRESHOLD_THROTTLE         128 /* now based on remaining room */
+#define TTY_THRESHOLD_UNTHROTTLE       128
+
+
+// #undef UNIPI_TTY_TRACE
+#ifdef NEURONSPI_DETAILED_DEBUG
+# define unipi_tty_trace(f, args...)   trace_printk(f, ##args)
+#else
+# define unipi_tty_trace(f, args...)
+#endif
+
+struct unipi_tty_data {
+       /* producer-published */
+       size_t read_head;
+       size_t commit_head;
+       //DECLARE_BITMAP(char_map, 256);
 
-struct tty_ldisc_ops neuronspi_tty_ldisc;
+       /* private to unipi_tty_receive_overrun (single-threaded) */
+       /* non-atomic */
+       bool no_room;
 
-int neuronspi_tty_init()
+       /* shared by producer and consumer */
+       char read_buf[N_TTY_BUF_SIZE];
+
+       /* consumer-published */
+       size_t read_tail;
+
+
+       struct mutex atomic_read_lock;
+       struct mutex output_lock;
+};
+
+static inline size_t read_cnt(struct unipi_tty_data *ldata)
 {
-       int err;
-#if NEURONSPI_DETAILED_DEBUG > 0
-       printk(KERN_INFO "UNIPISPI: TTY Init\n");
+       return ldata->read_head - ldata->read_tail;
+}
+
+static inline unsigned char read_buf(struct unipi_tty_data *ldata, size_t i)
+{
+       return ldata->read_buf[i & (N_TTY_BUF_SIZE - 1)];
+}
+
+static inline unsigned char *read_buf_addr(struct unipi_tty_data *ldata, size_t i)
+{
+       return &ldata->read_buf[i & (N_TTY_BUF_SIZE - 1)];
+}
+
+
+/**
+ *     tty_throttle_safe       -       flow control
+ *     @tty: terminal
+ *
+ *     Similar to tty_throttle() but will only attempt throttle
+ *     if tty->flow_change is TTY_THROTTLE_SAFE. Prevents an accidental
+ *     throttle due to race conditions when throttling is conditional
+ *     on factors evaluated prior to throttling.
+ *
+ *     Returns 0 if tty is throttled (or was already throttled)
+ */
+
+int unipi_tty_throttle_safe(struct tty_struct *tty)
+{
+       int ret = 0;
+
+       mutex_lock(&tty->throttle_mutex);
+       if (!tty_throttled(tty)) {
+               if (tty->flow_change != TTY_THROTTLE_SAFE)
+                       ret = 1;
+               else {
+                       set_bit(TTY_THROTTLED, &tty->flags);
+                       if (tty->ops->throttle)
+                               tty->ops->throttle(tty);
+               }
+       }
+       mutex_unlock(&tty->throttle_mutex);
+
+       return ret;
+}
+
+/**
+ *     tty_unthrottle_safe     -       flow control
+ *     @tty: terminal
+ *
+ *     Similar to tty_unthrottle() but will only attempt unthrottle
+ *     if tty->flow_change is TTY_UNTHROTTLE_SAFE. Prevents an accidental
+ *     unthrottle due to race conditions when unthrottling is conditional
+ *     on factors evaluated prior to unthrottling.
+ *
+ *     Returns 0 if tty is unthrottled (or was already unthrottled)
+ */
+
+int unipi_tty_unthrottle_safe(struct tty_struct *tty)
+{
+       int ret = 0;
+
+       mutex_lock(&tty->throttle_mutex);
+       if (tty_throttled(tty)) {
+               if (tty->flow_change != TTY_UNTHROTTLE_SAFE)
+                       ret = 1;
+               else {
+                       clear_bit(TTY_THROTTLED, &tty->flags);
+                       if (tty->ops->unthrottle)
+                               tty->ops->unthrottle(tty);
+               }
+       }
+       mutex_unlock(&tty->throttle_mutex);
+
+       return ret;
+}
+
+
+/**
+ *     unipi_tty_kick_worker - start input worker (if required)
+ *     @tty: terminal
+ *
+ *     Re-schedules the flip buffer work if it may have stopped
+ *
+ *     Caller holds exclusive termios_rwsem
+ *        or
+ *     unipi_tty_read()/consumer path:
+ *             holds non-exclusive termios_rwsem
+ */
+
+static void unipi_tty_kick_worker(struct tty_struct *tty)
+{
+       struct unipi_tty_data *ldata = tty->disc_data;
+    struct tty_port *port = tty->port;
+
+       /* Did the input worker stop? Restart it */
+       if (unlikely(ldata->no_room)) {
+               ldata->no_room = 0;
+
+               WARN_RATELIMIT(tty->port->itty == NULL,
+                               "scheduling with invalid itty\n");
+               /* see if ldisc has been killed - if so, this means that
+                * even though the ldisc has been halted and ->buf.work
+                * cancelled, ->buf.work is about to be rescheduled
+                */
+               WARN_RATELIMIT(test_bit(TTY_LDISC_HALTED, &tty->flags),
+                              "scheduling buffer work for halted ldisc\n");
+               //tty_buffer_restart_work(tty->port);
+        queue_work(system_unbound_wq, &port->buf.work);
+       }
+}
+
+static ssize_t chars_in_buffer(struct tty_struct *tty)
+{
+       struct unipi_tty_data *ldata = tty->disc_data;
+       ssize_t n = 0;
+
+    n = ldata->commit_head - ldata->read_tail;
+       return n;
+}
+
+/**
+ *     unipi_tty_write_wakeup  -       asynchronous I/O notifier
+ *     @tty: tty device
+ *
+ *     Required for the ptys, serial driver etc. since processes
+ *     that attach themselves to the master and rely on ASYNC
+ *     IO must be woken up
+ */
+
+static void unipi_tty_write_wakeup(struct tty_struct *tty)
+{
+       unipi_tty_trace(KERN_INFO "UNIPI_LDISC: Wakeup start.");
+
+       clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
+       kill_fasync(&tty->fasync, SIGIO, POLL_OUT);
+}
+
+static void unipi_tty_check_throttle(struct tty_struct *tty)
+{
+       struct unipi_tty_data *ldata = tty->disc_data;
+
+       /*
+        * Check the remaining room for the input canonicalization
+        * mode.  We don't want to throttle the driver if we're in
+        * canonical mode and don't have a newline yet!
+        */
+
+       while (1) {
+               int throttled;
+               tty_set_flow_change(tty, TTY_THROTTLE_SAFE);
+               if (N_TTY_BUF_SIZE - read_cnt(ldata) >= TTY_THRESHOLD_THROTTLE)
+                       break;
+               throttled = unipi_tty_throttle_safe(tty);
+               if (!throttled)
+                       break;
+       }
+       __tty_set_flow_change(tty, 0);
+}
+
+static void unipi_tty_check_unthrottle(struct tty_struct *tty)
+{
+       if (tty->driver->type == TTY_DRIVER_TYPE_PTY) {
+               if (chars_in_buffer(tty) > TTY_THRESHOLD_UNTHROTTLE)
+                       return;
+               unipi_tty_kick_worker(tty);
+               tty_wakeup(tty->link);
+               return;
+       }
+
+       /* If there is enough space in the read buffer now, let the
+        * low-level driver know. We use chars_in_buffer() to
+        * check the buffer, as it now knows about canonical mode.
+        * Otherwise, if the driver is throttled and the line is
+        * longer than TTY_THRESHOLD_UNTHROTTLE in canonical mode,
+        * we won't get any more characters.
+        */
+
+       while (1) {
+               int unthrottled;
+               tty_set_flow_change(tty, TTY_UNTHROTTLE_SAFE);
+               if (chars_in_buffer(tty) > TTY_THRESHOLD_UNTHROTTLE)
+                       break;
+               unipi_tty_kick_worker(tty);
+               unthrottled = unipi_tty_unthrottle_safe(tty);
+               if (!unthrottled)
+                       break;
+       }
+       __tty_set_flow_change(tty, 0);
+}
+
+/**
+ *     reset_buffer_flags      -       reset buffer state
+ *     @tty: terminal to reset
+ *
+ *     Reset the read buffer counters and clear the flags.
+ *     Called from unipi_tty_open() and unipi_tty_flush_buffer().
+ *
+ *     Locking: caller holds exclusive termios_rwsem
+ *              (or locking is not required)
+ */
+
+static void reset_buffer_flags(struct unipi_tty_data *ldata)
+{
+       ldata->read_head =  ldata->read_tail = 0;
+       ldata->commit_head = 0;
+
+}
+
+static void unipi_tty_packet_mode_flush(struct tty_struct *tty)
+{
+       unsigned long flags;
+
+       if (tty->link->packet) {
+               spin_lock_irqsave(&tty->ctrl_lock, flags);
+               tty->ctrl_status |= TIOCPKT_FLUSHREAD;
+               spin_unlock_irqrestore(&tty->ctrl_lock, flags);
+               wake_up_interruptible(&tty->link->read_wait);
+       }
+}
+
+/**
+ *     unipi_tty_flush_buffer  -       clean input queue
+ *     @tty:   terminal device
+ *
+ *     Flush the input buffer. Called when the tty layer wants the
+ *     buffer flushed (eg at hangup) or when the N_TTY line discipline
+ *     internally has to clean the pending queue (for example some signals).
+ *
+ *     Holds termios_rwsem to exclude producer/consumer while
+ *     buffer indices are reset.
+ *
+ *     Locking: ctrl_lock, exclusive termios_rwsem
+ */
+
+static void unipi_tty_flush_buffer(struct tty_struct *tty)
+{
+       unipi_tty_trace(KERN_INFO "UNIPI_LDISC: Flush start.");
+
+       down_write(&tty->termios_rwsem);
+       reset_buffer_flags(tty->disc_data);
+       unipi_tty_kick_worker(tty);
+
+       if (tty->link)
+               unipi_tty_packet_mode_flush(tty);
+       up_write(&tty->termios_rwsem);
+}
+
+
+
+static void __receive_buf(struct tty_struct *tty, const unsigned char *cp,
+                         char *fp, int count)
+{
+       struct unipi_tty_data *ldata = tty->disc_data;
+       size_t n, head;
+
+       head = ldata->read_head & (N_TTY_BUF_SIZE - 1);
+       n = min_t(size_t, count, N_TTY_BUF_SIZE - head);
+       memcpy(read_buf_addr(ldata, head), cp, n);
+       ldata->read_head += n;
+       cp += n;
+       count -= n;
+
+       head = ldata->read_head & (N_TTY_BUF_SIZE - 1);
+       n = min_t(size_t, count, N_TTY_BUF_SIZE - head);
+       memcpy(read_buf_addr(ldata, head), cp, n);
+       ldata->read_head += n;
+
+       /* publish read_head to consumer */
+       smp_store_release(&ldata->commit_head, ldata->read_head);
+
+       if (read_cnt(ldata)) {
+               kill_fasync(&tty->fasync, SIGIO, POLL_IN);
+               wake_up_interruptible_poll(&tty->read_wait, POLLIN);
+       }
+}
+
+/**
+ *     unipi_tty_receive_buf_common    -       process input
+ *     @tty: device to receive input
+ *     @cp: input chars
+ *     @fp: flags for each char (if NULL, all chars are TTY_NORMAL)
+ *     @count: number of input chars in @cp
+ *
+ *     Called by the terminal driver when a block of characters has
+ *     been received. This function must be called from soft contexts
+ *     not from interrupt context. The driver is responsible for making
+ *     calls one at a time and in order (or using flush_to_ldisc)
+ *
+ *     Returns the # of input chars from @cp which were processed.
+ *
+ *     In canonical mode, the maximum line length is 4096 chars (including
+ *     the line termination char); lines longer than 4096 chars are
+ *     truncated. After 4095 chars, input data is still processed but
+ *     not stored. Overflow processing ensures the tty can always
+ *     receive more input until at least one line can be read.
+ *
+ *     In non-canonical mode, the read buffer will only accept 4095 chars;
+ *     this provides the necessary space for a newline char if the input
+ *     mode is switched to canonical.
+ *
+ *     Note it is possible for the read buffer to _contain_ 4096 chars
+ *     in non-canonical mode: the read buffer could already contain the
+ *     maximum canon line of 4096 chars when the mode is switched to
+ *     non-canonical.
+ *
+ *     unipi_tty_receive_buf()/producer path:
+ *             claims non-exclusive termios_rwsem
+ *             publishes commit_head or canon_head
+ */
+static int
+unipi_tty_receive_buf_common(struct tty_struct *tty, const unsigned char *cp,
+                        char *fp, int count, int flow)
+{
+       struct unipi_tty_data *ldata = tty->disc_data;
+       int room, n, rcvd = 0;
+
+       down_read(&tty->termios_rwsem);
+
+       while (1) {
+
+               size_t tail = smp_load_acquire(&ldata->read_tail);
+
+               room = N_TTY_BUF_SIZE - (ldata->read_head - tail);
+               room--;
+               if (room <= 0) {
+            room = 0;
+                       ldata->no_room = flow;
+               }
+               n = min(count, room);
+               if (!n)
+                       break;
+
+               __receive_buf(tty, cp, fp, n);
+
+               cp += n;
+               count -= n;
+               rcvd += n;
+       }
+
+       tty->receive_room = room;
+
+       /* Unthrottle if handling overflow on pty */
+       if (tty->driver->type == TTY_DRIVER_TYPE_PTY) {
+       } else
+               unipi_tty_check_throttle(tty);
+
+       up_read(&tty->termios_rwsem);
+
+       return rcvd;
+}
+
+static void unipi_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp,
+                             char *fp, int count)
+{
+       unipi_tty_trace(KERN_INFO "UNIPI_LDISC: Buf start.");
+       unipi_tty_receive_buf_common(tty, cp, fp, count, 0);
+}
+
+static int unipi_tty_receive_buf2(struct tty_struct *tty, const unsigned char *cp,
+                             char *fp, int count)
+{
+       unipi_tty_trace(KERN_INFO "UNIPI_LDISC: Buf2 start.");
+       return unipi_tty_receive_buf_common(tty, cp, fp, count, 1);
+}
+
+/**
+ *     unipi_tty_set_termios   -       termios data changed
+ *     @tty: terminal
+ *     @old: previous data
+ *
+ *     Called by the tty layer when the user changes termios flags so
+ *     that the line discipline can plan ahead. This function cannot sleep
+ *     and is protected from re-entry by the tty layer. The user is
+ *     guaranteed that this function will not be re-entered or in progress
+ *     when the ldisc is closed.
+ *
+ *     Locking: Caller holds tty->termios_rwsem
+ */
+
+static void unipi_tty_set_termios(struct tty_struct *tty, struct ktermios *old)
+{
+       struct unipi_tty_data *ldata = tty->disc_data;
+    int lx=0; int ix=0; int ox=0; int cx = 0;
+    if (old) {
+        lx = old->c_lflag; 
+        ix = old->c_iflag; 
+        ox = old->c_oflag; 
+        cx = old->c_cflag; 
+    }
+       unipi_tty_trace(KERN_INFO "UNIPI_LDISC: Termios new:0x%x %x %x %x old:0x%x %x %x %x", \
+         tty->termios.c_lflag, tty->termios.c_iflag, tty->termios.c_oflag,  tty->termios.c_cflag,\
+         lx, ix, ox, cx);
+       if (!old || (old->c_lflag ^ tty->termios.c_lflag) & (ICANON | EXTPROC)) {
+               ldata->commit_head = ldata->read_head;
+       }
+
+       /*
+        * Fix tty hang when I_IXON(tty) is cleared, but the tty
+        * been stopped by STOP_CHAR(tty) before it.
+        */
+       if (!I_IXON(tty) && old && (old->c_iflag & IXON) && !tty->flow_stopped) {
+               start_tty(tty);
+       }
+
+       /* The termios change make the tty ready for I/O */
+       wake_up_interruptible(&tty->write_wait);
+       wake_up_interruptible(&tty->read_wait);
+}
+
+/**
+ *     unipi_tty_close         -       close the ldisc for this tty
+ *     @tty: device
+ *
+ *     Called from the terminal layer when this line discipline is
+ *     being shut down, either because of a close or becsuse of a
+ *     discipline change. The function will not be called while other
+ *     ldisc methods are in progress.
+ */
+
+static void unipi_tty_close(struct tty_struct *tty)
+{
+       struct unipi_tty_data *ldata = tty->disc_data;
+
+       if (tty->link)
+               unipi_tty_packet_mode_flush(tty);
+
+       vfree(ldata);
+       tty->disc_data = NULL;
+       unipi_tty_trace(KERN_INFO "UNIPI_LDISC: Close OK.");
+}
+
+/**
+ *     unipi_tty_open          -       open an ldisc
+ *     @tty: terminal to open
+ *
+ *     Called when this line discipline is being attached to the
+ *     terminal device. Can sleep. Called serialized so that no
+ *     other events will occur in parallel. No further open will occur
+ *     until a close.
+ */
+
+static int unipi_tty_open(struct tty_struct *tty)
+{
+       struct unipi_tty_data *ldata;
+
+       /* Currently a malloc failure here can panic */
+       ldata = vmalloc(sizeof(*ldata));
+       if (!ldata)
+               goto err;
+
+       mutex_init(&ldata->atomic_read_lock);
+       mutex_init(&ldata->output_lock);
+
+       tty->disc_data = ldata;
+       reset_buffer_flags(tty->disc_data);
+       ldata->no_room = 0;
+       tty->closing = 0;
+       /* indicate buffer work may resume */
+       clear_bit(TTY_LDISC_HALTED, &tty->flags);
+       unipi_tty_set_termios(tty, NULL);
+       tty_unthrottle(tty);
+
+       unipi_tty_trace(KERN_INFO "UNIPI_LDISC: Open OK.");
+
+       return 0;
+err:
+       return -ENOMEM;
+}
+
+static inline int input_available_p(struct tty_struct *tty, int poll)
+{
+       struct unipi_tty_data *ldata = tty->disc_data;
+       int amt = poll && !TIME_CHAR(tty) && MIN_CHAR(tty) ? MIN_CHAR(tty) : 1;
+
+    return ldata->commit_head - ldata->read_tail >= amt;
+}
+
+/**
+ *     copy_from_read_buf      -       copy read data directly
+ *     @tty: terminal device
+ *     @b: user data
+ *     @nr: size of data
+ *
+ *     Helper function to speed up unipi_tty_read.  It is only called when
+ *     ICANON is off; it copies characters straight from the tty queue to
+ *     user space directly.  It can be profitably called twice; once to
+ *     drain the space from the tail pointer to the (physical) end of the
+ *     buffer, and once to drain the space from the (physical) beginning of
+ *     the buffer to head pointer.
+ *
+ *     Called under the ldata->atomic_read_lock sem
+ *
+ *     unipi_tty_read()/consumer path:
+ *             caller holds non-exclusive termios_rwsem
+ *             read_tail published
+ */
+
+static int copy_from_read_buf(struct tty_struct *tty,
+                                     unsigned char __user **b,
+                                     size_t *nr)
+
+{
+       struct unipi_tty_data *ldata = tty->disc_data;
+       int retval;
+       size_t n;
+       size_t head = smp_load_acquire(&ldata->commit_head);
+       size_t tail = ldata->read_tail & (N_TTY_BUF_SIZE - 1);
+
+       retval = 0;
+       n = min(head - ldata->read_tail, N_TTY_BUF_SIZE - tail);
+       n = min(*nr, n);
+       if (n) {
+               const unsigned char *from = read_buf_addr(ldata, tail);
+               retval = copy_to_user(*b, from, n);
+               n -= retval;
+               //tty_audit_add_data(tty, from, n);
+               smp_store_release(&ldata->read_tail, ldata->read_tail + n);
+               *b += n;
+               *nr -= n;
+       }
+       return retval;
+}
+
+
+
+/**
+ *     unipi_tty_read          -       read function for tty
+ *     @tty: tty device
+ *     @file: file object
+ *     @buf: userspace buffer pointer
+ *     @nr: size of I/O
+ *
+ *     Perform reads for the line discipline. We are guaranteed that the
+ *     line discipline will not be closed under us but we may get multiple
+ *     parallel readers and must handle this ourselves. We may also get
+ *     a hangup. Always called in user context, may sleep.
+ *
+ *     This code must be sure never to sleep through a hangup.
+ *
+ *     unipi_tty_read()/consumer path:
+ *             claims non-exclusive termios_rwsem
+ *             publishes read_tail
+ */
+
+static ssize_t unipi_tty_read(struct tty_struct *tty, struct file *file,
+                        unsigned char __user *buf, size_t nr)
+{
+       struct unipi_tty_data *ldata = tty->disc_data;
+    struct tty_port *port = tty->port;
+       unsigned char __user *b = buf;
+       DEFINE_WAIT_FUNC(wait, woken_wake_function);
+       int minimum, time;
+       ssize_t retval = 0;
+       long timeout;
+       int packet;
+       size_t tail;
+
+       /*
+        *      Internal serialization of reads.
+        */
+       unipi_tty_trace(KERN_INFO "UNIPI_LDISC: Read start.");
+       if (file->f_flags & O_NONBLOCK) {
+               if (!mutex_trylock(&ldata->atomic_read_lock))
+                       return -EAGAIN;
+       } else {
+               if (mutex_lock_interruptible(&ldata->atomic_read_lock))
+                       return -ERESTARTSYS;
+       }
+
+       down_read(&tty->termios_rwsem);
+
+       minimum = time = 0;
+       timeout = MAX_SCHEDULE_TIMEOUT;
+
+       minimum = MIN_CHAR(tty);
+       if (minimum) {
+               time = (HZ / 10) * TIME_CHAR(tty);
+       } else {
+               timeout = (HZ / 10) * TIME_CHAR(tty);
+               minimum = 1;
+       }
+
+       packet = tty->packet;
+       tail = ldata->read_tail;
+
+       add_wait_queue(&tty->read_wait, &wait);
+       while (nr) {
+               /* First test for status change. */
+               if (packet && tty->link->ctrl_status) {         
+                       unsigned char cs;
+                       if (b != buf)
+                               break;
+                       spin_lock_irq(&tty->link->ctrl_lock);
+                       cs = tty->link->ctrl_status;
+                       tty->link->ctrl_status = 0;
+                       spin_unlock_irq(&tty->link->ctrl_lock);
+                       if (put_user(cs, b)) {
+                               retval = -EFAULT;
+                               break;
+                       }
+                       b++;
+                       nr--;
+                       break;
+               }
+
+               if (!input_available_p(tty, 0)) {
+                       up_read(&tty->termios_rwsem);
+                       //tty_buffer_flush_work(tty->port);
+            flush_work(&port->buf.work);
+                       down_read(&tty->termios_rwsem);
+                       if (!input_available_p(tty, 0)) {
+                               if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) {
+                                       retval = -EIO;
+                                       break;
+                               }
+                               if (tty_hung_up_p(file))
+                                       break;
+                               /*
+                                * Abort readers for ttys which never actually
+                                * get hung up.  See __tty_hangup().
+                                */
+#ifdef TTY_HUPPING                 
+                               if (test_bit(TTY_HUPPING, &tty->flags))
+                                       break;
 #endif
-       memset(&neuronspi_tty_ldisc, 0, sizeof(neuronspi_tty_ldisc));
-       n_tty_inherit_ops(&neuronspi_tty_ldisc);
-       neuronspi_tty_ldisc.magic                       = TTY_LDISC_MAGIC;
-       neuronspi_tty_ldisc.name                        = "unipi_tty";
-       neuronspi_tty_ldisc.owner                       = THIS_MODULE;
-       err = tty_register_ldisc(N_PROFIBUS_FDL, &neuronspi_tty_ldisc);
+                               if (!timeout)
+                                       break;
+                               if (file->f_flags & O_NONBLOCK) {
+                                       retval = -EAGAIN;
+                                       break;
+                               }
+                               if (signal_pending(current)) {
+                                       retval = -ERESTARTSYS;
+                                       break;
+                               }
+                               up_read(&tty->termios_rwsem);
+
+                               timeout = wait_woken(&wait, TASK_INTERRUPTIBLE,
+                                               timeout);
+
+                               down_read(&tty->termios_rwsem);
+                               continue;
+                       }
+               }
+
+        {
+                       int uncopied;
+
+                       /* Deal with packet mode. */
+                       if (packet && b == buf) {
+                               if (put_user(TIOCPKT_DATA, b)) {
+                                       retval = -EFAULT;
+                                       break;
+                               }
+                               b++;
+                               nr--;
+                       }
+
+                       uncopied = copy_from_read_buf(tty, &b, &nr);
+                       uncopied += copy_from_read_buf(tty, &b, &nr);
+                       if (uncopied) {
+                               retval = -EFAULT;
+                               break;
+                       }
+               }
+
+               unipi_tty_check_unthrottle(tty);
+
+               if (b - buf >= minimum)
+                       break;
+               if (time)
+                       timeout = time;
+       }
+       if (tail != ldata->read_tail)
+               unipi_tty_kick_worker(tty);
+       up_read(&tty->termios_rwsem);
+
+       remove_wait_queue(&tty->read_wait, &wait);
+       mutex_unlock(&ldata->atomic_read_lock);
+
+       if (b - buf)
+               retval = b - buf;
+
+       return retval;
+}
+
+/**
+ *     unipi_tty_write         -       write function for tty
+ *     @tty: tty device
+ *     @file: file object
+ *     @buf: userspace buffer pointer
+ *     @nr: size of I/O
+ *
+ *     Write function of the terminal device.  This is serialized with
+ *     respect to other write callers but not to termios changes, reads
+ *     and other such events.  Since the receive code will echo characters,
+ *     thus calling driver write methods, the output_lock is used in
+ *     the output processing functions called here as well as in the
+ *     echo processing function to protect the column state and space
+ *     left in the buffer.
+ *
+ *     This code must be sure never to sleep through a hangup.
+ *
+ *     Locking: output_lock to protect column state and space left
+ *              (note that the process_output*() functions take this
+ *               lock themselves)
+ */
+
+static ssize_t unipi_tty_write(struct tty_struct *tty, struct file *file,
+                          const unsigned char *buf, size_t nr)
+{
+       const unsigned char *b = buf;
+       DEFINE_WAIT_FUNC(wait, woken_wake_function);
+    struct unipi_tty_data *ldata;
+       int c;
+       ssize_t retval = 0;
+
+       unipi_tty_trace(KERN_INFO "UNIPI_LDISC: Write start.");
+       down_read(&tty->termios_rwsem);
+
+       add_wait_queue(&tty->write_wait, &wait);
+       while (1) {
+               if (signal_pending(current)) {
+                       retval = -ERESTARTSYS;
+                       break;
+               }
+               if (tty_hung_up_p(file) || (tty->link && !tty->link->count)) {
+                       retval = -EIO;
+                       break;
+               }
+        
+               ldata = tty->disc_data;
+
+               while (nr > 0) {
+                       mutex_lock(&ldata->output_lock);
+                       c = tty->ops->write(tty, b, nr);
+                       mutex_unlock(&ldata->output_lock);
+                       if (c < 0) {
+                               retval = c;
+                               goto break_out;
+                       }
+                       if (!c)
+                               break;
+                       b += c;
+                       nr -= c;
+               }
+               
+               if (!nr)
+                       break;
+               if (file->f_flags & O_NONBLOCK) {
+                       retval = -EAGAIN;
+                       break;
+               }
+               up_read(&tty->termios_rwsem);
+
+               wait_woken(&wait, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
+
+               down_read(&tty->termios_rwsem);
+       }
+break_out:
+       remove_wait_queue(&tty->write_wait, &wait);
+       if (nr && tty->fasync)
+               set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
+       up_read(&tty->termios_rwsem);
+       return (b - buf) ? b - buf : retval;
+}
+
+/**
+ *     unipi_tty_poll          -       poll method for N_TTY
+ *     @tty: terminal device
+ *     @file: file accessing it
+ *     @wait: poll table
+ *
+ *     Called when the line discipline is asked to poll() for data or
+ *     for special events. This code is not serialized with respect to
+ *     other events save open/close.
+ *
+ *     This code must be sure never to sleep through a hangup.
+ *     Called without the kernel lock held - fine
+ */
+
+static unsigned int unipi_tty_poll(struct tty_struct *tty, struct file *file,
+                                                       poll_table *wait)
+{
+       unsigned int mask = 0;
+    struct tty_port *port = tty->port;
+
+       //unipi_tty_trace(KERN_INFO "UNIPI_LDISC: Poll start.");
+       poll_wait(file, &tty->read_wait, wait);
+       poll_wait(file, &tty->write_wait, wait);
+       if (input_available_p(tty, 1))
+               mask |= POLLIN | POLLRDNORM;
+       else {
+               //tty_buffer_flush_work(tty->port);
+        flush_work(&port->buf.work);
+               if (input_available_p(tty, 1))
+                       mask |= POLLIN | POLLRDNORM;
+       }
+       if (tty->packet && tty->link->ctrl_status)
+               mask |= POLLPRI | POLLIN | POLLRDNORM;
+       if (test_bit(TTY_OTHER_CLOSED, &tty->flags))
+               mask |= POLLHUP;
+       if (tty_hung_up_p(file))
+               mask |= POLLHUP;
+       if (tty->ops->write && !tty_is_writelocked(tty) &&
+                       tty_chars_in_buffer(tty) < WAKEUP_CHARS &&
+                       tty_write_room(tty) > 0)
+               mask |= POLLOUT | POLLWRNORM;
+       return mask;
+}
+
+
+static int unipi_tty_ioctl(struct tty_struct *tty, struct file *file,
+                      unsigned int cmd, unsigned long arg)
+{
+       struct unipi_tty_data *ldata = tty->disc_data;
+       int retval;
+
+       unipi_tty_trace(KERN_INFO "UNIPI_LDISC: Ioctl start. cmd=%x", cmd);
+       switch (cmd) {
+       case TIOCOUTQ:
+               return put_user(tty_chars_in_buffer(tty), (int __user *) arg);
+       case TIOCINQ:
+               down_write(&tty->termios_rwsem);
+        retval = read_cnt(ldata);
+               up_write(&tty->termios_rwsem);
+               return put_user(retval, (unsigned int __user *) arg);
+       default:
+               return  n_tty_ioctl_helper(tty, file, cmd, arg);
+       }
+}
+
+static long unipi_tty_compat_ioctl(struct tty_struct *tty, struct file *file,
+                      unsigned int cmd, unsigned long arg)
+{
+       int retval;
+
+       unipi_tty_trace(KERN_INFO "UNIPI_LDISC: Compat_ioctl start. cmd=%x", cmd);
+       switch (cmd) {
+       case 0x5481:
+       case 0x5480:
+               if (tty->ops->ioctl) {
+                       retval = tty->ops->ioctl(tty, cmd,  (unsigned long) compat_ptr(arg));
+                       if (retval != -ENOIOCTLCMD)
+                               return retval;
+               }
+               //return tty_mode_ioctl(tty, file, cmd, (unsigned long) compat_ptr(arg));
+       default:
+               return n_tty_compat_ioctl_helper(tty, file, cmd, arg);
+       }
+}
+
+
+static struct tty_ldisc_ops unipi_tty_ops = {
+       .magic           = TTY_LDISC_MAGIC,
+       .owner                   = THIS_MODULE,
+       .name            = "unipi_tty",
+       .open            = unipi_tty_open,
+       .close           = unipi_tty_close,
+       .flush_buffer    = unipi_tty_flush_buffer,
+       .read            = unipi_tty_read,
+       .write           = unipi_tty_write,
+       .ioctl           = unipi_tty_ioctl,
+       .compat_ioctl    = unipi_tty_compat_ioctl,
+       .set_termios     = unipi_tty_set_termios,
+       .poll            = unipi_tty_poll,
+       .receive_buf     = unipi_tty_receive_buf,
+       .write_wakeup    = unipi_tty_write_wakeup,
+       .receive_buf2    = unipi_tty_receive_buf2,
+};
+
+
+
+int __init unipi_tty_init(void)
+{
+       int err;
+       unipi_tty_trace(KERN_INFO "UNIPISPI: TTY Init\n");
+       err = tty_register_ldisc(N_PROFIBUS_FDL, &unipi_tty_ops);
        if (err) {
                printk(KERN_INFO "UNIPISPI: UniPi line discipline registration failed. (%d)", err);
                return err;
index 1d98f194f990bcad34582bd0b79859690cc8fbaa..371f13374b391d6fd8b07d85c5f8756134abf7ac 100644 (file)
@@ -17,6 +17,6 @@
 
 #include "unipi_common.h"
 
-int neuronspi_tty_init(void);
+int unipi_tty_init(void);
 
 #endif /* MODULES_UNIPI_SRC_UNIPI_TTY_H_ */
index 48b6b8e548ae45b02dde4ff6bb78db9628c525ba..d0b875db14c434c96665c905fee5f9d238fc3b78 100644 (file)
@@ -67,7 +67,7 @@ void neuronspi_uart_set_ldisc(struct uart_port *port, struct ktermios *kterm)
        spi = neuronspi_s_dev[n_port->dev_index];
        n_spi = spi_get_drvdata(spi);
 #if NEURONSPI_DETAILED_DEBUG > 0
-       printk(KERN_INFO "UNIPISPI: PROFIBUS discipline set\n");
+       printk(KERN_INFO "UNIPISPI: line discipline set\n");
 #endif
        write_length = neuronspi_spi_compose_single_register_write(NEURONSPI_UART_LDISC_REGISTER, &inp_buf, &outp_buf, kterm->c_line);
        neuronspi_spi_send_message(spi, inp_buf, outp_buf, write_length, n_spi->ideal_frequency, 25, 1, 0);
@@ -124,7 +124,9 @@ int neuronspi_uart_ioctl (struct uart_port *port, unsigned int ioctl_code, unsig
                printk(KERN_INFO "UNIPISPI: IOCTL 0x5481\n");
 #endif
                write_length = neuronspi_spi_compose_single_register_write(NEURONSPI_UART_TIMEOUT_REGISTER, &inp_buf, &outp_buf, (ioctl_arg * 1000000) / n_port->baud);
-               printk(KERN_INFO "UNIPISPI: val_upper: %x, val_lower: %x", inp_buf[10], inp_buf[11]);
+#if NEURONSPI_DETAILED_DEBUG > 0
+               printk(KERN_INFO "UNIPISPI: val_upper: 0x%x, val_lower: 0x%x", inp_buf[10], inp_buf[11]);
+#endif
                neuronspi_spi_send_message(spi, inp_buf, outp_buf, write_length, n_spi->ideal_frequency, 25, 1, 0);
                kfree(inp_buf);
                kfree(outp_buf);
@@ -135,7 +137,9 @@ int neuronspi_uart_ioctl (struct uart_port *port, unsigned int ioctl_code, unsig
                printk(KERN_INFO "UNIPISPI: IOCTL 0x5480\n");
 #endif
                write_length = neuronspi_spi_compose_single_register_write(NEURONSPI_UART_TIMEOUT_REGISTER, &inp_buf, &outp_buf, ioctl_arg * 10);
-               printk(KERN_INFO "UNIPISPI: val_upper: %x, val_lower: %x", inp_buf[10], inp_buf[11]);
+#if NEURONSPI_DETAILED_DEBUG > 0
+               printk(KERN_INFO "UNIPISPI: val_upper: 0x%x, val_lower: 0x%x", inp_buf[10], inp_buf[11]);
+#endif
                neuronspi_spi_send_message(spi, inp_buf, outp_buf, write_length, n_spi->ideal_frequency, 25, 1, 0);
                kfree(inp_buf);
                kfree(outp_buf);
@@ -153,26 +157,27 @@ void neuronspi_uart_set_termios(struct uart_port *port, struct ktermios *termios
 {
        struct neuronspi_port *n_port;
        n_port = to_neuronspi_port(port, port);
-       if (old && old->c_iflag && old->c_iflag != termios->c_iflag) {
+
 #if NEURONSPI_DETAILED_DEBUG > 0
-               printk(KERN_INFO "UNIPISPI: c_iflag termios:%d\n", termios->c_iflag);
-#endif
+       if (old && old->c_iflag && old->c_iflag != termios->c_iflag) {
+               printk(KERN_INFO "UNIPI_UART: c_iflag termios:%d\n", termios->c_iflag);
        }
-#if NEURONSPI_DETAILED_DEBUG > 0
-       printk(KERN_DEBUG "UNIPISPI: TERMIOS Set, p:%d, c_cflag:%x\n", port->line, termios->c_cflag);
+       printk(KERN_INFO "UNIPI_UART: Termios port:%d new:0x%x %x %x %x", port->line,\
+         termios->c_lflag, termios->c_iflag, \
+         termios->c_oflag,  termios->c_cflag);
 #endif
        neuronspi_spi_uart_set_cflag(neuronspi_s_dev[n_port->dev_index], n_port->dev_port, termios->c_cflag);
        if (old && termios && (old->c_iflag & PARMRK) != (termios->c_iflag & PARMRK)) {
                neuronspi_uart_set_iflags(port, termios->c_iflag);
+#if 0
+               /* No more required to disable PARMRK */
                if (termios->c_iflag & PARMRK) {
                        termios->c_iflag &= ~PARMRK;
                }
-       }
-       if (old && termios && old->c_line != termios->c_line) {
-               if (termios->c_line == N_PROFIBUS_FDL) {
-#if NEURONSPI_DETAILED_DEBUG > 0
-                       printk(KERN_INFO "UNIPISPI: Line Discipline change\n");
 #endif
+       }
+       if (termios) { 
+               if (!old || old->c_line != termios->c_line) {
                        neuronspi_uart_set_ldisc(port, termios);
                }
        }