Patch [1-2] are pre patches. Patch [3] fix TX fifo corruption.
Hugo Villeneuve (2): [Backport] serial: sc16is7xx: refactor FIFO access functions to increase commonality [Backport] serial: sc16is7xx: fix TX fifo corruption
Jiri Slaby (SUSE) (1): [Backport] kfifo: add kfifo_out_linear{,_ptr}()
drivers/tty/serial/sc16is7xx.c | 67 ++++++++++++++-------------------- include/linux/kfifo.h | 63 ++++++++++++++++++++++++++++++++ lib/kfifo.c | 26 +++++++++++++ 3 files changed, 117 insertions(+), 39 deletions(-)
From: Hugo Villeneuve hvilleneuve@dimonoff.com
mainline inclusion from mainline-v6.8-rc1 commit f031d763dcb0b4dbd4bbf1d4324f7ca91761d3b1 category: feature bugzilla: https://gitee.com/src-openeuler/kernel/issues/IAOXYG CVE: CVE-2024-44951
Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?i...
--------------------------------
serial: sc16is7xx: refactor FIFO access functions to increase commonality
Simplify FIFO access functions by avoiding to declare a struct sc16is7xx_port *s variable within each function.
This is mainly done to have more commonality between the max310x and sc16is7xx drivers.
Signed-off-by: Hugo Villeneuve hvilleneuve@dimonoff.com Link: https://lore.kernel.org/r/20231221231823.2327894-15-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Signed-off-by: Zhao Yipeng zhaoyipeng5@huawei.com --- drivers/tty/serial/sc16is7xx.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index f290fbe21d63..1e555421cdd2 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -378,17 +378,15 @@ static void sc16is7xx_port_write(struct uart_port *port, u8 reg, u8 val) regmap_write(one->regmap, reg, val); }
-static void sc16is7xx_fifo_read(struct uart_port *port, unsigned int rxlen) +static void sc16is7xx_fifo_read(struct uart_port *port, u8 *rxbuf, unsigned int rxlen) { - struct sc16is7xx_port *s = dev_get_drvdata(port->dev); struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
- regmap_noinc_read(one->regmap, SC16IS7XX_RHR_REG, s->buf, rxlen); + regmap_noinc_read(one->regmap, SC16IS7XX_RHR_REG, rxbuf, rxlen); }
-static void sc16is7xx_fifo_write(struct uart_port *port, u8 to_send) +static void sc16is7xx_fifo_write(struct uart_port *port, u8 *txbuf, u8 to_send) { - struct sc16is7xx_port *s = dev_get_drvdata(port->dev); struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
/* @@ -398,7 +396,7 @@ static void sc16is7xx_fifo_write(struct uart_port *port, u8 to_send) if (unlikely(!to_send)) return;
- regmap_noinc_write(one->regmap, SC16IS7XX_THR_REG, s->buf, to_send); + regmap_noinc_write(one->regmap, SC16IS7XX_THR_REG, txbuf, to_send); }
static void sc16is7xx_port_update(struct uart_port *port, u8 reg, @@ -595,7 +593,7 @@ static void sc16is7xx_handle_rx(struct uart_port *port, unsigned int rxlen, s->buf[0] = sc16is7xx_port_read(port, SC16IS7XX_RHR_REG); bytes_read = 1; } else { - sc16is7xx_fifo_read(port, rxlen); + sc16is7xx_fifo_read(port, s->buf, rxlen); bytes_read = rxlen; }
@@ -684,7 +682,7 @@ static void sc16is7xx_handle_tx(struct uart_port *port) uart_xmit_advance(port, 1); }
- sc16is7xx_fifo_write(port, to_send); + sc16is7xx_fifo_write(port, s->buf, to_send); }
uart_port_lock_irqsave(port, &flags);
From: "Jiri Slaby (SUSE)" jirislaby@kernel.org
mainline inclusion from mainline-v6.10-rc1 commit 4edd7e96a1f159f43bd1cb82616f81eaddd54262 category: feature bugzilla: https://gitee.com/src-openeuler/kernel/issues/IAOXYG CVE: CVE-2024-44951
Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?i...
--------------------------------
kfifo: add kfifo_out_linear{,_ptr}()
These are helpers which are going to be used in the serial layer. We need a wrapper around kfifo which provides us with a tail (sometimes "tail" offset, sometimes a pointer) to the kfifo data. And which returns count of available data -- but not larger than to the end of the buffer (hence _linear in the names). I.e. something like CIRC_CNT_TO_END() in the legacy circ_buf.
This patch adds such two helpers.
Signed-off-by: Jiri Slaby (SUSE) jirislaby@kernel.org Cc: Stefani Seibold stefani@seibold.net Cc: Andrew Morton akpm@linux-foundation.org Link: https://lore.kernel.org/r/20240405060826.2521-4-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Signed-off-by: Zhao Yipeng zhaoyipeng5@huawei.com --- include/linux/kfifo.h | 63 +++++++++++++++++++++++++++++++++++++++++++ lib/kfifo.c | 26 ++++++++++++++++++ 2 files changed, 89 insertions(+)
diff --git a/include/linux/kfifo.h b/include/linux/kfifo.h index 0b35a41440ff..0f2e7effe17c 100644 --- a/include/linux/kfifo.h +++ b/include/linux/kfifo.h @@ -828,6 +828,63 @@ __kfifo_uint_must_check_helper( \ }) \ )
+/** + * kfifo_out_linear - gets a tail of/offset to available data + * @fifo: address of the fifo to be used + * @tail: pointer to an unsigned int to store the value of tail + * @n: max. number of elements to point at + * + * This macro obtains the offset (tail) to the available data in the fifo + * buffer and returns the + * numbers of elements available. It returns the available count till the end + * of data or till the end of the buffer. So that it can be used for linear + * data processing (like memcpy() of (@fifo->data + @tail) with count + * returned). + * + * Note that with only one concurrent reader and one concurrent + * writer, you don't need extra locking to use these macro. + */ +#define kfifo_out_linear(fifo, tail, n) \ +__kfifo_uint_must_check_helper( \ +({ \ + typeof((fifo) + 1) __tmp = (fifo); \ + unsigned int *__tail = (tail); \ + unsigned long __n = (n); \ + const size_t __recsize = sizeof(*__tmp->rectype); \ + struct __kfifo *__kfifo = &__tmp->kfifo; \ + (__recsize) ? \ + __kfifo_out_linear_r(__kfifo, __tail, __n, __recsize) : \ + __kfifo_out_linear(__kfifo, __tail, __n); \ +}) \ +) + +/** + * kfifo_out_linear_ptr - gets a pointer to the available data + * @fifo: address of the fifo to be used + * @ptr: pointer to data to store the pointer to tail + * @n: max. number of elements to point at + * + * Similarly to kfifo_out_linear(), this macro obtains the pointer to the + * available data in the fifo buffer and returns the numbers of elements + * available. It returns the available count till the end of available data or + * till the end of the buffer. So that it can be used for linear data + * processing (like memcpy() of @ptr with count returned). + * + * Note that with only one concurrent reader and one concurrent + * writer, you don't need extra locking to use these macro. + */ +#define kfifo_out_linear_ptr(fifo, ptr, n) \ +__kfifo_uint_must_check_helper( \ +({ \ + typeof((fifo) + 1) ___tmp = (fifo); \ + unsigned int ___tail; \ + unsigned int ___n = kfifo_out_linear(___tmp, &___tail, (n)); \ + *(ptr) = ___tmp->kfifo.data + ___tail * kfifo_esize(___tmp); \ + ___n; \ +}) \ +) + + extern int __kfifo_alloc(struct __kfifo *fifo, unsigned int size, size_t esize, gfp_t gfp_mask);
@@ -857,6 +914,9 @@ extern unsigned int __kfifo_dma_out_prepare(struct __kfifo *fifo, extern unsigned int __kfifo_out_peek(struct __kfifo *fifo, void *buf, unsigned int len);
+extern unsigned int __kfifo_out_linear(struct __kfifo *fifo, + unsigned int *tail, unsigned int n); + extern unsigned int __kfifo_in_r(struct __kfifo *fifo, const void *buf, unsigned int len, size_t recsize);
@@ -888,6 +948,9 @@ extern void __kfifo_skip_r(struct __kfifo *fifo, size_t recsize); extern unsigned int __kfifo_out_peek_r(struct __kfifo *fifo, void *buf, unsigned int len, size_t recsize);
+extern unsigned int __kfifo_out_linear_r(struct __kfifo *fifo, + unsigned int *tail, unsigned int n, size_t recsize); + extern unsigned int __kfifo_max_r(unsigned int len, size_t recsize);
#endif diff --git a/lib/kfifo.c b/lib/kfifo.c index 12f5a347aa13..abad82f6e135 100644 --- a/lib/kfifo.c +++ b/lib/kfifo.c @@ -163,6 +163,19 @@ unsigned int __kfifo_out_peek(struct __kfifo *fifo, } EXPORT_SYMBOL(__kfifo_out_peek);
+unsigned int __kfifo_out_linear(struct __kfifo *fifo, + unsigned int *tail, unsigned int n) +{ + unsigned int size = fifo->mask + 1; + unsigned int off = fifo->out & fifo->mask; + + if (tail) + *tail = off; + + return min3(n, fifo->in - fifo->out, size - off); +} +EXPORT_SYMBOL(__kfifo_out_linear); + unsigned int __kfifo_out(struct __kfifo *fifo, void *buf, unsigned int len) { @@ -473,6 +486,19 @@ unsigned int __kfifo_out_peek_r(struct __kfifo *fifo, void *buf, } EXPORT_SYMBOL(__kfifo_out_peek_r);
+unsigned int __kfifo_out_linear_r(struct __kfifo *fifo, + unsigned int *tail, unsigned int n, size_t recsize) +{ + if (fifo->in == fifo->out) + return 0; + + if (tail) + *tail = fifo->out + recsize; + + return min(n, __kfifo_peek_n(fifo, recsize)); +} +EXPORT_SYMBOL(__kfifo_out_linear_r); + unsigned int __kfifo_out_r(struct __kfifo *fifo, void *buf, unsigned int len, size_t recsize) {
From: Hugo Villeneuve hvilleneuve@dimonoff.com
mainline inclusion from mainline-v6.11-rc3 commit 133f4c00b8b2bfcacead9b81e7e8edfceb4b06c4 category: bugfix bugzilla: https://gitee.com/src-openeuler/kernel/issues/IAOXYG CVE: CVE-2024-44951
Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?i...
--------------------------------
serial: sc16is7xx: fix TX fifo corruption
Sometimes, when a packet is received on channel A at almost the same time as a packet is about to be transmitted on channel B, we observe with a logic analyzer that the received packet on channel A is transmitted on channel B. In other words, the Tx buffer data on channel B is corrupted with data from channel A.
The problem appeared since commit 4409df5866b7 ("serial: sc16is7xx: change EFR lock to operate on each channels"), which changed the EFR locking to operate on each channel instead of chip-wise.
This commit has introduced a regression, because the EFR lock is used not only to protect the EFR registers access, but also, in a very obscure and undocumented way, to protect access to the data buffer, which is shared by the Tx and Rx handlers, but also by each channel of the IC.
Fix this regression first by switching to kfifo_out_linear_ptr() in sc16is7xx_handle_tx() to eliminate the need for a shared Rx/Tx buffer.
Secondly, replace the chip-wise Rx buffer with a separate Rx buffer for each channel.
Fixes: 4409df5866b7 ("serial: sc16is7xx: change EFR lock to operate on each channels") Cc: stable@vger.kernel.org Signed-off-by: Hugo Villeneuve hvilleneuve@dimonoff.com Link: https://lore.kernel.org/r/20240723125302.1305372-2-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Conflicts: drivers/tty/serial/sc16is7xx.c [The cause of the conflict comes from the prefix "1788cf6a91d9fa9aa61fc2917afe192c23d67f6a tty: serial: switch from circ_buf to kfifo", which modifies a large number of files. After analysis, only the changes of sc16is7xx.c are related to the fix patch, and the modifications do not involve external interfaces. Therefore, only this part is merged to resolve the conflict.] Signed-off-by: Zhao Yipeng zhaoyipeng5@huawei.com --- drivers/tty/serial/sc16is7xx.c | 57 ++++++++++++++-------------------- 1 file changed, 24 insertions(+), 33 deletions(-)
diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 1e555421cdd2..673aeda71388 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -329,6 +329,7 @@ struct sc16is7xx_one { struct kthread_work reg_work; struct kthread_delayed_work ms_work; struct sc16is7xx_one_config config; + unsigned char buf[SC16IS7XX_FIFO_SIZE]; /* Rx buffer. */ bool irda_mode; unsigned int old_mctrl; }; @@ -341,7 +342,6 @@ struct sc16is7xx_port { unsigned long gpio_valid_mask; #endif u8 mctrl_mask; - unsigned char buf[SC16IS7XX_FIFO_SIZE]; struct kthread_worker kworker; struct task_struct *kworker_task; struct sc16is7xx_one p[]; @@ -566,18 +566,18 @@ static int sc16is7xx_set_baud(struct uart_port *port, int baud) static void sc16is7xx_handle_rx(struct uart_port *port, unsigned int rxlen, unsigned int iir) { - struct sc16is7xx_port *s = dev_get_drvdata(port->dev); + struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); unsigned int lsr = 0, bytes_read, i; bool read_lsr = (iir == SC16IS7XX_IIR_RLSE_SRC) ? true : false; u8 ch, flag;
- if (unlikely(rxlen >= sizeof(s->buf))) { + if (unlikely(rxlen >= sizeof(one->buf))) { dev_warn_ratelimited(port->dev, "ttySC%i: Possible RX FIFO overrun: %d\n", port->line, rxlen); port->icount.buf_overrun++; /* Ensure sanity of RX level */ - rxlen = sizeof(s->buf); + rxlen = sizeof(one->buf); }
while (rxlen) { @@ -590,10 +590,10 @@ static void sc16is7xx_handle_rx(struct uart_port *port, unsigned int rxlen, lsr = 0;
if (read_lsr) { - s->buf[0] = sc16is7xx_port_read(port, SC16IS7XX_RHR_REG); + one->buf[0] = sc16is7xx_port_read(port, SC16IS7XX_RHR_REG); bytes_read = 1; } else { - sc16is7xx_fifo_read(port, s->buf, rxlen); + sc16is7xx_fifo_read(port, one->buf, rxlen); bytes_read = rxlen; }
@@ -626,7 +626,7 @@ static void sc16is7xx_handle_rx(struct uart_port *port, unsigned int rxlen, }
for (i = 0; i < bytes_read; ++i) { - ch = s->buf[i]; + ch = one->buf[i]; if (uart_handle_sysrq_char(port, ch)) continue;
@@ -644,10 +644,10 @@ static void sc16is7xx_handle_rx(struct uart_port *port, unsigned int rxlen,
static void sc16is7xx_handle_tx(struct uart_port *port) { - struct sc16is7xx_port *s = dev_get_drvdata(port->dev); - struct circ_buf *xmit = &port->state->xmit; - unsigned int txlen, to_send, i; + struct tty_port *tport = &port->state->port; unsigned long flags; + unsigned int txlen; + unsigned char *tail;
if (unlikely(port->x_char)) { sc16is7xx_port_write(port, SC16IS7XX_THR_REG, port->x_char); @@ -656,40 +656,31 @@ static void sc16is7xx_handle_tx(struct uart_port *port) return; }
- if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { + if (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(port)) { uart_port_lock_irqsave(port, &flags); sc16is7xx_stop_tx(port); uart_port_unlock_irqrestore(port, flags); return; }
- /* Get length of data pending in circular buffer */ - to_send = uart_circ_chars_pending(xmit); - if (likely(to_send)) { - /* Limit to size of TX FIFO */ - txlen = sc16is7xx_port_read(port, SC16IS7XX_TXLVL_REG); - if (txlen > SC16IS7XX_FIFO_SIZE) { - dev_err_ratelimited(port->dev, - "chip reports %d free bytes in TX fifo, but it only has %d", - txlen, SC16IS7XX_FIFO_SIZE); - txlen = 0; - } - to_send = (to_send > txlen) ? txlen : to_send; - - /* Convert to linear buffer */ - for (i = 0; i < to_send; ++i) { - s->buf[i] = xmit->buf[xmit->tail]; - uart_xmit_advance(port, 1); - } - - sc16is7xx_fifo_write(port, s->buf, to_send); + /* Limit to space available in TX FIFO */ + txlen = sc16is7xx_port_read(port, SC16IS7XX_TXLVL_REG); + if (txlen > SC16IS7XX_FIFO_SIZE) { + dev_err_ratelimited(port->dev, + "chip reports %d free bytes in TX fifo, but it only has %d", + txlen, SC16IS7XX_FIFO_SIZE); + txlen = 0; }
+ txlen = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail, txlen); + sc16is7xx_fifo_write(port, tail, txlen); + uart_xmit_advance(port, txlen); + uart_port_lock_irqsave(port, &flags); - if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS) uart_write_wakeup(port);
- if (uart_circ_empty(xmit)) + if (kfifo_is_empty(&tport->xmit_fifo)) sc16is7xx_stop_tx(port); else sc16is7xx_ier_set(port, SC16IS7XX_IER_THRI_BIT);
反馈: 您发送到kernel@openeuler.org的补丁/补丁集,已成功转换为PR! PR链接地址: https://gitee.com/openeuler/kernel/pulls/12545 邮件列表地址:https://mailweb.openeuler.org/hyperkitty/list/kernel@openeuler.org/message/W...
FeedBack: The patch(es) which you have sent to kernel@openeuler.org mailing list has been converted to a pull request successfully! Pull request link: https://gitee.com/openeuler/kernel/pulls/12545 Mailing list address: https://mailweb.openeuler.org/hyperkitty/list/kernel@openeuler.org/message/W...