linux-stable/drivers/tty/serial/serial_port.c

274 lines
7.1 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0+
/*
* Serial core port device driver
*
* Copyright (C) 2023 Texas Instruments Incorporated - https://www.ti.com/
* Author: Tony Lindgren <tony@atomide.com>
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/property.h>
#include <linux/serial_core.h>
#include <linux/spinlock.h>
#include "serial_base.h"
#define SERIAL_PORT_AUTOSUSPEND_DELAY_MS 500
/* Only considers pending TX for now. Caller must take care of locking */
static int __serial_port_busy(struct uart_port *port)
{
return !uart_tx_stopped(port) &&
tty: serial: switch from circ_buf to kfifo Switch from struct circ_buf to proper kfifo. kfifo provides much better API, esp. when wrap-around of the buffer needs to be taken into account. Look at pl011_dma_tx_refill() or cpm_uart_tx_pump() changes for example. Kfifo API can also fill in scatter-gather DMA structures, so it easier for that use case too. Look at lpuart_dma_tx() for example. Note that not all drivers can be converted to that (like atmel_serial), they handle DMA specially. Note that usb-serial uses kfifo for TX for ages. omap needed a bit more care as it needs to put a char into FIFO to start the DMA transfer when OMAP_DMA_TX_KICK is set. In that case, we have to do kfifo_dma_out_prepare twice: once to find out the tx_size (to find out if it is worths to do DMA at all -- size >= 4), the second time for the actual transfer. All traces of circ_buf are removed from serial_core.h (and its struct uart_state). Signed-off-by: Jiri Slaby (SUSE) <jirislaby@kernel.org> Cc: Al Cooper <alcooperx@gmail.com> Cc: Matthias Brugger <matthias.bgg@gmail.com> Cc: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com> Cc: Kumaravel Thiagarajan <kumaravel.thiagarajan@microchip.com> Cc: Tharun Kumar P <tharunkumar.pasumarthi@microchip.com> Cc: Russell King <linux@armlinux.org.uk> Cc: Vineet Gupta <vgupta@kernel.org> Cc: Richard Genoud <richard.genoud@gmail.com> Cc: Nicolas Ferre <nicolas.ferre@microchip.com> Cc: Alexandre Belloni <alexandre.belloni@bootlin.com> Cc: Claudiu Beznea <claudiu.beznea@tuxon.dev> Cc: Alexander Shiyan <shc_work@mail.ru> Cc: Baruch Siach <baruch@tkos.co.il> Cc: Maciej W. Rozycki <macro@orcam.me.uk> Cc: Shawn Guo <shawnguo@kernel.org> Cc: Sascha Hauer <s.hauer@pengutronix.de> Cc: Fabio Estevam <festevam@gmail.com> Cc: Neil Armstrong <neil.armstrong@linaro.org> Cc: Kevin Hilman <khilman@baylibre.com> Cc: Jerome Brunet <jbrunet@baylibre.com> Cc: Martin Blumenstingl <martin.blumenstingl@googlemail.com> Cc: Taichi Sugaya <sugaya.taichi@socionext.com> Cc: Takao Orito <orito.takao@socionext.com> Cc: Bjorn Andersson <andersson@kernel.org> Cc: Konrad Dybcio <konrad.dybcio@linaro.org> Cc: Pali Rohár <pali@kernel.org> Cc: Michael Ellerman <mpe@ellerman.id.au> Cc: Nicholas Piggin <npiggin@gmail.com> Cc: Christophe Leroy <christophe.leroy@csgroup.eu> Cc: Aneesh Kumar K.V <aneesh.kumar@kernel.org> Cc: Naveen N. Rao <naveen.n.rao@linux.ibm.com> Cc: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> Cc: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org> Cc: Alim Akhtar <alim.akhtar@samsung.com> Cc: Laxman Dewangan <ldewangan@nvidia.com> Cc: Thierry Reding <thierry.reding@gmail.com> Cc: Jonathan Hunter <jonathanh@nvidia.com> Cc: Orson Zhai <orsonzhai@gmail.com> Cc: Baolin Wang <baolin.wang@linux.alibaba.com> Cc: Chunyan Zhang <zhang.lyra@gmail.com> Cc: Patrice Chotard <patrice.chotard@foss.st.com> Cc: Maxime Coquelin <mcoquelin.stm32@gmail.com> Cc: Alexandre Torgue <alexandre.torgue@foss.st.com> Cc: David S. Miller <davem@davemloft.net> Cc: Hammer Hsieh <hammerh0314@gmail.com> Cc: Peter Korsgaard <jacmet@sunsite.dk> Cc: Timur Tabi <timur@kernel.org> Cc: Michal Simek <michal.simek@amd.com> Cc: Sumit Semwal <sumit.semwal@linaro.org> Cc: Christian König <christian.koenig@amd.com> Link: https://lore.kernel.org/r/20240405060826.2521-13-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2024-04-05 06:08:23 +00:00
!kfifo_is_empty(&port->state->port.xmit_fifo);
}
static int serial_port_runtime_resume(struct device *dev)
{
struct serial_port_device *port_dev = to_serial_base_port_device(dev);
struct uart_port *port;
unsigned long flags;
port = port_dev->port;
if (port->flags & UPF_DEAD)
goto out;
/* Flush any pending TX for the port */
uart_port_lock_irqsave(port, &flags);
if (__serial_port_busy(port))
port->ops->start_tx(port);
uart_port_unlock_irqrestore(port, flags);
out:
pm_runtime_mark_last_busy(dev);
return 0;
}
serial: port: Don't suspend if the port is still busy We accidently met the issue that the bash prompt is not shown after the previous command done and until the next input if there's only one CPU (In our issue other CPUs are isolated by isolcpus=). Further analysis shows it's because the port entering runtime suspend even if there's still pending chars in the buffer and the pending chars will only be processed in next device resuming. We are using amba-pl011 and the problematic flow is like below: Bash                                         kworker tty_write()   file_tty_write()     n_tty_write()       uart_write()         __uart_start()           pm_runtime_get() // wakeup waker             queue_work()                                     pm_runtime_work()                                                rpm_resume()                                                 status = RPM_RESUMING                                                 serial_port_runtime_resume()                                                   port->ops->start_tx()                                                     pl011_tx_chars()                                                       uart_write_wakeup()         […]         __uart_start()           pm_runtime_get() < 0 // because runtime status = RPM_RESUMING                                // later data are not commit to the port driver                                                 status = RPM_ACTIVE                                                 rpm_idle() -> rpm_suspend() This patch tries to fix this by checking the port busy before entering runtime suspending. A runtime_suspend callback is added for the port driver. When entering runtime suspend the callback is invoked, if there's still pending chars in the buffer then flush the buffer. Fixes: 84a9582fd203 ("serial: core: Start managing serial controllers to enable runtime PM") Cc: stable <stable@kernel.org> Reviewed-by: Tony Lindgren <tony@atomide.com> Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Signed-off-by: Yicong Yang <yangyicong@hisilicon.com> Link: https://lore.kernel.org/r/20240226152351.40924-1-yangyicong@huawei.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2024-02-26 15:23:51 +00:00
static int serial_port_runtime_suspend(struct device *dev)
{
struct serial_port_device *port_dev = to_serial_base_port_device(dev);
struct uart_port *port = port_dev->port;
unsigned long flags;
bool busy;
if (port->flags & UPF_DEAD)
return 0;
uart_port_lock_irqsave(port, &flags);
busy = __serial_port_busy(port);
if (busy)
port->ops->start_tx(port);
uart_port_unlock_irqrestore(port, flags);
if (busy)
pm_runtime_mark_last_busy(dev);
return busy ? -EBUSY : 0;
}
static DEFINE_RUNTIME_DEV_PM_OPS(serial_port_pm,
serial: port: Don't suspend if the port is still busy We accidently met the issue that the bash prompt is not shown after the previous command done and until the next input if there's only one CPU (In our issue other CPUs are isolated by isolcpus=). Further analysis shows it's because the port entering runtime suspend even if there's still pending chars in the buffer and the pending chars will only be processed in next device resuming. We are using amba-pl011 and the problematic flow is like below: Bash                                         kworker tty_write()   file_tty_write()     n_tty_write()       uart_write()         __uart_start()           pm_runtime_get() // wakeup waker             queue_work()                                     pm_runtime_work()                                                rpm_resume()                                                 status = RPM_RESUMING                                                 serial_port_runtime_resume()                                                   port->ops->start_tx()                                                     pl011_tx_chars()                                                       uart_write_wakeup()         […]         __uart_start()           pm_runtime_get() < 0 // because runtime status = RPM_RESUMING                                // later data are not commit to the port driver                                                 status = RPM_ACTIVE                                                 rpm_idle() -> rpm_suspend() This patch tries to fix this by checking the port busy before entering runtime suspending. A runtime_suspend callback is added for the port driver. When entering runtime suspend the callback is invoked, if there's still pending chars in the buffer then flush the buffer. Fixes: 84a9582fd203 ("serial: core: Start managing serial controllers to enable runtime PM") Cc: stable <stable@kernel.org> Reviewed-by: Tony Lindgren <tony@atomide.com> Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Signed-off-by: Yicong Yang <yangyicong@hisilicon.com> Link: https://lore.kernel.org/r/20240226152351.40924-1-yangyicong@huawei.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2024-02-26 15:23:51 +00:00
serial_port_runtime_suspend,
serial_port_runtime_resume, NULL);
static int serial_port_probe(struct device *dev)
{
pm_runtime_enable(dev);
pm_runtime_set_autosuspend_delay(dev, SERIAL_PORT_AUTOSUSPEND_DELAY_MS);
pm_runtime_use_autosuspend(dev);
return 0;
}
static int serial_port_remove(struct device *dev)
{
pm_runtime_dont_use_autosuspend(dev);
pm_runtime_disable(dev);
return 0;
}
/*
* Serial core port device init functions. Note that the physical serial
* port device driver may not have completed probe at this point.
*/
int uart_add_one_port(struct uart_driver *drv, struct uart_port *port)
{
return serial_ctrl_register_port(drv, port);
}
EXPORT_SYMBOL(uart_add_one_port);
void uart_remove_one_port(struct uart_driver *drv, struct uart_port *port)
{
serial_ctrl_unregister_port(drv, port);
}
EXPORT_SYMBOL(uart_remove_one_port);
/**
* __uart_read_properties - read firmware properties of the given UART port
* @port: corresponding port
* @use_defaults: apply defaults (when %true) or validate the values (when %false)
*
* The following device properties are supported:
* - clock-frequency (optional)
* - fifo-size (optional)
* - no-loopback-test (optional)
* - reg-shift (defaults may apply)
* - reg-offset (value may be validated)
* - reg-io-width (defaults may apply or value may be validated)
* - interrupts (OF only)
* - serial [alias ID] (OF only)
*
* If the port->dev is of struct platform_device type the interrupt line
* will be retrieved via platform_get_irq() call against that device.
* Otherwise it will be assigned by fwnode_irq_get() call. In both cases
* the index 0 of the resource is used.
*
* The caller is responsible to initialize the following fields of the @port
* ->dev (must be valid)
* ->flags
* ->mapbase
* ->mapsize
* ->regshift (if @use_defaults is false)
* before calling this function. Alternatively the above mentioned fields
* may be zeroed, in such case the only ones, that have associated properties
* found, will be set to the respective values.
*
* If no error happened, the ->irq, ->mapbase, ->mapsize will be altered.
* The ->iotype is always altered.
*
* When @use_defaults is true and the respective property is not found
* the following values will be applied:
* ->regshift = 0
* In this case IRQ must be provided, otherwise an error will be returned.
*
* When @use_defaults is false and the respective property is found
* the following values will be validated:
* - reg-io-width (->iotype)
* - reg-offset (->mapsize against ->mapbase)
*
* Returns: 0 on success or negative errno on failure
*/
static int __uart_read_properties(struct uart_port *port, bool use_defaults)
{
struct device *dev = port->dev;
u32 value;
int ret;
/* Read optional UART functional clock frequency */
device_property_read_u32(dev, "clock-frequency", &port->uartclk);
/* Read the registers alignment (default: 8-bit) */
ret = device_property_read_u32(dev, "reg-shift", &value);
if (ret)
port->regshift = use_defaults ? 0 : port->regshift;
else
port->regshift = value;
/* Read the registers I/O access type (default: MMIO 8-bit) */
ret = device_property_read_u32(dev, "reg-io-width", &value);
if (ret) {
port->iotype = UPIO_MEM;
} else {
switch (value) {
case 1:
port->iotype = UPIO_MEM;
break;
case 2:
port->iotype = UPIO_MEM16;
break;
case 4:
port->iotype = device_is_big_endian(dev) ? UPIO_MEM32BE : UPIO_MEM32;
break;
default:
if (!use_defaults) {
dev_err(dev, "Unsupported reg-io-width (%u)\n", value);
return -EINVAL;
}
port->iotype = UPIO_UNKNOWN;
break;
}
}
/* Read the address mapping base offset (default: no offset) */
ret = device_property_read_u32(dev, "reg-offset", &value);
if (ret)
value = 0;
/* Check for shifted address mapping overflow */
if (!use_defaults && port->mapsize < value) {
dev_err(dev, "reg-offset %u exceeds region size %pa\n", value, &port->mapsize);
return -EINVAL;
}
port->mapbase += value;
port->mapsize -= value;
/* Read optional FIFO size */
device_property_read_u32(dev, "fifo-size", &port->fifosize);
if (device_property_read_bool(dev, "no-loopback-test"))
port->flags |= UPF_SKIP_TEST;
/* Get index of serial line, if found in DT aliases */
ret = of_alias_get_id(dev_of_node(dev), "serial");
if (ret >= 0)
port->line = ret;
if (dev_is_platform(dev))
ret = platform_get_irq(to_platform_device(dev), 0);
else
ret = fwnode_irq_get(dev_fwnode(dev), 0);
if (ret == -EPROBE_DEFER)
return ret;
if (ret > 0)
port->irq = ret;
else if (use_defaults)
/* By default IRQ support is mandatory */
return ret;
else
port->irq = 0;
port->flags |= UPF_SHARE_IRQ;
return 0;
}
int uart_read_port_properties(struct uart_port *port)
{
return __uart_read_properties(port, true);
}
EXPORT_SYMBOL_GPL(uart_read_port_properties);
int uart_read_and_validate_port_properties(struct uart_port *port)
{
return __uart_read_properties(port, false);
}
EXPORT_SYMBOL_GPL(uart_read_and_validate_port_properties);
static struct device_driver serial_port_driver = {
.name = "port",
.suppress_bind_attrs = true,
.probe = serial_port_probe,
.remove = serial_port_remove,
.pm = pm_ptr(&serial_port_pm),
};
int serial_base_port_init(void)
{
return serial_base_driver_register(&serial_port_driver);
}
void serial_base_port_exit(void)
{
serial_base_driver_unregister(&serial_port_driver);
}
MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
MODULE_DESCRIPTION("Serial controller port driver");
MODULE_LICENSE("GPL");