linux/drivers/tty/tty_port.c
Xin Zhao eb3b0d92c9 tty: tty_port: add workqueue to flip TTY buffer
On the embedded platform, certain critical data, such as IMU data, is
transmitted through UART. The tty_flip_buffer_push() interface in the TTY
layer uses system_dfl_wq to handle the flipping of the TTY buffer.
Although the unbound workqueue can create new threads on demand and wake
up the kworker thread on an idle CPU, it may be preempted by real-time
tasks or other high-prio tasks.

flush_to_ldisc() needs to wake up the relevant data handle thread. When
executing __wake_up_common_lock(), it calls spin_lock_irqsave(), which
does not disable preemption but disables migration in RT-Linux. This
prevents the kworker thread from being migrated to other cores by CPU's
balancing logic, resulting in long delays. The call trace is as follows:
    __wake_up_common_lock
    __wake_up
    ep_poll_callback
    __wake_up_common
    __wake_up_common_lock
    __wake_up
    n_tty_receive_buf_common
    n_tty_receive_buf2
    tty_ldisc_receive_buf
    tty_port_default_receive_buf
    flush_to_ldisc

In our system, the processing interval for each frame of IMU data
transmitted via UART can experience significant jitter due to this issue.
Instead of the expected 10 to 15 ms frame processing interval, we see
spikes up to 30 to 35 ms. Moreover, in just one or two hours, there can
be 2 to 3 occurrences of such high jitter, which is quite frequent. This
jitter exceeds the software's tolerable limit of 20 ms.

Introduce flip_wq in tty_port which can be set by tty_port_link_wq() or as
default linked to default workqueue allocated when tty_register_driver().
The default workqueue is allocated with flag WQ_SYSFS, so that cpumask and
nice can be set dynamically. The execution timing of tty_port_link_wq() is
not clearly restricted. The newly added function tty_port_link_driver_wq()
checks whether the flip_wq of the tty_port has already been assigned when
linking the default tty_driver's workqueue to the port. After the user has
set a custom workqueue for a certain tty_port using tty_port_link_wq(), the
system will only use this custom workqueue, even if tty_driver does not
have %TTY_DRIVER_NO_WORKQUEUE flag. When tty_port register device, flip_wq
link operation is done by tty_port_link_driver_wq(), but for in-memory
devices the link operation cannot cover all the cases. Although
tty_port_install() is dedicated for in-memory devices lik PTY to link port
allocated on demand, the logic of tty_port_install() is so simple that
people may not call it, vc_cons[0].d->port is one such case. We check the
buf.flip_wq when flip TTY buffer, if buf.flip_wq of TTY port is NULL, use
system_dfl_wq as a backup.

To avoid naming conflict of the default tty_driver's workqueue, using
'"%s-%s", driver->name, driver->driver_name' as the workqueue name. In
cases where driver_name is not specified and therefore is NULL, the
workqueue is not created. Drivers that do not define driver_name are
potentially in-memory devices like vty, which generally do not require
special workqueue settings. Even with the combination of name and
driver_name, the workqueue names can still be duplicated, as many tty
serial drivers use "ttyS" as dev_name and "serial" as driver_name. I
modified the conflicting driver_name of these drivers by appending a
suffix of _xx based on the corresponding .c file. If this modification is
not made, it could not only lead to duplicate workqueue names but also
result in duplicate entries for the /proc/tty/driver/<driver_name> nodes.

Introduce %TTY_DRIVER_NO_WORKQUEUE flag meaning not to create the
default single tty_driver workqueue. Two reasons why need to introduce the
%TTY_DRIVER_NO_WORKQUEUE flag:
1. If the WQ_SYSFS parameter is enabled, workqueue_sysfs_register() will
fail when trying to create a workqueue with the same name. The pty is an
example of this; if both CONFIG_LEGACY_PTYS and CONFIG_UNIX98_PTYS are
enabled, the call to tty_register_driver() in unix98_pty_init() will fail.
2. Different TTY ports may be used for different tasks, which may require
separate core binding control via workqueues. In this case, the workqueue
created by default in the TTY driver is unnecessary. Enabling this flag
prevents the creation of this redundant workqueue.

After applying this patch, we can set the related UART TTY flip buffer
workqueue by sysfs. We set the cpumask to CPU cores associated with the
IMU tasks, and set the nice to -20. Testing has shown significant
improvement in the previously described issue, with almost no stuttering
occurring anymore.

Tested-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
Signed-off-by: Xin Zhao <jackzxcui1989@163.com>
Link: https://patch.msgid.link/20260213085039.3274704-1-jackzxcui1989@163.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2026-03-12 15:26:29 +01:00

778 lines
21 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Tty port functions
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/tty.h>
#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/timer.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/sched/signal.h>
#include <linux/wait.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/serdev.h>
#include "tty.h"
static size_t tty_port_default_receive_buf(struct tty_port *port, const u8 *p,
const u8 *f, size_t count)
{
struct tty_struct *tty;
struct tty_ldisc *ld;
tty = READ_ONCE(port->itty);
if (!tty)
return 0;
ld = tty_ldisc_ref(tty);
if (!ld)
return 0;
count = tty_ldisc_receive_buf(ld, p, f, count);
tty_ldisc_deref(ld);
return count;
}
static void tty_port_default_lookahead_buf(struct tty_port *port, const u8 *p,
const u8 *f, size_t count)
{
struct tty_struct *tty;
struct tty_ldisc *ld;
tty = READ_ONCE(port->itty);
if (!tty)
return;
ld = tty_ldisc_ref(tty);
if (!ld)
return;
if (ld->ops->lookahead_buf)
ld->ops->lookahead_buf(ld->tty, p, f, count);
tty_ldisc_deref(ld);
}
static void tty_port_default_wakeup(struct tty_port *port)
{
scoped_guard(tty_port_tty, port)
tty_wakeup(scoped_tty());
}
const struct tty_port_client_operations tty_port_default_client_ops = {
.receive_buf = tty_port_default_receive_buf,
.lookahead_buf = tty_port_default_lookahead_buf,
.write_wakeup = tty_port_default_wakeup,
};
EXPORT_SYMBOL_GPL(tty_port_default_client_ops);
/**
* tty_port_init - initialize tty_port
* @port: tty_port to initialize
*
* Initializes the state of struct tty_port. When a port was initialized using
* this function, one has to destroy the port by tty_port_destroy(). Either
* indirectly by using &tty_port refcounting (tty_port_put()) or directly if
* refcounting is not used.
*/
void tty_port_init(struct tty_port *port)
{
memset(port, 0, sizeof(*port));
tty_buffer_init(port);
init_waitqueue_head(&port->open_wait);
init_waitqueue_head(&port->delta_msr_wait);
mutex_init(&port->mutex);
mutex_init(&port->buf_mutex);
spin_lock_init(&port->lock);
port->close_delay = (50 * HZ) / 100;
port->closing_wait = (3000 * HZ) / 100;
port->client_ops = &tty_port_default_client_ops;
kref_init(&port->kref);
}
EXPORT_SYMBOL(tty_port_init);
/**
* tty_port_link_wq - link tty_port and flip workqueue
* @port: tty_port of the device
* @flip_wq: workqueue to queue flip buffer work on
*
* Whenever %TTY_DRIVER_NO_WORKQUEUE is used, every tty_port can be linked to
* a workqueue manually by this function.
* tty_port will use system_dfl_wq when buf.flip_wq is NULL.
*
* Note that tty_port API will NOT destroy the workqueue.
*/
void tty_port_link_wq(struct tty_port *port, struct workqueue_struct *flip_wq)
{
port->buf.flip_wq = flip_wq;
}
EXPORT_SYMBOL_GPL(tty_port_link_wq);
/**
* tty_port_link_device - link tty and tty_port
* @port: tty_port of the device
* @driver: tty_driver for this device
* @index: index of the tty
*
* Provide the tty layer with a link from a tty (specified by @index) to a
* tty_port (@port). Use this only if neither tty_port_register_device() nor
* tty_port_install() is used in the driver. If used, this has to be called
* before tty_register_driver().
*/
void tty_port_link_device(struct tty_port *port,
struct tty_driver *driver, unsigned index)
{
if (WARN_ON(index >= driver->num))
return;
driver->ports[index] = port;
}
EXPORT_SYMBOL_GPL(tty_port_link_device);
/**
* tty_port_register_device - register tty device
* @port: tty_port of the device
* @driver: tty_driver for this device
* @index: index of the tty
* @device: parent if exists, otherwise NULL
*
* It is the same as tty_register_device() except the provided @port is linked
* to a concrete tty specified by @index. Use this or tty_port_install() (or
* both). Call tty_port_link_device() as a last resort.
*/
struct device *tty_port_register_device(struct tty_port *port,
struct tty_driver *driver, unsigned index,
struct device *device)
{
return tty_port_register_device_attr(port, driver, index, device, NULL, NULL);
}
EXPORT_SYMBOL_GPL(tty_port_register_device);
/**
* tty_port_register_device_attr - register tty device
* @port: tty_port of the device
* @driver: tty_driver for this device
* @index: index of the tty
* @device: parent if exists, otherwise NULL
* @drvdata: Driver data to be set to device.
* @attr_grp: Attribute group to be set on device.
*
* It is the same as tty_register_device_attr() except the provided @port is
* linked to a concrete tty specified by @index. Use this or tty_port_install()
* (or both). Call tty_port_link_device() as a last resort.
*/
struct device *tty_port_register_device_attr(struct tty_port *port,
struct tty_driver *driver, unsigned index,
struct device *device, void *drvdata,
const struct attribute_group **attr_grp)
{
tty_port_link_device(port, driver, index);
tty_port_link_driver_wq(port, driver);
return tty_register_device_attr(driver, index, device, drvdata,
attr_grp);
}
EXPORT_SYMBOL_GPL(tty_port_register_device_attr);
/**
* tty_port_register_device_attr_serdev - register tty or serdev device
* @port: tty_port of the device
* @driver: tty_driver for this device
* @index: index of the tty
* @host: serial port hardware device
* @parent: parent if exists, otherwise NULL
* @drvdata: driver data for the device
* @attr_grp: attribute group for the device
*
* Register a serdev or tty device depending on if the parent device has any
* defined serdev clients or not.
*/
struct device *tty_port_register_device_attr_serdev(struct tty_port *port,
struct tty_driver *driver, unsigned index,
struct device *host, struct device *parent, void *drvdata,
const struct attribute_group **attr_grp)
{
struct device *dev;
tty_port_link_device(port, driver, index);
tty_port_link_driver_wq(port, driver);
dev = serdev_tty_port_register(port, host, parent, driver, index);
if (PTR_ERR(dev) != -ENODEV) {
/* Skip creating cdev if we registered a serdev device */
return dev;
}
return tty_register_device_attr(driver, index, parent, drvdata,
attr_grp);
}
EXPORT_SYMBOL_GPL(tty_port_register_device_attr_serdev);
/**
* tty_port_unregister_device - deregister a tty or serdev device
* @port: tty_port of the device
* @driver: tty_driver for this device
* @index: index of the tty
*
* If a tty or serdev device is registered with a call to
* tty_port_register_device_serdev() then this function must be called when
* the device is gone.
*/
void tty_port_unregister_device(struct tty_port *port,
struct tty_driver *driver, unsigned index)
{
int ret;
WRITE_ONCE(port->buf.flip_wq, NULL);
ret = serdev_tty_port_unregister(port);
if (ret == 0)
return;
tty_unregister_device(driver, index);
}
EXPORT_SYMBOL_GPL(tty_port_unregister_device);
int tty_port_alloc_xmit_buf(struct tty_port *port)
{
/* We may sleep in get_zeroed_page() */
guard(mutex)(&port->buf_mutex);
if (port->xmit_buf)
return 0;
port->xmit_buf = (u8 *)get_zeroed_page(GFP_KERNEL);
if (port->xmit_buf == NULL)
return -ENOMEM;
kfifo_init(&port->xmit_fifo, port->xmit_buf, PAGE_SIZE);
return 0;
}
EXPORT_SYMBOL(tty_port_alloc_xmit_buf);
void tty_port_free_xmit_buf(struct tty_port *port)
{
guard(mutex)(&port->buf_mutex);
free_page((unsigned long)port->xmit_buf);
port->xmit_buf = NULL;
INIT_KFIFO(port->xmit_fifo);
}
EXPORT_SYMBOL(tty_port_free_xmit_buf);
/**
* tty_port_destroy - destroy inited port
* @port: tty port to be destroyed
*
* When a port was initialized using tty_port_init(), one has to destroy the
* port by this function. Either indirectly by using &tty_port refcounting
* (tty_port_put()) or directly if refcounting is not used.
*/
void tty_port_destroy(struct tty_port *port)
{
tty_buffer_cancel_work(port);
tty_buffer_free_all(port);
WRITE_ONCE(port->buf.flip_wq, NULL);
}
EXPORT_SYMBOL(tty_port_destroy);
static void tty_port_destructor(struct kref *kref)
{
struct tty_port *port = container_of(kref, struct tty_port, kref);
/* check if last port ref was dropped before tty release */
if (WARN_ON(port->itty))
return;
free_page((unsigned long)port->xmit_buf);
tty_port_destroy(port);
if (port->ops && port->ops->destruct)
port->ops->destruct(port);
else
kfree(port);
}
/**
* tty_port_put - drop a reference to tty_port
* @port: port to drop a reference of (can be NULL)
*
* The final put will destroy and free up the @port using
* @port->ops->destruct() hook, or using kfree() if not provided.
*/
void tty_port_put(struct tty_port *port)
{
if (port)
kref_put(&port->kref, tty_port_destructor);
}
EXPORT_SYMBOL(tty_port_put);
/**
* tty_port_tty_get - get a tty reference
* @port: tty port
*
* Return a refcount protected tty instance or %NULL if the port is not
* associated with a tty (eg due to close or hangup).
*/
struct tty_struct *tty_port_tty_get(struct tty_port *port)
{
guard(spinlock_irqsave)(&port->lock);
return tty_kref_get(port->tty);
}
EXPORT_SYMBOL(tty_port_tty_get);
/**
* tty_port_tty_set - set the tty of a port
* @port: tty port
* @tty: the tty
*
* Associate the port and tty pair. Manages any internal refcounts. Pass %NULL
* to deassociate a port.
*/
void tty_port_tty_set(struct tty_port *port, struct tty_struct *tty)
{
guard(spinlock_irqsave)(&port->lock);
tty_kref_put(port->tty);
port->tty = tty_kref_get(tty);
}
EXPORT_SYMBOL(tty_port_tty_set);
/**
* tty_port_shutdown - internal helper to shutdown the device
* @port: tty port to be shut down
* @tty: the associated tty
*
* It is used by tty_port_hangup() and tty_port_close(). Its task is to
* shutdown the device if it was initialized (note consoles remain
* functioning). It lowers DTR/RTS (if @tty has HUPCL set) and invokes
* @port->ops->shutdown().
*/
static void tty_port_shutdown(struct tty_port *port, struct tty_struct *tty)
{
guard(mutex)(&port->mutex);
if (port->console)
return;
if (!tty_port_initialized(port))
return;
tty_port_set_initialized(port, false);
/*
* Drop DTR/RTS if HUPCL is set. This causes any attached
* modem to hang up the line.
*/
if (tty && C_HUPCL(tty))
tty_port_lower_dtr_rts(port);
if (port->ops->shutdown)
port->ops->shutdown(port);
}
/**
* tty_port_hangup - hangup helper
* @port: tty port
*
* Perform port level tty hangup flag and count changes. Drop the tty
* reference.
*
* Caller holds tty lock.
*/
void tty_port_hangup(struct tty_port *port)
{
struct tty_struct *tty;
scoped_guard(spinlock_irqsave, &port->lock) {
port->count = 0;
tty = port->tty;
if (tty)
set_bit(TTY_IO_ERROR, &tty->flags);
port->tty = NULL;
}
tty_port_set_active(port, false);
tty_port_shutdown(port, tty);
tty_kref_put(tty);
wake_up_interruptible(&port->open_wait);
wake_up_interruptible(&port->delta_msr_wait);
}
EXPORT_SYMBOL(tty_port_hangup);
void __tty_port_tty_hangup(struct tty_port *port, bool check_clocal, bool async)
{
scoped_guard(tty_port_tty, port) {
struct tty_struct *tty = scoped_tty();
if (!check_clocal || !C_CLOCAL(tty)) {
if (async)
tty_hangup(tty);
else
tty_vhangup(tty);
}
}
}
EXPORT_SYMBOL_GPL(__tty_port_tty_hangup);
/**
* tty_port_tty_wakeup - helper to wake up a tty
* @port: tty port
*/
void tty_port_tty_wakeup(struct tty_port *port)
{
port->client_ops->write_wakeup(port);
}
EXPORT_SYMBOL_GPL(tty_port_tty_wakeup);
/**
* tty_port_carrier_raised - carrier raised check
* @port: tty port
*
* Wrapper for the carrier detect logic. For the moment this is used
* to hide some internal details. This will eventually become entirely
* internal to the tty port.
*/
bool tty_port_carrier_raised(struct tty_port *port)
{
if (port->ops->carrier_raised == NULL)
return true;
return port->ops->carrier_raised(port);
}
EXPORT_SYMBOL(tty_port_carrier_raised);
/**
* tty_port_raise_dtr_rts - Raise DTR/RTS
* @port: tty port
*
* Wrapper for the DTR/RTS raise logic. For the moment this is used to hide
* some internal details. This will eventually become entirely internal to the
* tty port.
*/
void tty_port_raise_dtr_rts(struct tty_port *port)
{
if (port->ops->dtr_rts)
port->ops->dtr_rts(port, true);
}
EXPORT_SYMBOL(tty_port_raise_dtr_rts);
/**
* tty_port_lower_dtr_rts - Lower DTR/RTS
* @port: tty port
*
* Wrapper for the DTR/RTS raise logic. For the moment this is used to hide
* some internal details. This will eventually become entirely internal to the
* tty port.
*/
void tty_port_lower_dtr_rts(struct tty_port *port)
{
if (port->ops->dtr_rts)
port->ops->dtr_rts(port, false);
}
EXPORT_SYMBOL(tty_port_lower_dtr_rts);
/**
* tty_port_block_til_ready - Waiting logic for tty open
* @port: the tty port being opened
* @tty: the tty device being bound
* @filp: the file pointer of the opener or %NULL
*
* Implement the core POSIX/SuS tty behaviour when opening a tty device.
* Handles:
*
* - hangup (both before and during)
* - non blocking open
* - rts/dtr/dcd
* - signals
* - port flags and counts
*
* The passed @port must implement the @port->ops->carrier_raised method if it
* can do carrier detect and the @port->ops->dtr_rts method if it supports
* software management of these lines. Note that the dtr/rts raise is done each
* iteration as a hangup may have previously dropped them while we wait.
*
* Caller holds tty lock.
*
* Note: May drop and reacquire tty lock when blocking, so @tty and @port may
* have changed state (eg., may have been hung up).
*/
int tty_port_block_til_ready(struct tty_port *port,
struct tty_struct *tty, struct file *filp)
{
int do_clocal = 0, retval;
DEFINE_WAIT(wait);
/* if non-blocking mode is set we can pass directly to open unless
* the port has just hung up or is in another error state.
*/
if (tty_io_error(tty)) {
tty_port_set_active(port, true);
return 0;
}
if (filp == NULL || (filp->f_flags & O_NONBLOCK)) {
/* Indicate we are open */
if (C_BAUD(tty))
tty_port_raise_dtr_rts(port);
tty_port_set_active(port, true);
return 0;
}
if (C_CLOCAL(tty))
do_clocal = 1;
/* Block waiting until we can proceed. We may need to wait for the
* carrier, but we must also wait for any close that is in progress
* before the next open may complete.
*/
retval = 0;
/* The port lock protects the port counts */
scoped_guard(spinlock_irqsave, &port->lock) {
port->count--;
port->blocked_open++;
}
while (1) {
/* Indicate we are open */
if (C_BAUD(tty) && tty_port_initialized(port))
tty_port_raise_dtr_rts(port);
prepare_to_wait(&port->open_wait, &wait, TASK_INTERRUPTIBLE);
/* Check for a hangup or uninitialised port.
* Return accordingly.
*/
if (tty_hung_up_p(filp) || !tty_port_initialized(port)) {
if (port->flags & ASYNC_HUP_NOTIFY)
retval = -EAGAIN;
else
retval = -ERESTARTSYS;
break;
}
/*
* Probe the carrier. For devices with no carrier detect
* tty_port_carrier_raised will always return true.
* Never ask drivers if CLOCAL is set, this causes troubles
* on some hardware.
*/
if (do_clocal || tty_port_carrier_raised(port))
break;
if (signal_pending(current)) {
retval = -ERESTARTSYS;
break;
}
tty_unlock(tty);
schedule();
tty_lock(tty);
}
finish_wait(&port->open_wait, &wait);
/* Update counts. A parallel hangup will have set count to zero and
* we must not mess that up further.
*/
scoped_guard(spinlock_irqsave, &port->lock) {
if (!tty_hung_up_p(filp))
port->count++;
port->blocked_open--;
}
if (retval == 0)
tty_port_set_active(port, true);
return retval;
}
EXPORT_SYMBOL(tty_port_block_til_ready);
static void tty_port_drain_delay(struct tty_port *port, struct tty_struct *tty)
{
unsigned int bps = tty_get_baud_rate(tty);
long timeout;
if (bps > 1200) {
timeout = (HZ * 10 * port->drain_delay) / bps;
timeout = max_t(long, timeout, HZ / 10);
} else {
timeout = 2 * HZ;
}
schedule_timeout_interruptible(timeout);
}
/**
* tty_port_close_start - helper for tty->ops->close, part 1/2
* @port: tty_port of the device
* @tty: tty being closed
* @filp: passed file pointer
*
* Decrements and checks open count. Flushes the port if this is the last
* close. That means, dropping the data from the outpu buffer on the device and
* waiting for sending logic to finish. The rest of close handling is performed
* in tty_port_close_end().
*
* Locking: Caller holds tty lock.
*
* Return: 1 if this is the last close, otherwise 0
*/
int tty_port_close_start(struct tty_port *port,
struct tty_struct *tty, struct file *filp)
{
if (tty_hung_up_p(filp))
return 0;
scoped_guard(spinlock_irqsave, &port->lock) {
if (tty->count == 1 && port->count != 1) {
tty_warn(tty, "%s: tty->count = 1 port count = %d\n", __func__,
port->count);
port->count = 1;
}
if (--port->count < 0) {
tty_warn(tty, "%s: bad port count (%d)\n", __func__,
port->count);
port->count = 0;
}
if (port->count)
return 0;
}
tty->closing = 1;
if (tty_port_initialized(port)) {
/* Don't block on a stalled port, just pull the chain */
if (tty->flow.tco_stopped)
tty_driver_flush_buffer(tty);
if (port->closing_wait != ASYNC_CLOSING_WAIT_NONE)
tty_wait_until_sent(tty, port->closing_wait);
if (port->drain_delay)
tty_port_drain_delay(port, tty);
}
/* Flush the ldisc buffering */
tty_ldisc_flush(tty);
/* Report to caller this is the last port reference */
return 1;
}
EXPORT_SYMBOL(tty_port_close_start);
/**
* tty_port_close_end - helper for tty->ops->close, part 2/2
* @port: tty_port of the device
* @tty: tty being closed
*
* This is a continuation of the first part: tty_port_close_start(). This
* should be called after turning off the device. It flushes the data from the
* line discipline and delays the close by @port->close_delay.
*
* Locking: Caller holds tty lock.
*/
void tty_port_close_end(struct tty_port *port, struct tty_struct *tty)
{
unsigned long flags;
tty_ldisc_flush(tty);
tty->closing = 0;
spin_lock_irqsave(&port->lock, flags);
if (port->blocked_open) {
spin_unlock_irqrestore(&port->lock, flags);
if (port->close_delay)
msleep_interruptible(jiffies_to_msecs(port->close_delay));
spin_lock_irqsave(&port->lock, flags);
wake_up_interruptible(&port->open_wait);
}
spin_unlock_irqrestore(&port->lock, flags);
tty_port_set_active(port, false);
}
EXPORT_SYMBOL(tty_port_close_end);
/**
* tty_port_close - generic tty->ops->close handler
* @port: tty_port of the device
* @tty: tty being closed
* @filp: passed file pointer
*
* It is a generic helper to be used in driver's @tty->ops->close. It wraps a
* sequence of tty_port_close_start(), tty_port_shutdown(), and
* tty_port_close_end(). The latter two are called only if this is the last
* close. See the respective functions for the details.
*
* Locking: Caller holds tty lock
*/
void tty_port_close(struct tty_port *port, struct tty_struct *tty,
struct file *filp)
{
if (tty_port_close_start(port, tty, filp) == 0)
return;
tty_port_shutdown(port, tty);
if (!port->console)
set_bit(TTY_IO_ERROR, &tty->flags);
tty_port_close_end(port, tty);
tty_port_tty_set(port, NULL);
}
EXPORT_SYMBOL(tty_port_close);
/**
* tty_port_install - generic tty->ops->install handler
* @port: tty_port of the device
* @driver: tty_driver for this device
* @tty: tty to be installed
*
* It is the same as tty_standard_install() except the provided @port is linked
* to a concrete tty specified by @tty. Use this or tty_port_register_device()
* (or both). Call tty_port_link_device() as a last resort.
*/
int tty_port_install(struct tty_port *port, struct tty_driver *driver,
struct tty_struct *tty)
{
tty->port = port;
tty_port_link_driver_wq(port, driver);
return tty_standard_install(driver, tty);
}
EXPORT_SYMBOL_GPL(tty_port_install);
/**
* tty_port_open - generic tty->ops->open handler
* @port: tty_port of the device
* @tty: tty to be opened
* @filp: passed file pointer
*
* It is a generic helper to be used in driver's @tty->ops->open. It activates
* the devices using @port->ops->activate if not active already. And waits for
* the device to be ready using tty_port_block_til_ready() (e.g. raises
* DTR/CTS and waits for carrier).
*
* Note that @port->ops->shutdown is not called when @port->ops->activate
* returns an error (on the contrary, @tty->ops->close is).
*
* Locking: Caller holds tty lock.
*
* Note: may drop and reacquire tty lock (in tty_port_block_til_ready()) so
* @tty and @port may have changed state (eg., may be hung up now).
*/
int tty_port_open(struct tty_port *port, struct tty_struct *tty,
struct file *filp)
{
scoped_guard(spinlock_irq, &port->lock)
++port->count;
tty_port_tty_set(port, tty);
/*
* Do the device-specific open only if the hardware isn't
* already initialized. Serialize open and shutdown using the
* port mutex.
*/
scoped_guard(mutex, &port->mutex) {
if (tty_port_initialized(port))
break;
clear_bit(TTY_IO_ERROR, &tty->flags);
if (port->ops->activate) {
int retval = port->ops->activate(port, tty);
if (retval)
return retval;
}
tty_port_set_initialized(port, true);
}
return tty_port_block_til_ready(port, tty, filp);
}
EXPORT_SYMBOL(tty_port_open);