]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
4.14-stable patches
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 3 Apr 2019 14:10:58 +0000 (16:10 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 3 Apr 2019 14:10:58 +0000 (16:10 +0200)
added patches:
ext4-cleanup-bh-release-code-in-ext4_ind_remove_space.patch
lib-int_sqrt-optimize-initial-value-compute.patch
mm-mempolicy-make-mbind-return-eio-when-mpol_mf_strict-is-specified.patch
tty-serial-atmel-add-is_half_duplex-helper.patch
tty-serial-atmel-rs485-hd-w-dma-enable-rx-after-tx-is-stopped.patch

queue-4.14/ext4-cleanup-bh-release-code-in-ext4_ind_remove_space.patch [new file with mode: 0644]
queue-4.14/lib-int_sqrt-optimize-initial-value-compute.patch [new file with mode: 0644]
queue-4.14/mm-mempolicy-make-mbind-return-eio-when-mpol_mf_strict-is-specified.patch [new file with mode: 0644]
queue-4.14/series
queue-4.14/tty-serial-atmel-add-is_half_duplex-helper.patch [new file with mode: 0644]
queue-4.14/tty-serial-atmel-rs485-hd-w-dma-enable-rx-after-tx-is-stopped.patch [new file with mode: 0644]

diff --git a/queue-4.14/ext4-cleanup-bh-release-code-in-ext4_ind_remove_space.patch b/queue-4.14/ext4-cleanup-bh-release-code-in-ext4_ind_remove_space.patch
new file mode 100644 (file)
index 0000000..47ca587
--- /dev/null
@@ -0,0 +1,163 @@
+From 5e86bdda41534e17621d5a071b294943cae4376e Mon Sep 17 00:00:00 2001
+From: "zhangyi (F)" <yi.zhang@huawei.com>
+Date: Sat, 23 Mar 2019 11:56:01 -0400
+Subject: ext4: cleanup bh release code in ext4_ind_remove_space()
+
+From: zhangyi (F) <yi.zhang@huawei.com>
+
+commit 5e86bdda41534e17621d5a071b294943cae4376e upstream.
+
+Currently, we are releasing the indirect buffer where we are done with
+it in ext4_ind_remove_space(), so we can see the brelse() and
+BUFFER_TRACE() everywhere.  It seems fragile and hard to read, and we
+may probably forget to release the buffer some day.  This patch cleans
+up the code by putting of the code which releases the buffers to the
+end of the function.
+
+Signed-off-by: zhangyi (F) <yi.zhang@huawei.com>
+Signed-off-by: Theodore Ts'o <tytso@mit.edu>
+Reviewed-by: Jan Kara <jack@suse.cz>
+Cc: Jari Ruusu <jari.ruusu@gmail.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ fs/ext4/indirect.c |   47 ++++++++++++++++++++++-------------------------
+ 1 file changed, 22 insertions(+), 25 deletions(-)
+
+--- a/fs/ext4/indirect.c
++++ b/fs/ext4/indirect.c
+@@ -1219,6 +1219,7 @@ int ext4_ind_remove_space(handle_t *hand
+       ext4_lblk_t offsets[4], offsets2[4];
+       Indirect chain[4], chain2[4];
+       Indirect *partial, *partial2;
++      Indirect *p = NULL, *p2 = NULL;
+       ext4_lblk_t max_block;
+       __le32 nr = 0, nr2 = 0;
+       int n = 0, n2 = 0;
+@@ -1260,7 +1261,7 @@ int ext4_ind_remove_space(handle_t *hand
+               }
+-              partial = ext4_find_shared(inode, n, offsets, chain, &nr);
++              partial = p = ext4_find_shared(inode, n, offsets, chain, &nr);
+               if (nr) {
+                       if (partial == chain) {
+                               /* Shared branch grows from the inode */
+@@ -1285,13 +1286,11 @@ int ext4_ind_remove_space(handle_t *hand
+                               partial->p + 1,
+                               (__le32 *)partial->bh->b_data+addr_per_block,
+                               (chain+n-1) - partial);
+-                      BUFFER_TRACE(partial->bh, "call brelse");
+-                      brelse(partial->bh);
+                       partial--;
+               }
+ end_range:
+-              partial2 = ext4_find_shared(inode, n2, offsets2, chain2, &nr2);
++              partial2 = p2 = ext4_find_shared(inode, n2, offsets2, chain2, &nr2);
+               if (nr2) {
+                       if (partial2 == chain2) {
+                               /*
+@@ -1321,16 +1320,14 @@ end_range:
+                                          (__le32 *)partial2->bh->b_data,
+                                          partial2->p,
+                                          (chain2+n2-1) - partial2);
+-                      BUFFER_TRACE(partial2->bh, "call brelse");
+-                      brelse(partial2->bh);
+                       partial2--;
+               }
+               goto do_indirects;
+       }
+       /* Punch happened within the same level (n == n2) */
+-      partial = ext4_find_shared(inode, n, offsets, chain, &nr);
+-      partial2 = ext4_find_shared(inode, n2, offsets2, chain2, &nr2);
++      partial = p = ext4_find_shared(inode, n, offsets, chain, &nr);
++      partial2 = p2 = ext4_find_shared(inode, n2, offsets2, chain2, &nr2);
+       /* Free top, but only if partial2 isn't its subtree. */
+       if (nr) {
+@@ -1387,15 +1384,7 @@ end_range:
+                                          partial->p + 1,
+                                          partial2->p,
+                                          (chain+n-1) - partial);
+-                      while (partial > chain) {
+-                              BUFFER_TRACE(partial->bh, "call brelse");
+-                              brelse(partial->bh);
+-                      }
+-                      while (partial2 > chain2) {
+-                              BUFFER_TRACE(partial2->bh, "call brelse");
+-                              brelse(partial2->bh);
+-                      }
+-                      return 0;
++                      goto cleanup;
+               }
+               /*
+@@ -1410,8 +1399,6 @@ end_range:
+                                          partial->p + 1,
+                                          (__le32 *)partial->bh->b_data+addr_per_block,
+                                          (chain+n-1) - partial);
+-                      BUFFER_TRACE(partial->bh, "call brelse");
+-                      brelse(partial->bh);
+                       partial--;
+               }
+               if (partial2 > chain2 && depth2 <= depth) {
+@@ -1419,11 +1406,21 @@ end_range:
+                                          (__le32 *)partial2->bh->b_data,
+                                          partial2->p,
+                                          (chain2+n2-1) - partial2);
+-                      BUFFER_TRACE(partial2->bh, "call brelse");
+-                      brelse(partial2->bh);
+                       partial2--;
+               }
+       }
++
++cleanup:
++      while (p && p > chain) {
++              BUFFER_TRACE(p->bh, "call brelse");
++              brelse(p->bh);
++              p--;
++      }
++      while (p2 && p2 > chain2) {
++              BUFFER_TRACE(p2->bh, "call brelse");
++              brelse(p2->bh);
++              p2--;
++      }
+       return 0;
+ do_indirects:
+@@ -1431,7 +1428,7 @@ do_indirects:
+       switch (offsets[0]) {
+       default:
+               if (++n >= n2)
+-                      return 0;
++                      break;
+               nr = i_data[EXT4_IND_BLOCK];
+               if (nr) {
+                       ext4_free_branches(handle, inode, NULL, &nr, &nr+1, 1);
+@@ -1439,7 +1436,7 @@ do_indirects:
+               }
+       case EXT4_IND_BLOCK:
+               if (++n >= n2)
+-                      return 0;
++                      break;
+               nr = i_data[EXT4_DIND_BLOCK];
+               if (nr) {
+                       ext4_free_branches(handle, inode, NULL, &nr, &nr+1, 2);
+@@ -1447,7 +1444,7 @@ do_indirects:
+               }
+       case EXT4_DIND_BLOCK:
+               if (++n >= n2)
+-                      return 0;
++                      break;
+               nr = i_data[EXT4_TIND_BLOCK];
+               if (nr) {
+                       ext4_free_branches(handle, inode, NULL, &nr, &nr+1, 3);
+@@ -1456,5 +1453,5 @@ do_indirects:
+       case EXT4_TIND_BLOCK:
+               ;
+       }
+-      return 0;
++      goto cleanup;
+ }
diff --git a/queue-4.14/lib-int_sqrt-optimize-initial-value-compute.patch b/queue-4.14/lib-int_sqrt-optimize-initial-value-compute.patch
new file mode 100644 (file)
index 0000000..82b6c2a
--- /dev/null
@@ -0,0 +1,91 @@
+From f8ae107eef209bff29a5816bc1aad40d5cd69a80 Mon Sep 17 00:00:00 2001
+From: Peter Zijlstra <peterz@infradead.org>
+Date: Fri, 17 Nov 2017 15:28:08 -0800
+Subject: lib/int_sqrt: optimize initial value compute
+
+From: Peter Zijlstra <peterz@infradead.org>
+
+commit f8ae107eef209bff29a5816bc1aad40d5cd69a80 upstream.
+
+The initial value (@m) compute is:
+
+       m = 1UL << (BITS_PER_LONG - 2);
+       while (m > x)
+               m >>= 2;
+
+Which is a linear search for the highest even bit smaller or equal to @x
+We can implement this using a binary search using __fls() (or better when
+its hardware implemented).
+
+       m = 1UL << (__fls(x) & ~1UL);
+
+Especially for small values of @x; which are the more common arguments
+when doing a CDF on idle times; the linear search is near to worst case,
+while the binary search of __fls() is a constant 6 (or 5 on 32bit)
+branches.
+
+      cycles:                 branches:              branch-misses:
+
+PRE:
+
+hot:   43.633557 +- 0.034373  45.333132 +- 0.002277  0.023529 +- 0.000681
+cold: 207.438411 +- 0.125840  45.333132 +- 0.002277  6.976486 +- 0.004219
+
+SOFTWARE FLS:
+
+hot:   29.576176 +- 0.028850  26.666730 +- 0.004511  0.019463 +- 0.000663
+cold: 165.947136 +- 0.188406  26.666746 +- 0.004511  6.133897 +- 0.004386
+
+HARDWARE FLS:
+
+hot:   24.720922 +- 0.025161  20.666784 +- 0.004509  0.020836 +- 0.000677
+cold: 132.777197 +- 0.127471  20.666776 +- 0.004509  5.080285 +- 0.003874
+
+Averages computed over all values <128k using a LFSR to generate order.
+Cold numbers have a LFSR based branch trace buffer 'confuser' ran between
+each int_sqrt() invocation.
+
+Link: http://lkml.kernel.org/r/20171020164644.936577234@infradead.org
+Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
+Suggested-by: Joe Perches <joe@perches.com>
+Acked-by: Will Deacon <will.deacon@arm.com>
+Acked-by: Linus Torvalds <torvalds@linux-foundation.org>
+Cc: Anshul Garg <aksgarg1989@gmail.com>
+Cc: Davidlohr Bueso <dave@stgolabs.net>
+Cc: David Miller <davem@davemloft.net>
+Cc: Ingo Molnar <mingo@kernel.org>
+Cc: Kees Cook <keescook@chromium.org>
+Cc: Matthew Wilcox <mawilcox@microsoft.com>
+Cc: Michael Davidson <md@google.com>
+Cc: Thomas Gleixner <tglx@linutronix.de>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
+Cc: Joe Perches <joe@perches.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ lib/int_sqrt.c |    6 ++----
+ 1 file changed, 2 insertions(+), 4 deletions(-)
+
+--- a/lib/int_sqrt.c
++++ b/lib/int_sqrt.c
+@@ -8,6 +8,7 @@
+ #include <linux/kernel.h>
+ #include <linux/export.h>
++#include <linux/bitops.h>
+ /**
+  * int_sqrt - rough approximation to sqrt
+@@ -22,10 +23,7 @@ unsigned long int_sqrt(unsigned long x)
+       if (x <= 1)
+               return x;
+-      m = 1UL << (BITS_PER_LONG - 2);
+-      while (m > x)
+-              m >>= 2;
+-
++      m = 1UL << (__fls(x) & ~1UL);
+       while (m != 0) {
+               b = y + m;
+               y >>= 1;
diff --git a/queue-4.14/mm-mempolicy-make-mbind-return-eio-when-mpol_mf_strict-is-specified.patch b/queue-4.14/mm-mempolicy-make-mbind-return-eio-when-mpol_mf_strict-is-specified.patch
new file mode 100644 (file)
index 0000000..a39c136
--- /dev/null
@@ -0,0 +1,142 @@
+From a7f40cfe3b7ada57af9b62fd28430eeb4a7cfcb7 Mon Sep 17 00:00:00 2001
+From: Yang Shi <yang.shi@linux.alibaba.com>
+Date: Thu, 28 Mar 2019 20:43:55 -0700
+Subject: mm: mempolicy: make mbind() return -EIO when MPOL_MF_STRICT is specified
+
+From: Yang Shi <yang.shi@linux.alibaba.com>
+
+commit a7f40cfe3b7ada57af9b62fd28430eeb4a7cfcb7 upstream.
+
+When MPOL_MF_STRICT was specified and an existing page was already on a
+node that does not follow the policy, mbind() should return -EIO.  But
+commit 6f4576e3687b ("mempolicy: apply page table walker on
+queue_pages_range()") broke the rule.
+
+And commit c8633798497c ("mm: mempolicy: mbind and migrate_pages support
+thp migration") didn't return the correct value for THP mbind() too.
+
+If MPOL_MF_STRICT is set, ignore vma_migratable() to make sure it
+reaches queue_pages_to_pte_range() or queue_pages_pmd() to check if an
+existing page was already on a node that does not follow the policy.
+And, non-migratable vma may be used, return -EIO too if MPOL_MF_MOVE or
+MPOL_MF_MOVE_ALL was specified.
+
+Tested with https://github.com/metan-ucw/ltp/blob/master/testcases/kernel/syscalls/mbind/mbind02.c
+
+[akpm@linux-foundation.org: tweak code comment]
+Link: http://lkml.kernel.org/r/1553020556-38583-1-git-send-email-yang.shi@linux.alibaba.com
+Fixes: 6f4576e3687b ("mempolicy: apply page table walker on queue_pages_range()")
+Signed-off-by: Yang Shi <yang.shi@linux.alibaba.com>
+Signed-off-by: Oscar Salvador <osalvador@suse.de>
+Reported-by: Cyril Hrubis <chrubis@suse.cz>
+Suggested-by: Kirill A. Shutemov <kirill@shutemov.name>
+Acked-by: Rafael Aquini <aquini@redhat.com>
+Reviewed-by: Oscar Salvador <osalvador@suse.de>
+Acked-by: David Rientjes <rientjes@google.com>
+Cc: Vlastimil Babka <vbabka@suse.cz>
+Cc: <stable@vger.kernel.org>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+
+---
+ mm/mempolicy.c |   40 +++++++++++++++++++++++++++++++++-------
+ 1 file changed, 33 insertions(+), 7 deletions(-)
+
+--- a/mm/mempolicy.c
++++ b/mm/mempolicy.c
+@@ -427,6 +427,13 @@ static inline bool queue_pages_required(
+       return node_isset(nid, *qp->nmask) == !(flags & MPOL_MF_INVERT);
+ }
++/*
++ * queue_pages_pmd() has three possible return values:
++ * 1 - pages are placed on the right node or queued successfully.
++ * 0 - THP was split.
++ * -EIO - is migration entry or MPOL_MF_STRICT was specified and an existing
++ *        page was already on a node that does not follow the policy.
++ */
+ static int queue_pages_pmd(pmd_t *pmd, spinlock_t *ptl, unsigned long addr,
+                               unsigned long end, struct mm_walk *walk)
+ {
+@@ -436,7 +443,7 @@ static int queue_pages_pmd(pmd_t *pmd, s
+       unsigned long flags;
+       if (unlikely(is_pmd_migration_entry(*pmd))) {
+-              ret = 1;
++              ret = -EIO;
+               goto unlock;
+       }
+       page = pmd_page(*pmd);
+@@ -462,8 +469,15 @@ static int queue_pages_pmd(pmd_t *pmd, s
+       ret = 1;
+       flags = qp->flags;
+       /* go to thp migration */
+-      if (flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL))
++      if (flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL)) {
++              if (!vma_migratable(walk->vma)) {
++                      ret = -EIO;
++                      goto unlock;
++              }
++
+               migrate_page_add(page, qp->pagelist, flags);
++      } else
++              ret = -EIO;
+ unlock:
+       spin_unlock(ptl);
+ out:
+@@ -488,8 +502,10 @@ static int queue_pages_pte_range(pmd_t *
+       ptl = pmd_trans_huge_lock(pmd, vma);
+       if (ptl) {
+               ret = queue_pages_pmd(pmd, ptl, addr, end, walk);
+-              if (ret)
++              if (ret > 0)
+                       return 0;
++              else if (ret < 0)
++                      return ret;
+       }
+       if (pmd_trans_unstable(pmd))
+@@ -526,11 +542,16 @@ retry:
+                       goto retry;
+               }
+-              migrate_page_add(page, qp->pagelist, flags);
++              if (flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL)) {
++                      if (!vma_migratable(vma))
++                              break;
++                      migrate_page_add(page, qp->pagelist, flags);
++              } else
++                      break;
+       }
+       pte_unmap_unlock(pte - 1, ptl);
+       cond_resched();
+-      return 0;
++      return addr != end ? -EIO : 0;
+ }
+ static int queue_pages_hugetlb(pte_t *pte, unsigned long hmask,
+@@ -600,7 +621,12 @@ static int queue_pages_test_walk(unsigne
+       unsigned long endvma = vma->vm_end;
+       unsigned long flags = qp->flags;
+-      if (!vma_migratable(vma))
++      /*
++       * Need check MPOL_MF_STRICT to return -EIO if possible
++       * regardless of vma_migratable
++       */
++      if (!vma_migratable(vma) &&
++          !(flags & MPOL_MF_STRICT))
+               return 1;
+       if (endvma > end)
+@@ -627,7 +653,7 @@ static int queue_pages_test_walk(unsigne
+       }
+       /* queue pages from current vma */
+-      if (flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL))
++      if (flags & MPOL_MF_VALID)
+               return 0;
+       return 1;
+ }
index e7069e2324288439680af7dc9efe3fb032433eef..9f24c2567c6cb6ed301c9101cf7137a371d66ca4 100644 (file)
@@ -1 +1,6 @@
 arm64-debug-don-t-propagate-unknown-far-into-si_code-for-debug-signals.patch
+ext4-cleanup-bh-release-code-in-ext4_ind_remove_space.patch
+lib-int_sqrt-optimize-initial-value-compute.patch
+tty-serial-atmel-add-is_half_duplex-helper.patch
+tty-serial-atmel-rs485-hd-w-dma-enable-rx-after-tx-is-stopped.patch
+mm-mempolicy-make-mbind-return-eio-when-mpol_mf_strict-is-specified.patch
diff --git a/queue-4.14/tty-serial-atmel-add-is_half_duplex-helper.patch b/queue-4.14/tty-serial-atmel-add-is_half_duplex-helper.patch
new file mode 100644 (file)
index 0000000..1da0f2c
--- /dev/null
@@ -0,0 +1,80 @@
+From f3040983132bf3477acd45d2452a906e67c2fec9 Mon Sep 17 00:00:00 2001
+From: Razvan Stefanescu <razvan.stefanescu@microchip.com>
+Date: Tue, 19 Mar 2019 15:20:34 +0200
+Subject: tty/serial: atmel: Add is_half_duplex helper
+
+From: Razvan Stefanescu <razvan.stefanescu@microchip.com>
+
+commit f3040983132bf3477acd45d2452a906e67c2fec9 upstream.
+
+Use a helper function to check that a port needs to use half duplex
+communication, replacing several occurrences of multi-line bit checking.
+
+Fixes: b389f173aaa1 ("tty/serial: atmel: RS485 half duplex w/DMA: enable RX after TX is done")
+Cc: stable <stable@vger.kernel.org>
+Signed-off-by: Razvan Stefanescu <razvan.stefanescu@microchip.com>
+Acked-by: Richard Genoud <richard.genoud@gmail.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+
+---
+ drivers/tty/serial/atmel_serial.c |   19 +++++++++++--------
+ 1 file changed, 11 insertions(+), 8 deletions(-)
+
+--- a/drivers/tty/serial/atmel_serial.c
++++ b/drivers/tty/serial/atmel_serial.c
+@@ -238,6 +238,12 @@ static inline void atmel_uart_write_char
+       __raw_writeb(value, port->membase + ATMEL_US_THR);
+ }
++static inline int atmel_uart_is_half_duplex(struct uart_port *port)
++{
++      return (port->rs485.flags & SER_RS485_ENABLED) &&
++              !(port->rs485.flags & SER_RS485_RX_DURING_TX);
++}
++
+ #ifdef CONFIG_SERIAL_ATMEL_PDC
+ static bool atmel_use_pdc_rx(struct uart_port *port)
+ {
+@@ -489,9 +495,9 @@ static void atmel_stop_tx(struct uart_po
+       /* Disable interrupts */
+       atmel_uart_writel(port, ATMEL_US_IDR, atmel_port->tx_done_mask);
+-      if ((port->rs485.flags & SER_RS485_ENABLED) &&
+-          !(port->rs485.flags & SER_RS485_RX_DURING_TX))
++      if (atmel_uart_is_half_duplex(port))
+               atmel_start_rx(port);
++
+ }
+ /*
+@@ -508,8 +514,7 @@ static void atmel_start_tx(struct uart_p
+               return;
+       if (atmel_use_pdc_tx(port) || atmel_use_dma_tx(port))
+-              if ((port->rs485.flags & SER_RS485_ENABLED) &&
+-                  !(port->rs485.flags & SER_RS485_RX_DURING_TX))
++              if (atmel_uart_is_half_duplex(port))
+                       atmel_stop_rx(port);
+       if (atmel_use_pdc_tx(port))
+@@ -806,8 +811,7 @@ static void atmel_complete_tx_dma(void *
+        */
+       if (!uart_circ_empty(xmit))
+               atmel_tasklet_schedule(atmel_port, &atmel_port->tasklet_tx);
+-      else if ((port->rs485.flags & SER_RS485_ENABLED) &&
+-               !(port->rs485.flags & SER_RS485_RX_DURING_TX)) {
++      else if (atmel_uart_is_half_duplex(port)) {
+               /* DMA done, stop TX, start RX for RS485 */
+               atmel_start_rx(port);
+       }
+@@ -1383,8 +1387,7 @@ static void atmel_tx_pdc(struct uart_por
+               atmel_uart_writel(port, ATMEL_US_IER,
+                                 atmel_port->tx_done_mask);
+       } else {
+-              if ((port->rs485.flags & SER_RS485_ENABLED) &&
+-                  !(port->rs485.flags & SER_RS485_RX_DURING_TX)) {
++              if (atmel_uart_is_half_duplex(port)) {
+                       /* DMA done, stop TX, start RX for RS485 */
+                       atmel_start_rx(port);
+               }
diff --git a/queue-4.14/tty-serial-atmel-rs485-hd-w-dma-enable-rx-after-tx-is-stopped.patch b/queue-4.14/tty-serial-atmel-rs485-hd-w-dma-enable-rx-after-tx-is-stopped.patch
new file mode 100644 (file)
index 0000000..4345260
--- /dev/null
@@ -0,0 +1,79 @@
+From 69646d7a3689fbe1a65ae90397d22ac3f1b8d40f Mon Sep 17 00:00:00 2001
+From: Razvan Stefanescu <razvan.stefanescu@microchip.com>
+Date: Tue, 19 Mar 2019 15:20:35 +0200
+Subject: tty/serial: atmel: RS485 HD w/DMA: enable RX after TX is stopped
+
+From: Razvan Stefanescu <razvan.stefanescu@microchip.com>
+
+commit 69646d7a3689fbe1a65ae90397d22ac3f1b8d40f upstream.
+
+In half-duplex operation, RX should be started after TX completes.
+
+If DMA is used, there is a case when the DMA transfer completes but the
+TX FIFO is not emptied, so the RX cannot be restarted just yet.
+
+Use a boolean variable to store this state and rearm TX interrupt mask
+to be signaled again that the transfer finished. In interrupt transmit
+handler this variable is used to start RX. A warning message is generated
+if RX is activated before TX fifo is cleared.
+
+Fixes: b389f173aaa1 ("tty/serial: atmel: RS485 half duplex w/DMA: enable
+RX after TX is done")
+Signed-off-by: Razvan Stefanescu <razvan.stefanescu@microchip.com>
+Acked-by: Richard Genoud <richard.genoud@gmail.com>
+Cc: stable <stable@vger.kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ drivers/tty/serial/atmel_serial.c |   24 +++++++++++++++++++++---
+ 1 file changed, 21 insertions(+), 3 deletions(-)
+
+--- a/drivers/tty/serial/atmel_serial.c
++++ b/drivers/tty/serial/atmel_serial.c
+@@ -176,6 +176,8 @@ struct atmel_uart_port {
+       unsigned int            pending_status;
+       spinlock_t              lock_suspended;
++      bool                    hd_start_rx;    /* can start RX during half-duplex operation */
++
+ #ifdef CONFIG_PM
+       struct {
+               u32             cr;
+@@ -812,8 +814,13 @@ static void atmel_complete_tx_dma(void *
+       if (!uart_circ_empty(xmit))
+               atmel_tasklet_schedule(atmel_port, &atmel_port->tasklet_tx);
+       else if (atmel_uart_is_half_duplex(port)) {
+-              /* DMA done, stop TX, start RX for RS485 */
+-              atmel_start_rx(port);
++              /*
++               * DMA done, re-enable TXEMPTY and signal that we can stop
++               * TX and start RX for RS485
++               */
++              atmel_port->hd_start_rx = true;
++              atmel_uart_writel(port, ATMEL_US_IER,
++                                atmel_port->tx_done_mask);
+       }
+       spin_unlock_irqrestore(&port->lock, flags);
+@@ -1258,9 +1265,20 @@ atmel_handle_transmit(struct uart_port *
+       struct atmel_uart_port *atmel_port = to_atmel_uart_port(port);
+       if (pending & atmel_port->tx_done_mask) {
+-              /* Either PDC or interrupt transmission */
+               atmel_uart_writel(port, ATMEL_US_IDR,
+                                 atmel_port->tx_done_mask);
++
++              /* Start RX if flag was set and FIFO is empty */
++              if (atmel_port->hd_start_rx) {
++                      if (!(atmel_uart_readl(port, ATMEL_US_CSR)
++                                      & ATMEL_US_TXEMPTY))
++                              dev_warn(port->dev, "Should start RX, but TX fifo is not empty\n");
++
++                      atmel_port->hd_start_rx = false;
++                      atmel_start_rx(port);
++                      return;
++              }
++
+               atmel_tasklet_schedule(atmel_port, &atmel_port->tasklet_tx);
+       }
+ }