summaryrefslogtreecommitdiffstats
path: root/drivers/tty/tty_ldisc.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-09-13 12:27:00 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-09-13 12:27:00 -0700
commit22098763a10d9c1340827fcf6edab66f153b27f0 (patch)
tree3962f95a34df034652ff93686cdd156c7197f2d9 /drivers/tty/tty_ldisc.c
downloadlinux-stable-22098763a10d9c1340827fcf6edab66f153b27f0.tar.gz
linux-stable-22098763a10d9c1340827fcf6edab66f153b27f0.zip
Merge tag 'trace-v7.3-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-tracegrafted
Pull tracing fixes from Steven Rostedt: - Don't destroy user event fields when removal fails User event fields are destroyed before the event is removed from visibility. But that can fail leaving the still visible event with no fields. Move the destroying of the fields to after the event is successfully removed from visibility. - Initialize function graph state is fork before calling copy_exec_state() For non-CLONE_VM forks, copy_exec_state() allocates a new task_exec_state. If that allocation fails, ftrace_graph_exit_task() will free the tasks ret_stack pointer. Since that pointer is still using the parent's ret_stack, it mistakenly frees the parent's pointer too. Call ftrace_graph_init() on the task first which will NULL out the new tasks's ret_stack and if the copy fails, it will not free anything. - Remove FGRAPH_MAX_INDEX The macro FGRAPH_MAX_INDEX was added but never used. Remove it. - Save ent_size in function graph printing of nested functions The function graph tracer needs to look at the next event to see if the next event is the return of the current function entry. If it is, it prints a single line: ktime_get(); Otherwise it prints it like a nested function: tick_nohz_irq_exit() { ktime_get(); kcpustat_irq_exit(); } In order to look at the next event, it must save the current event so that it has the information to print from it. It saves the event in the iterator descriptor called "ent". What it doesn't save is the ent_size of the event which is now used to know if the function graph arguments are to be printed. The peek doesn't save the size so the size used happens to be that of the size of the last event that was seen. Save the entry event size in the iterator descriptor so that the correct size is used. - Fix several errors with freeing data in the histogram code The histogram code had a lot of leaked or or incorrect accounting when failures happen. Correct them. - Fix histogram regression of .percent and .graph modifiers Up until 6.3 histogram values could have "percent" or "graph" modifiers that changed how they were printed. But a change that added restricting histograms values from being strings, stack traces and other modifiers inadvertently prevented them from using the percent and graph modifiers, which were legal use cases for values. Put back the percent and graph modifiers. - Fix various typos in the comments - Set the trace_clock before initializing a histogram with clock argument The histogram API allows the user to specific which trace clock to use via a "clock=" string. The histogram is set up first before the clock is checked. If the passed in clock is not valid, it exits without fully fixing up the histogram leaving it on the list and a use-after-free can trigger. Update the clock argument first and if it fails then exit gracefully before the histogram trigger is placed on any lists. - Restore :mod: trailer after parsing in ftrace_set_clr_event The function ftrace_set_clr_event() modifies the parse string and needs to put it back to what was passed in. It searches for ":mod:" via a strsep() but fails to put back the first ':' in the string. Add back the ':' in the passed in string. - Take trace_array reference when opening a tracer options file The options files are dynamically created and some tracers add their own options. When a tracer adds their own list of options, the trace_array holding them has an array to hold the list of options for each tracer. This array increases in size via a krealloc(), and the new entry gets a newly allocated array to hold the options of the new tracer being added. The element in each entry of the tracer's option array holds a pointer back to the trace_array, a pointer to the tracer it is associated to, a pointer to the flags of the option. The issue is that these arrays are freed when the trace_array is freed when its instance it represents is removed from the instances directory. There's a race that an open of one of these options files can happen when the instance is being removed. Add a new helper function to be called by the open function of the options file to iterate all existing trace_arrays under a lock and find the one that has the given option element in one of it's tracer arrays. If found, then update the associated trace_array's reference counter to keep it from being freed. If not found, have the open call return -ENODEV. - Disable interrupts when acquiring the lock in rb_wake_up_waiters() The function rb_wake_up_waiters() assumes it will be called in interrupt context and does not disable irqs when taking cpu_buffer->reader_lock, which can be called in hard interrupt context. The issue is in PREEMPT_RT, this function is called in thread context leaving this lock open to a deadlock. Take the lock with interrupts disabled. - Use rcu_assign_pointer() for tmp_ops filter hash The tmp_ops used in update_ftrace_direct_mod() assigns its filter_hash field directly, but that field is annotated as __rcu and sparse complains. Assign it with rcu_assign_pointer() - Fix use-after-free in enable_trigger_private_data_free() The trace_event_call is accessed through the event_trigger_data's trace_event_file pointer to put the trace_event_call on freeing. The issue is that the trace_event_file data may have been freed already causing a use-after-free. Add a field to the event_trigger_data that points directly to the trace_event_call so that it can decrement its reference directly without needing to go through the trace_event_file. - Fix accounting of buffer data remote headers trace_buffer_desc_size() and trace_remote_alloc_buffer() undercount the number of pages is needed for the asked for size as it doesn't take into account the meta data on each page. Add a helper function to do the calculation properly and use that in these functions. - Catch nr_page_va overflow in ring_buffer_desc sizing The number of pages per remote ring buffer is capped by ring_buffer_desc::nr_page_va (32 bits). A buffer_size large enough to overflow that field would silently allocate a descriptor smaller than what was asked for. - Do not resize the subbuf order if any per_cpu buffer is disabled The mmapping of ring buffers disables resizing the subbuffers, but it is done per-cpu whereas the subbuf size change is done for all the per_cpu buffers under the buffer->mutex. It could change the size of some while the mapping is happening on others. Have the resize of the subbuf order check all the per_cpu buffers under the lock to see if any of them is disabled before starting and causing an inconsistency between buffers that are being mapped. * tag 'trace-v7.3-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: (25 commits) ring-buffer: Check resize_disabled before publishing the new subbuf order tracing/remotes: Catch nr_page_va overflow in ring_buffer_desc sizing tracing/remotes: Account for ring buffer page header in size calculation tracing: Don't dereference trace_event_file in deferred trigger free ftrace: Use rcu_assign_pointer() for tmp_ops filter hash ring-buffer: Acquire the lock with irqsave in rb_wake_up_waiters() tracing: Take trace_array reference when opening a tracer options file tracing: Fix ring_buffer_read_page_size() kernel-doc tracing: Restore :mod: trailer after parsing in ftrace_set_clr_event() tracing: Fix memory corruption from a "STACKTRACE" histogram key tracing: Fix memory corruption from the histogram stacktrace modifier tracing: Undo the registration when enabling the histogram trigger fails tracing: Take the reference before publishing the named histogram trigger tracing: Set the trace clock before registering the histogram trigger tracing: Fix typo "preceeded" in comment tracing: Fix typo "availabe" in comment tracing: Let histogram values keep the percent and graph modifiers tracing: Keep the entry count when the histogram stats allocation fails tracing: Free histogram the field rejected for a bad modifier tracing: Free histogram the var ref when its initialization fails ...
Diffstat (limited to 'drivers/tty/tty_ldisc.c')
-rw-r--r--drivers/tty/tty_ldisc.c824
1 files changed, 824 insertions, 0 deletions
diff --git a/drivers/tty/tty_ldisc.c b/drivers/tty/tty_ldisc.c
new file mode 100644
index 000000000..27fe8236f
--- /dev/null
+++ b/drivers/tty/tty_ldisc.c
@@ -0,0 +1,824 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kmod.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/file.h>
+#include <linux/mm.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/proc_fs.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/wait.h>
+#include <linux/bitops.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+#include <linux/ratelimit.h>
+#include "tty.h"
+
+#undef LDISC_DEBUG_HANGUP
+
+#ifdef LDISC_DEBUG_HANGUP
+#define tty_ldisc_debug(tty, f, args...) tty_debug(tty, f, ##args)
+#else
+#define tty_ldisc_debug(tty, f, args...)
+#endif
+
+/* lockdep nested classes for tty->ldisc_sem */
+enum {
+ LDISC_SEM_NORMAL,
+ LDISC_SEM_OTHER,
+};
+
+
+/*
+ * This guards the refcounted line discipline lists. The lock
+ * must be taken with irqs off because there are hangup path
+ * callers who will do ldisc lookups and cannot sleep.
+ */
+
+static DEFINE_RAW_SPINLOCK(tty_ldiscs_lock);
+/* Line disc dispatch table */
+static const struct tty_ldisc_ops *tty_ldiscs[NR_LDISCS];
+
+/**
+ * tty_register_ldisc - install a line discipline
+ * @new_ldisc: pointer to the ldisc object
+ *
+ * Installs a new line discipline into the kernel. The discipline is set up as
+ * unreferenced and then made available to the kernel from this point onwards.
+ *
+ * Locking: takes %tty_ldiscs_lock to guard against ldisc races
+ */
+int tty_register_ldisc(const struct tty_ldisc_ops *new_ldisc)
+{
+ unsigned long flags;
+
+ if (new_ldisc->num < N_TTY || new_ldisc->num >= NR_LDISCS)
+ return -EINVAL;
+
+ raw_spin_lock_irqsave(&tty_ldiscs_lock, flags);
+ tty_ldiscs[new_ldisc->num] = new_ldisc;
+ raw_spin_unlock_irqrestore(&tty_ldiscs_lock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL(tty_register_ldisc);
+
+/**
+ * tty_unregister_ldisc - unload a line discipline
+ * @ldisc: ldisc number
+ *
+ * Remove a line discipline from the kernel providing it is not currently in
+ * use.
+ *
+ * Locking: takes %tty_ldiscs_lock to guard against ldisc races
+ */
+
+void tty_unregister_ldisc(const struct tty_ldisc_ops *ldisc)
+{
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&tty_ldiscs_lock, flags);
+ tty_ldiscs[ldisc->num] = NULL;
+ raw_spin_unlock_irqrestore(&tty_ldiscs_lock, flags);
+}
+EXPORT_SYMBOL(tty_unregister_ldisc);
+
+static const struct tty_ldisc_ops *get_ldops(int disc)
+{
+ unsigned long flags;
+ const struct tty_ldisc_ops *ldops, *ret;
+
+ raw_spin_lock_irqsave(&tty_ldiscs_lock, flags);
+ ret = ERR_PTR(-EINVAL);
+ ldops = tty_ldiscs[disc];
+ if (ldops) {
+ ret = ERR_PTR(-EAGAIN);
+ if (try_module_get(ldops->owner))
+ ret = ldops;
+ }
+ raw_spin_unlock_irqrestore(&tty_ldiscs_lock, flags);
+ return ret;
+}
+
+static void put_ldops(const struct tty_ldisc_ops *ldops)
+{
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&tty_ldiscs_lock, flags);
+ module_put(ldops->owner);
+ raw_spin_unlock_irqrestore(&tty_ldiscs_lock, flags);
+}
+
+int tty_ldisc_autoload = IS_BUILTIN(CONFIG_LDISC_AUTOLOAD);
+
+/**
+ * tty_ldisc_get - take a reference to an ldisc
+ * @tty: tty device
+ * @disc: ldisc number
+ *
+ * Takes a reference to a line discipline. Deals with refcounts and module
+ * locking counts. If the discipline is not available, its module loaded, if
+ * possible.
+ *
+ * Returns:
+ * * -%EINVAL if the discipline index is not [%N_TTY .. %NR_LDISCS] or if the
+ * discipline is not registered
+ * * -%EAGAIN if request_module() failed to load or register the discipline
+ * * -%ENOMEM if allocation failure
+ * * Otherwise, returns a pointer to the discipline and bumps the ref count
+ *
+ * Locking: takes %tty_ldiscs_lock to guard against ldisc races
+ */
+static struct tty_ldisc *tty_ldisc_get(struct tty_struct *tty, int disc)
+{
+ struct tty_ldisc *ld;
+ const struct tty_ldisc_ops *ldops;
+
+ if (disc < N_TTY || disc >= NR_LDISCS)
+ return ERR_PTR(-EINVAL);
+
+ /*
+ * Get the ldisc ops - we may need to request them to be loaded
+ * dynamically and try again.
+ */
+ ldops = get_ldops(disc);
+ if (IS_ERR(ldops)) {
+ if (!capable(CAP_SYS_MODULE) && !tty_ldisc_autoload)
+ return ERR_PTR(-EPERM);
+ request_module("tty-ldisc-%d", disc);
+ ldops = get_ldops(disc);
+ if (IS_ERR(ldops))
+ return ERR_CAST(ldops);
+ }
+
+ /*
+ * There is no way to handle allocation failure of only 16 bytes.
+ * Let's simplify error handling and save more memory.
+ */
+ ld = kmalloc_obj(struct tty_ldisc, GFP_KERNEL | __GFP_NOFAIL);
+ ld->ops = ldops;
+ ld->tty = tty;
+
+ return ld;
+}
+
+/**
+ * tty_ldisc_put - release the ldisc
+ * @ld: lisdsc to release
+ *
+ * Complement of tty_ldisc_get().
+ */
+static void tty_ldisc_put(struct tty_ldisc *ld)
+{
+ if (WARN_ON_ONCE(!ld))
+ return;
+
+ put_ldops(ld->ops);
+ kfree(ld);
+}
+
+static void *tty_ldiscs_seq_start(struct seq_file *m, loff_t *pos)
+{
+ return (*pos < NR_LDISCS) ? pos : NULL;
+}
+
+static void *tty_ldiscs_seq_next(struct seq_file *m, void *v, loff_t *pos)
+{
+ (*pos)++;
+ return (*pos < NR_LDISCS) ? pos : NULL;
+}
+
+static void tty_ldiscs_seq_stop(struct seq_file *m, void *v)
+{
+}
+
+static int tty_ldiscs_seq_show(struct seq_file *m, void *v)
+{
+ int i = *(loff_t *)v;
+ const struct tty_ldisc_ops *ldops;
+
+ ldops = get_ldops(i);
+ if (IS_ERR(ldops))
+ return 0;
+ seq_printf(m, "%-10s %2d\n", ldops->name ? ldops->name : "???", i);
+ put_ldops(ldops);
+ return 0;
+}
+
+const struct seq_operations tty_ldiscs_seq_ops = {
+ .start = tty_ldiscs_seq_start,
+ .next = tty_ldiscs_seq_next,
+ .stop = tty_ldiscs_seq_stop,
+ .show = tty_ldiscs_seq_show,
+};
+
+/**
+ * tty_ldisc_ref_wait - wait for the tty ldisc
+ * @tty: tty device
+ *
+ * Dereference the line discipline for the terminal and take a reference to it.
+ * If the line discipline is in flux then wait patiently until it changes.
+ *
+ * Returns: %NULL if the tty has been hungup and not re-opened with a new file
+ * descriptor, otherwise valid ldisc reference
+ *
+ * Note 1: Must not be called from an IRQ/timer context. The caller must also
+ * be careful not to hold other locks that will deadlock against a discipline
+ * change, such as an existing ldisc reference (which we check for).
+ *
+ * Note 2: a file_operations routine (read/poll/write) should use this function
+ * to wait for any ldisc lifetime events to finish.
+ */
+struct tty_ldisc *tty_ldisc_ref_wait(struct tty_struct *tty)
+{
+ struct tty_ldisc *ld;
+
+ ldsem_down_read(&tty->ldisc_sem, MAX_SCHEDULE_TIMEOUT);
+ ld = tty->ldisc;
+ if (!ld)
+ ldsem_up_read(&tty->ldisc_sem);
+ return ld;
+}
+EXPORT_SYMBOL_GPL(tty_ldisc_ref_wait);
+
+/**
+ * tty_ldisc_ref - get the tty ldisc
+ * @tty: tty device
+ *
+ * Dereference the line discipline for the terminal and take a reference to it.
+ * If the line discipline is in flux then return %NULL. Can be called from IRQ
+ * and timer functions.
+ */
+struct tty_ldisc *tty_ldisc_ref(struct tty_struct *tty)
+{
+ struct tty_ldisc *ld = NULL;
+
+ if (ldsem_down_read_trylock(&tty->ldisc_sem)) {
+ ld = tty->ldisc;
+ if (!ld)
+ ldsem_up_read(&tty->ldisc_sem);
+ }
+ return ld;
+}
+EXPORT_SYMBOL_GPL(tty_ldisc_ref);
+
+/**
+ * tty_ldisc_deref - free a tty ldisc reference
+ * @ld: reference to free up
+ *
+ * Undoes the effect of tty_ldisc_ref() or tty_ldisc_ref_wait(). May be called
+ * in IRQ context.
+ */
+void tty_ldisc_deref(struct tty_ldisc *ld)
+{
+ ldsem_up_read(&ld->tty->ldisc_sem);
+}
+EXPORT_SYMBOL_GPL(tty_ldisc_deref);
+
+
+static inline int
+__tty_ldisc_lock(struct tty_struct *tty, unsigned long timeout)
+{
+ return ldsem_down_write(&tty->ldisc_sem, timeout);
+}
+
+static inline int
+__tty_ldisc_lock_nested(struct tty_struct *tty, unsigned long timeout)
+{
+ return ldsem_down_write_nested(&tty->ldisc_sem,
+ LDISC_SEM_OTHER, timeout);
+}
+
+static inline void __tty_ldisc_unlock(struct tty_struct *tty)
+{
+ ldsem_up_write(&tty->ldisc_sem);
+}
+
+int tty_ldisc_lock(struct tty_struct *tty, unsigned long timeout)
+{
+ int ret;
+
+ /* Kindly asking blocked readers to release the read side */
+ set_bit(TTY_LDISC_CHANGING, &tty->flags);
+ wake_up_interruptible_all(&tty->read_wait);
+ wake_up_interruptible_all(&tty->write_wait);
+
+ ret = __tty_ldisc_lock(tty, timeout);
+ if (!ret)
+ return -EBUSY;
+ set_bit(TTY_LDISC_HALTED, &tty->flags);
+ return 0;
+}
+
+void tty_ldisc_unlock(struct tty_struct *tty)
+{
+ clear_bit(TTY_LDISC_HALTED, &tty->flags);
+ /* Can be cleared here - ldisc_unlock will wake up writers firstly */
+ clear_bit(TTY_LDISC_CHANGING, &tty->flags);
+ __tty_ldisc_unlock(tty);
+}
+
+static int
+tty_ldisc_lock_pair_timeout(struct tty_struct *tty, struct tty_struct *tty2,
+ unsigned long timeout)
+{
+ int ret;
+
+ if (tty < tty2) {
+ ret = __tty_ldisc_lock(tty, timeout);
+ if (ret) {
+ ret = __tty_ldisc_lock_nested(tty2, timeout);
+ if (!ret)
+ __tty_ldisc_unlock(tty);
+ }
+ } else {
+ /* if this is possible, it has lots of implications */
+ WARN_ON_ONCE(tty == tty2);
+ if (tty2 && tty != tty2) {
+ ret = __tty_ldisc_lock(tty2, timeout);
+ if (ret) {
+ ret = __tty_ldisc_lock_nested(tty, timeout);
+ if (!ret)
+ __tty_ldisc_unlock(tty2);
+ }
+ } else
+ ret = __tty_ldisc_lock(tty, timeout);
+ }
+
+ if (!ret)
+ return -EBUSY;
+
+ set_bit(TTY_LDISC_HALTED, &tty->flags);
+ if (tty2)
+ set_bit(TTY_LDISC_HALTED, &tty2->flags);
+ return 0;
+}
+
+static void tty_ldisc_lock_pair(struct tty_struct *tty, struct tty_struct *tty2)
+{
+ tty_ldisc_lock_pair_timeout(tty, tty2, MAX_SCHEDULE_TIMEOUT);
+}
+
+static void tty_ldisc_unlock_pair(struct tty_struct *tty,
+ struct tty_struct *tty2)
+{
+ __tty_ldisc_unlock(tty);
+ if (tty2)
+ __tty_ldisc_unlock(tty2);
+}
+
+/**
+ * tty_ldisc_flush - flush line discipline queue
+ * @tty: tty to flush ldisc for
+ *
+ * Flush the line discipline queue (if any) and the tty flip buffers for this
+ * @tty.
+ */
+void tty_ldisc_flush(struct tty_struct *tty)
+{
+ struct tty_ldisc *ld = tty_ldisc_ref(tty);
+
+ tty_buffer_flush(tty, ld);
+ if (ld)
+ tty_ldisc_deref(ld);
+}
+EXPORT_SYMBOL_GPL(tty_ldisc_flush);
+
+/**
+ * tty_set_termios_ldisc - set ldisc field
+ * @tty: tty structure
+ * @disc: line discipline number
+ *
+ * This is probably overkill for real world processors but they are not on hot
+ * paths so a little discipline won't do any harm.
+ *
+ * The line discipline-related tty_struct fields are reset to prevent the ldisc
+ * driver from re-using stale information for the new ldisc instance.
+ *
+ * Locking: takes termios_rwsem
+ */
+static void tty_set_termios_ldisc(struct tty_struct *tty, int disc)
+{
+ down_write(&tty->termios_rwsem);
+ tty->termios.c_line = disc;
+ up_write(&tty->termios_rwsem);
+
+ tty->disc_data = NULL;
+ tty->receive_room = 0;
+}
+
+/**
+ * tty_ldisc_open - open a line discipline
+ * @tty: tty we are opening the ldisc on
+ * @ld: discipline to open
+ *
+ * A helper opening method. Also a convenient debugging and check point.
+ *
+ * Locking: always called with BTM already held.
+ */
+static int tty_ldisc_open(struct tty_struct *tty, struct tty_ldisc *ld)
+{
+ WARN_ON(test_and_set_bit(TTY_LDISC_OPEN, &tty->flags));
+ if (ld->ops->open) {
+ int ret;
+ /* BTM here locks versus a hangup event */
+ ret = ld->ops->open(tty);
+ if (ret)
+ clear_bit(TTY_LDISC_OPEN, &tty->flags);
+
+ tty_ldisc_debug(tty, "%p: opened\n", ld);
+ return ret;
+ }
+ return 0;
+}
+
+/**
+ * tty_ldisc_close - close a line discipline
+ * @tty: tty we are opening the ldisc on
+ * @ld: discipline to close
+ *
+ * A helper close method. Also a convenient debugging and check point.
+ */
+static void tty_ldisc_close(struct tty_struct *tty, struct tty_ldisc *ld)
+{
+ lockdep_assert_held_write(&tty->ldisc_sem);
+ WARN_ON(!test_bit(TTY_LDISC_OPEN, &tty->flags));
+ clear_bit(TTY_LDISC_OPEN, &tty->flags);
+ if (ld->ops->close)
+ ld->ops->close(tty);
+ tty_ldisc_debug(tty, "%p: closed\n", ld);
+}
+
+/**
+ * tty_ldisc_failto - helper for ldisc failback
+ * @tty: tty to open the ldisc on
+ * @ld: ldisc we are trying to fail back to
+ *
+ * Helper to try and recover a tty when switching back to the old ldisc fails
+ * and we need something attached.
+ */
+static int tty_ldisc_failto(struct tty_struct *tty, int ld)
+{
+ struct tty_ldisc *disc = tty_ldisc_get(tty, ld);
+ int r;
+
+ lockdep_assert_held_write(&tty->ldisc_sem);
+ if (IS_ERR(disc))
+ return PTR_ERR(disc);
+ tty->ldisc = disc;
+ tty_set_termios_ldisc(tty, ld);
+ r = tty_ldisc_open(tty, disc);
+ if (r < 0)
+ tty_ldisc_put(disc);
+ return r;
+}
+
+/**
+ * tty_ldisc_restore - helper for tty ldisc change
+ * @tty: tty to recover
+ * @old: previous ldisc
+ *
+ * Restore the previous line discipline or %N_TTY when a line discipline change
+ * fails due to an open error
+ */
+static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old)
+{
+ /* There is an outstanding reference here so this is safe */
+ if (tty_ldisc_failto(tty, old->ops->num) < 0) {
+ const char *name = tty_name(tty);
+
+ pr_warn("Falling back ldisc for %s.\n", name);
+ /*
+ * The traditional behaviour is to fall back to N_TTY, we
+ * want to avoid falling back to N_NULL unless we have no
+ * choice to avoid the risk of breaking anything
+ */
+ if (tty_ldisc_failto(tty, N_TTY) < 0 &&
+ tty_ldisc_failto(tty, N_NULL) < 0)
+ panic("Couldn't open N_NULL ldisc for %s.", name);
+ }
+}
+
+/**
+ * tty_set_ldisc - set line discipline
+ * @tty: the terminal to set
+ * @disc: the line discipline number
+ *
+ * Set the discipline of a tty line. Must be called from a process context. The
+ * ldisc change logic has to protect itself against any overlapping ldisc
+ * change (including on the other end of pty pairs), the close of one side of a
+ * tty/pty pair, and eventually hangup.
+ */
+int tty_set_ldisc(struct tty_struct *tty, int disc)
+{
+ int retval;
+ struct tty_ldisc *old_ldisc, *new_ldisc;
+
+ new_ldisc = tty_ldisc_get(tty, disc);
+ if (IS_ERR(new_ldisc))
+ return PTR_ERR(new_ldisc);
+
+ tty_lock(tty);
+ retval = tty_ldisc_lock(tty, 5 * HZ);
+ if (retval)
+ goto err;
+
+ if (!tty->ldisc) {
+ retval = -EIO;
+ goto out;
+ }
+
+ /* Check the no-op case */
+ if (tty->ldisc->ops->num == disc)
+ goto out;
+
+ if (test_bit(TTY_HUPPED, &tty->flags)) {
+ /* We were raced by hangup */
+ retval = -EIO;
+ goto out;
+ }
+
+ if (tty->ops->ldisc_ok) {
+ retval = tty->ops->ldisc_ok(tty, disc);
+ if (retval)
+ goto out;
+ }
+
+ old_ldisc = tty->ldisc;
+
+ /* Shutdown the old discipline. */
+ tty_ldisc_close(tty, old_ldisc);
+
+ /* Now set up the new line discipline. */
+ tty->ldisc = new_ldisc;
+ tty_set_termios_ldisc(tty, disc);
+
+ retval = tty_ldisc_open(tty, new_ldisc);
+ if (retval < 0) {
+ /* Back to the old one or N_TTY if we can't */
+ tty_ldisc_put(new_ldisc);
+ tty_ldisc_restore(tty, old_ldisc);
+ }
+
+ if (tty->ldisc->ops->num != old_ldisc->ops->num && tty->ops->set_ldisc) {
+ down_read(&tty->termios_rwsem);
+ tty->ops->set_ldisc(tty);
+ up_read(&tty->termios_rwsem);
+ }
+
+ /*
+ * At this point we hold a reference to the new ldisc and a
+ * reference to the old ldisc, or we hold two references to
+ * the old ldisc (if it was restored as part of error cleanup
+ * above). In either case, releasing a single reference from
+ * the old ldisc is correct.
+ */
+ new_ldisc = old_ldisc;
+out:
+ tty_ldisc_unlock(tty);
+
+ /*
+ * Restart the work queue in case no characters kick it off. Safe if
+ * already running
+ */
+ tty_buffer_restart_work(tty->port);
+err:
+ tty_ldisc_put(new_ldisc); /* drop the extra reference */
+ tty_unlock(tty);
+ return retval;
+}
+EXPORT_SYMBOL_GPL(tty_set_ldisc);
+
+/**
+ * tty_ldisc_kill - teardown ldisc
+ * @tty: tty being released
+ *
+ * Perform final close of the ldisc and reset @tty->ldisc
+ */
+static void tty_ldisc_kill(struct tty_struct *tty)
+{
+ lockdep_assert_held_write(&tty->ldisc_sem);
+ if (!tty->ldisc)
+ return;
+ /*
+ * Now kill off the ldisc
+ */
+ tty_ldisc_close(tty, tty->ldisc);
+ tty_ldisc_put(tty->ldisc);
+ /* Force an oops if we mess this up */
+ tty->ldisc = NULL;
+}
+
+/**
+ * tty_reset_termios - reset terminal state
+ * @tty: tty to reset
+ *
+ * Restore a terminal to the driver default state.
+ */
+static void tty_reset_termios(struct tty_struct *tty)
+{
+ down_write(&tty->termios_rwsem);
+ tty->termios = tty->driver->init_termios;
+ tty->termios.c_ispeed = tty_termios_input_baud_rate(&tty->termios);
+ tty->termios.c_ospeed = tty_termios_baud_rate(&tty->termios);
+ up_write(&tty->termios_rwsem);
+}
+
+
+/**
+ * tty_ldisc_reinit - reinitialise the tty ldisc
+ * @tty: tty to reinit
+ * @disc: line discipline to reinitialize
+ *
+ * Completely reinitialize the line discipline state, by closing the current
+ * instance, if there is one, and opening a new instance. If an error occurs
+ * opening the new non-%N_TTY instance, the instance is dropped and @tty->ldisc
+ * reset to %NULL. The caller can then retry with %N_TTY instead.
+ *
+ * Returns: 0 if successful, otherwise error code < 0
+ */
+int tty_ldisc_reinit(struct tty_struct *tty, int disc)
+{
+ struct tty_ldisc *ld;
+ int retval;
+
+ lockdep_assert_held_write(&tty->ldisc_sem);
+ ld = tty_ldisc_get(tty, disc);
+ if (IS_ERR(ld)) {
+ BUG_ON(disc == N_TTY);
+ return PTR_ERR(ld);
+ }
+
+ if (tty->ldisc) {
+ tty_ldisc_close(tty, tty->ldisc);
+ tty_ldisc_put(tty->ldisc);
+ }
+
+ /* switch the line discipline */
+ tty->ldisc = ld;
+ tty_set_termios_ldisc(tty, disc);
+ retval = tty_ldisc_open(tty, tty->ldisc);
+ if (retval) {
+ tty_ldisc_put(tty->ldisc);
+ tty->ldisc = NULL;
+ }
+ return retval;
+}
+
+/**
+ * tty_ldisc_hangup - hangup ldisc reset
+ * @tty: tty being hung up
+ * @reinit: whether to re-initialise the tty
+ *
+ * Some tty devices reset their termios when they receive a hangup event. In
+ * that situation we must also switch back to %N_TTY properly before we reset
+ * the termios data.
+ *
+ * Locking: We can take the ldisc mutex as the rest of the code is careful to
+ * allow for this.
+ *
+ * In the pty pair case this occurs in the close() path of the tty itself so we
+ * must be careful about locking rules.
+ */
+void tty_ldisc_hangup(struct tty_struct *tty, bool reinit)
+{
+ struct tty_ldisc *ld;
+
+ tty_ldisc_debug(tty, "%p: hangup\n", tty->ldisc);
+
+ ld = tty_ldisc_ref(tty);
+ if (ld != NULL) {
+ if (ld->ops->flush_buffer)
+ ld->ops->flush_buffer(tty);
+ tty_driver_flush_buffer(tty);
+ if ((test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) &&
+ ld->ops->write_wakeup)
+ ld->ops->write_wakeup(tty);
+ if (ld->ops->hangup)
+ ld->ops->hangup(tty);
+ tty_ldisc_deref(ld);
+ }
+
+ wake_up_interruptible_poll(&tty->write_wait, EPOLLOUT);
+ wake_up_interruptible_poll(&tty->read_wait, EPOLLIN);
+
+ /*
+ * Shutdown the current line discipline, and reset it to
+ * N_TTY if need be.
+ *
+ * Avoid racing set_ldisc or tty_ldisc_release
+ */
+ tty_ldisc_lock(tty, MAX_SCHEDULE_TIMEOUT);
+
+ if (tty->driver->flags & TTY_DRIVER_RESET_TERMIOS)
+ tty_reset_termios(tty);
+
+ if (tty->ldisc) {
+ if (reinit) {
+ if (tty_ldisc_reinit(tty, tty->termios.c_line) < 0 &&
+ tty_ldisc_reinit(tty, N_TTY) < 0)
+ WARN_ON(tty_ldisc_reinit(tty, N_NULL) < 0);
+ } else
+ tty_ldisc_kill(tty);
+ }
+ tty_ldisc_unlock(tty);
+}
+
+/**
+ * tty_ldisc_setup - open line discipline
+ * @tty: tty being shut down
+ * @o_tty: pair tty for pty/tty pairs
+ *
+ * Called during the initial open of a tty/pty pair in order to set up the line
+ * disciplines and bind them to the @tty. This has no locking issues as the
+ * device isn't yet active.
+ */
+int tty_ldisc_setup(struct tty_struct *tty, struct tty_struct *o_tty)
+{
+ int retval = tty_ldisc_open(tty, tty->ldisc);
+
+ if (retval)
+ return retval;
+
+ if (o_tty) {
+ /*
+ * Called without o_tty->ldisc_sem held, as o_tty has been
+ * just allocated and no one has a reference to it.
+ */
+ retval = tty_ldisc_open(o_tty, o_tty->ldisc);
+ if (retval) {
+ tty_ldisc_close(tty, tty->ldisc);
+ return retval;
+ }
+ }
+ return 0;
+}
+
+/**
+ * tty_ldisc_release - release line discipline
+ * @tty: tty being shut down (or one end of pty pair)
+ *
+ * Called during the final close of a tty or a pty pair in order to shut down
+ * the line discpline layer. On exit, each tty's ldisc is %NULL.
+ */
+void tty_ldisc_release(struct tty_struct *tty)
+{
+ struct tty_struct *o_tty = tty->link;
+
+ /*
+ * Shutdown this line discipline. As this is the final close,
+ * it does not race with the set_ldisc code path.
+ */
+
+ tty_ldisc_lock_pair(tty, o_tty);
+ tty_ldisc_kill(tty);
+ if (o_tty)
+ tty_ldisc_kill(o_tty);
+ tty_ldisc_unlock_pair(tty, o_tty);
+
+ /*
+ * And the memory resources remaining (buffers, termios) will be
+ * disposed of when the kref hits zero
+ */
+
+ tty_ldisc_debug(tty, "released\n");
+}
+
+/**
+ * tty_ldisc_init - ldisc setup for new tty
+ * @tty: tty being allocated
+ *
+ * Set up the line discipline objects for a newly allocated tty. Note that the
+ * tty structure is not completely set up when this call is made.
+ */
+int tty_ldisc_init(struct tty_struct *tty)
+{
+ struct tty_ldisc *ld = tty_ldisc_get(tty, N_TTY);
+
+ if (IS_ERR(ld))
+ return PTR_ERR(ld);
+ tty->ldisc = ld;
+ return 0;
+}
+
+/**
+ * tty_ldisc_deinit - ldisc cleanup for new tty
+ * @tty: tty that was allocated recently
+ *
+ * The tty structure must not be completely set up (tty_ldisc_setup()) when
+ * this call is made.
+ */
+void tty_ldisc_deinit(struct tty_struct *tty)
+{
+ /* no ldisc_sem, tty is being destroyed */
+ if (tty->ldisc)
+ tty_ldisc_put(tty->ldisc);
+ tty->ldisc = NULL;
+}