diff mbox series

[v2,31/34] drivers/tty: Enable capability analysis for core files

Message ID 20250304092417.2873893-32-elver@google.com (mailing list archive)
State New
Headers show
Series Compiler-Based Capability- and Locking-Analysis | expand

Commit Message

Marco Elver March 4, 2025, 9:21 a.m. UTC
Enable capability analysis for drivers/tty/*.

This demonstrates a larger conversion to use Clang's capability
analysis. The benefit is additional static checking of locking rules,
along with better documentation.

Signed-off-by: Marco Elver <elver@google.com>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Jiri Slaby <jirislaby@kernel.org>
---
v2:
* New patch.
---
 drivers/tty/Makefile      |  3 +++
 drivers/tty/n_tty.c       | 16 ++++++++++++++++
 drivers/tty/pty.c         |  1 +
 drivers/tty/sysrq.c       |  1 +
 drivers/tty/tty.h         |  8 ++++----
 drivers/tty/tty_buffer.c  |  8 +++-----
 drivers/tty/tty_io.c      | 12 +++++++++---
 drivers/tty/tty_ioctl.c   |  2 +-
 drivers/tty/tty_ldisc.c   | 35 ++++++++++++++++++++++++++++++++---
 drivers/tty/tty_ldsem.c   |  2 ++
 drivers/tty/tty_mutex.c   |  4 ++++
 drivers/tty/tty_port.c    |  2 ++
 include/linux/tty.h       | 14 +++++++-------
 include/linux/tty_flip.h  |  4 ++--
 include/linux/tty_ldisc.h | 19 ++++++++++---------
 15 files changed, 97 insertions(+), 34 deletions(-)

Comments

Jiri Slaby March 5, 2025, 9:15 a.m. UTC | #1
On 04. 03. 25, 10:21, Marco Elver wrote:
> Enable capability analysis for drivers/tty/*.
> 
> This demonstrates a larger conversion to use Clang's capability
> analysis. The benefit is additional static checking of locking rules,
> along with better documentation.
> 
> Signed-off-by: Marco Elver <elver@google.com>
> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> Cc: Jiri Slaby <jirislaby@kernel.org>
...
> --- a/drivers/tty/tty_buffer.c
> +++ b/drivers/tty/tty_buffer.c
> @@ -52,10 +52,8 @@
>    */
>   void tty_buffer_lock_exclusive(struct tty_port *port)
>   {
> -	struct tty_bufhead *buf = &port->buf;
> -
> -	atomic_inc(&buf->priority);
> -	mutex_lock(&buf->lock);
> +	atomic_inc(&port->buf.priority);
> +	mutex_lock(&port->buf.lock);

Here and:

> @@ -73,7 +71,7 @@ void tty_buffer_unlock_exclusive(struct tty_port *port)
>   	bool restart = buf->head->commit != buf->head->read;
>   
>   	atomic_dec(&buf->priority);
> -	mutex_unlock(&buf->lock);
> +	mutex_unlock(&port->buf.lock);

here, this appears excessive. You are changing code to adapt to one kind 
of static analysis. Adding function annotations is mostly fine, but 
changing code is too much. We don't do that. Fix the analyzer instead.

> --- a/drivers/tty/tty_io.c
> +++ b/drivers/tty/tty_io.c
> @@ -167,6 +167,7 @@ static void release_tty(struct tty_struct *tty, int idx);
>    * Locking: none. Must be called after tty is definitely unused
>    */
>   static void free_tty_struct(struct tty_struct *tty)
> +	__capability_unsafe(/* destructor */)
>   {
>   	tty_ldisc_deinit(tty);
>   	put_device(tty->dev);
> @@ -965,7 +966,7 @@ static ssize_t iterate_tty_write(struct tty_ldisc *ld, struct tty_struct *tty,
>   	ssize_t ret, written = 0;
>   
>   	ret = tty_write_lock(tty, file->f_flags & O_NDELAY);
> -	if (ret < 0)
> +	if (ret)

This change is not documented.

> @@ -1154,7 +1155,7 @@ int tty_send_xchar(struct tty_struct *tty, u8 ch)
>   		return 0;
>   	}
>   
> -	if (tty_write_lock(tty, false) < 0)
> +	if (tty_write_lock(tty, false))

And this one. And more times later.

> --- a/drivers/tty/tty_ldisc.c
> +++ b/drivers/tty/tty_ldisc.c
...
> +/*
> + * Note: Capability analysis does not like asymmetric interfaces (above types
> + * for ref and deref are tty_struct and tty_ldisc respectively -- which are
> + * dependent, but the compiler cannot figure that out); in this case, work
> + * around that with this helper which takes an unused @tty argument but tells
> + * the analysis which lock is released.
> + */
> +static inline void __tty_ldisc_deref(struct tty_struct *tty, struct tty_ldisc *ld)
> +	__releases_shared(&tty->ldisc_sem)
> +	__capability_unsafe(/* matches released with tty_ldisc_ref() */)
> +{
> +	tty_ldisc_deref(ld);
> +}

You want to invert the __ prefix for these two. tty_ldisc_deref() should 
be kept as the one to be called by everybody.

thanks,
Marco Elver March 5, 2025, 9:26 a.m. UTC | #2
On Wed, 5 Mar 2025 at 10:15, Jiri Slaby <jirislaby@kernel.org> wrote:
>
> On 04. 03. 25, 10:21, Marco Elver wrote:
> > Enable capability analysis for drivers/tty/*.
> >
> > This demonstrates a larger conversion to use Clang's capability
> > analysis. The benefit is additional static checking of locking rules,
> > along with better documentation.
> >
> > Signed-off-by: Marco Elver <elver@google.com>
> > Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> > Cc: Jiri Slaby <jirislaby@kernel.org>
> ...
> > --- a/drivers/tty/tty_buffer.c
> > +++ b/drivers/tty/tty_buffer.c
> > @@ -52,10 +52,8 @@
> >    */
> >   void tty_buffer_lock_exclusive(struct tty_port *port)
> >   {
> > -     struct tty_bufhead *buf = &port->buf;
> > -
> > -     atomic_inc(&buf->priority);
> > -     mutex_lock(&buf->lock);
> > +     atomic_inc(&port->buf.priority);
> > +     mutex_lock(&port->buf.lock);
>
> Here and:
>
> > @@ -73,7 +71,7 @@ void tty_buffer_unlock_exclusive(struct tty_port *port)
> >       bool restart = buf->head->commit != buf->head->read;
> >
> >       atomic_dec(&buf->priority);
> > -     mutex_unlock(&buf->lock);
> > +     mutex_unlock(&port->buf.lock);
>
> here, this appears excessive. You are changing code to adapt to one kind
> of static analysis. Adding function annotations is mostly fine, but
> changing code is too much. We don't do that. Fix the analyzer instead.

Right. So the analysis doesn't do alias analysis.

> > --- a/drivers/tty/tty_io.c
> > +++ b/drivers/tty/tty_io.c
> > @@ -167,6 +167,7 @@ static void release_tty(struct tty_struct *tty, int idx);
> >    * Locking: none. Must be called after tty is definitely unused
> >    */
> >   static void free_tty_struct(struct tty_struct *tty)
> > +     __capability_unsafe(/* destructor */)
> >   {
> >       tty_ldisc_deinit(tty);
> >       put_device(tty->dev);
> > @@ -965,7 +966,7 @@ static ssize_t iterate_tty_write(struct tty_ldisc *ld, struct tty_struct *tty,
> >       ssize_t ret, written = 0;
> >
> >       ret = tty_write_lock(tty, file->f_flags & O_NDELAY);
> > -     if (ret < 0)
> > +     if (ret)
>
> This change is not documented.

Fair point. This is because the analysis can only deal with
conditional locking when fed into zero/non-zero condition checks.

> > @@ -1154,7 +1155,7 @@ int tty_send_xchar(struct tty_struct *tty, u8 ch)
> >               return 0;
> >       }
> >
> > -     if (tty_write_lock(tty, false) < 0)
> > +     if (tty_write_lock(tty, false))
>
> And this one. And more times later.
>
> > --- a/drivers/tty/tty_ldisc.c
> > +++ b/drivers/tty/tty_ldisc.c
> ...
> > +/*
> > + * Note: Capability analysis does not like asymmetric interfaces (above types
> > + * for ref and deref are tty_struct and tty_ldisc respectively -- which are
> > + * dependent, but the compiler cannot figure that out); in this case, work
> > + * around that with this helper which takes an unused @tty argument but tells
> > + * the analysis which lock is released.
> > + */
> > +static inline void __tty_ldisc_deref(struct tty_struct *tty, struct tty_ldisc *ld)
> > +     __releases_shared(&tty->ldisc_sem)
> > +     __capability_unsafe(/* matches released with tty_ldisc_ref() */)
> > +{
> > +     tty_ldisc_deref(ld);
> > +}
>
> You want to invert the __ prefix for these two. tty_ldisc_deref() should
> be kept as the one to be called by everybody.

Ack.

I think in the near term the alias analysis issues + conditional check
of < 0 aren't solvable. Alias analysis being the bigger issue.
Two options:
1. Adding __capability_unsafe to the few functions that you weren't
happy with above.
2. Dropping the whole patch.

I'm inclined to drop the whole patch.

Thanks,
-- Marco
diff mbox series

Patch

diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile
index 07aca5184a55..35e1a62cbe16 100644
--- a/drivers/tty/Makefile
+++ b/drivers/tty/Makefile
@@ -1,4 +1,7 @@ 
 # SPDX-License-Identifier: GPL-2.0
+
+CAPABILITY_ANALYSIS := y
+
 obj-$(CONFIG_TTY)		+= tty_io.o n_tty.o tty_ioctl.o tty_ldisc.o \
 				   tty_buffer.o tty_port.o tty_mutex.o \
 				   tty_ldsem.o tty_baudrate.o tty_jobctrl.o \
diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c
index 5e9ca4376d68..45925fc5a8fd 100644
--- a/drivers/tty/n_tty.c
+++ b/drivers/tty/n_tty.c
@@ -1088,6 +1088,7 @@  static void __isig(int sig, struct tty_struct *tty)
  * Locking: %ctrl.lock
  */
 static void isig(int sig, struct tty_struct *tty)
+	__must_hold_shared(&tty->termios_rwsem)
 {
 	struct n_tty_data *ldata = tty->disc_data;
 
@@ -1135,6 +1136,7 @@  static void isig(int sig, struct tty_struct *tty)
  * Note: may get exclusive %termios_rwsem if flushing input buffer
  */
 static void n_tty_receive_break(struct tty_struct *tty)
+	__must_hold_shared(&tty->termios_rwsem)
 {
 	struct n_tty_data *ldata = tty->disc_data;
 
@@ -1204,6 +1206,7 @@  static void n_tty_receive_parity_error(const struct tty_struct *tty,
 
 static void
 n_tty_receive_signal_char(struct tty_struct *tty, int signal, u8 c)
+	__must_hold_shared(&tty->termios_rwsem)
 {
 	isig(signal, tty);
 	if (I_IXON(tty))
@@ -1353,6 +1356,7 @@  static bool n_tty_receive_char_canon(struct tty_struct *tty, u8 c)
 
 static void n_tty_receive_char_special(struct tty_struct *tty, u8 c,
 				       bool lookahead_done)
+	__must_hold_shared(&tty->termios_rwsem)
 {
 	struct n_tty_data *ldata = tty->disc_data;
 
@@ -1463,6 +1467,7 @@  static void n_tty_receive_char_closing(struct tty_struct *tty, u8 c,
 
 static void
 n_tty_receive_char_flagged(struct tty_struct *tty, u8 c, u8 flag)
+	__must_hold_shared(&tty->termios_rwsem)
 {
 	switch (flag) {
 	case TTY_BREAK:
@@ -1483,6 +1488,7 @@  n_tty_receive_char_flagged(struct tty_struct *tty, u8 c, u8 flag)
 
 static void
 n_tty_receive_char_lnext(struct tty_struct *tty, u8 c, u8 flag)
+	__must_hold_shared(&tty->termios_rwsem)
 {
 	struct n_tty_data *ldata = tty->disc_data;
 
@@ -1540,6 +1546,7 @@  n_tty_receive_buf_real_raw(const struct tty_struct *tty, const u8 *cp,
 static void
 n_tty_receive_buf_raw(struct tty_struct *tty, const u8 *cp, const u8 *fp,
 		      size_t count)
+	__must_hold_shared(&tty->termios_rwsem)
 {
 	struct n_tty_data *ldata = tty->disc_data;
 	u8 flag = TTY_NORMAL;
@@ -1571,6 +1578,7 @@  n_tty_receive_buf_closing(struct tty_struct *tty, const u8 *cp, const u8 *fp,
 static void n_tty_receive_buf_standard(struct tty_struct *tty, const u8 *cp,
 				       const u8 *fp, size_t count,
 				       bool lookahead_done)
+	__must_hold_shared(&tty->termios_rwsem)
 {
 	struct n_tty_data *ldata = tty->disc_data;
 	u8 flag = TTY_NORMAL;
@@ -1609,6 +1617,7 @@  static void n_tty_receive_buf_standard(struct tty_struct *tty, const u8 *cp,
 
 static void __receive_buf(struct tty_struct *tty, const u8 *cp, const u8 *fp,
 			  size_t count)
+	__must_hold_shared(&tty->termios_rwsem)
 {
 	struct n_tty_data *ldata = tty->disc_data;
 	bool preops = I_ISTRIP(tty) || (I_IUCLC(tty) && L_IEXTEN(tty));
@@ -2188,6 +2197,10 @@  static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, u8 *kbuf,
 				return kb - kbuf;
 		}
 
+		/* Adopted locks from prior call. */
+		__acquire(&ldata->atomic_read_lock);
+		__acquire_shared(&tty->termios_rwsem);
+
 		/* No more data - release locks and stop retries */
 		n_tty_kick_worker(tty);
 		n_tty_check_unthrottle(tty);
@@ -2305,6 +2318,9 @@  static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, u8 *kbuf,
 more_to_be_read:
 				remove_wait_queue(&tty->read_wait, &wait);
 				*cookie = cookie;
+				/* Hand-off locks to retry with cookie set. */
+				__release_shared(&tty->termios_rwsem);
+				__release(&ldata->atomic_read_lock);
 				return kb - kbuf;
 			}
 		}
diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c
index 8bb1a01fef2a..8d4eb0f4c84c 100644
--- a/drivers/tty/pty.c
+++ b/drivers/tty/pty.c
@@ -824,6 +824,7 @@  static int ptmx_open(struct inode *inode, struct file *filp)
 	tty = tty_init_dev(ptm_driver, index);
 	/* The tty returned here is locked so we can safely
 	   drop the mutex */
+	lockdep_assert_held(&tty->legacy_mutex);
 	mutex_unlock(&tty_mutex);
 
 	retval = PTR_ERR(tty);
diff --git a/drivers/tty/sysrq.c b/drivers/tty/sysrq.c
index f85ce02e4725..82dfa964c965 100644
--- a/drivers/tty/sysrq.c
+++ b/drivers/tty/sysrq.c
@@ -149,6 +149,7 @@  static const struct sysrq_key_op sysrq_unraw_op = {
 static void sysrq_handle_crash(u8 key)
 {
 	/* release the RCU read lock before crashing */
+	lockdep_assert_in_rcu_read_lock();
 	rcu_read_unlock();
 
 	panic("sysrq triggered crash\n");
diff --git a/drivers/tty/tty.h b/drivers/tty/tty.h
index 93cf5ef1e857..1a3c2f663b28 100644
--- a/drivers/tty/tty.h
+++ b/drivers/tty/tty.h
@@ -60,15 +60,15 @@  static inline void tty_set_flow_change(struct tty_struct *tty,
 	smp_mb();
 }
 
-int tty_ldisc_lock(struct tty_struct *tty, unsigned long timeout);
-void tty_ldisc_unlock(struct tty_struct *tty);
+int tty_ldisc_lock(struct tty_struct *tty, unsigned long timeout) __cond_acquires(0, &tty->ldisc_sem);
+void tty_ldisc_unlock(struct tty_struct *tty) __releases(&tty->ldisc_sem);
 
 int __tty_check_change(struct tty_struct *tty, int sig);
 int tty_check_change(struct tty_struct *tty);
 void __stop_tty(struct tty_struct *tty);
 void __start_tty(struct tty_struct *tty);
-void tty_write_unlock(struct tty_struct *tty);
-int tty_write_lock(struct tty_struct *tty, bool ndelay);
+void tty_write_unlock(struct tty_struct *tty) __releases(&tty->atomic_write_lock);
+int tty_write_lock(struct tty_struct *tty, bool ndelay) __cond_acquires(0, &tty->atomic_write_lock);
 void tty_vhangup_session(struct tty_struct *tty);
 void tty_open_proc_set_tty(struct file *filp, struct tty_struct *tty);
 int tty_signal_session_leader(struct tty_struct *tty, int exit_session);
diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c
index 79f0ff94ce00..dcc56537290f 100644
--- a/drivers/tty/tty_buffer.c
+++ b/drivers/tty/tty_buffer.c
@@ -52,10 +52,8 @@ 
  */
 void tty_buffer_lock_exclusive(struct tty_port *port)
 {
-	struct tty_bufhead *buf = &port->buf;
-
-	atomic_inc(&buf->priority);
-	mutex_lock(&buf->lock);
+	atomic_inc(&port->buf.priority);
+	mutex_lock(&port->buf.lock);
 }
 EXPORT_SYMBOL_GPL(tty_buffer_lock_exclusive);
 
@@ -73,7 +71,7 @@  void tty_buffer_unlock_exclusive(struct tty_port *port)
 	bool restart = buf->head->commit != buf->head->read;
 
 	atomic_dec(&buf->priority);
-	mutex_unlock(&buf->lock);
+	mutex_unlock(&port->buf.lock);
 
 	if (restart)
 		queue_work(system_unbound_wq, &buf->work);
diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c
index 449dbd216460..1eb3794fde4b 100644
--- a/drivers/tty/tty_io.c
+++ b/drivers/tty/tty_io.c
@@ -167,6 +167,7 @@  static void release_tty(struct tty_struct *tty, int idx);
  * Locking: none. Must be called after tty is definitely unused
  */
 static void free_tty_struct(struct tty_struct *tty)
+	__capability_unsafe(/* destructor */)
 {
 	tty_ldisc_deinit(tty);
 	put_device(tty->dev);
@@ -965,7 +966,7 @@  static ssize_t iterate_tty_write(struct tty_ldisc *ld, struct tty_struct *tty,
 	ssize_t ret, written = 0;
 
 	ret = tty_write_lock(tty, file->f_flags & O_NDELAY);
-	if (ret < 0)
+	if (ret)
 		return ret;
 
 	/*
@@ -1154,7 +1155,7 @@  int tty_send_xchar(struct tty_struct *tty, u8 ch)
 		return 0;
 	}
 
-	if (tty_write_lock(tty, false) < 0)
+	if (tty_write_lock(tty, false))
 		return -ERESTARTSYS;
 
 	down_read(&tty->termios_rwsem);
@@ -1391,6 +1392,7 @@  static int tty_reopen(struct tty_struct *tty)
  * Return: new tty structure
  */
 struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx)
+	__capability_unsafe(/* returns with locked tty */)
 {
 	struct tty_struct *tty;
 	int retval;
@@ -1874,6 +1876,7 @@  int tty_release(struct inode *inode, struct file *filp)
  * will not work then. It expects inodes to be from devpts FS.
  */
 static struct tty_struct *tty_open_current_tty(dev_t device, struct file *filp)
+	__capability_unsafe(/* returns with locked tty */)
 {
 	struct tty_struct *tty;
 	int retval;
@@ -2037,6 +2040,7 @@  EXPORT_SYMBOL_GPL(tty_kopen_shared);
  */
 static struct tty_struct *tty_open_by_driver(dev_t device,
 					     struct file *filp)
+	__capability_unsafe(/* returns with locked tty */)
 {
 	struct tty_struct *tty;
 	struct tty_driver *driver = NULL;
@@ -2137,6 +2141,8 @@  static int tty_open(struct inode *inode, struct file *filp)
 		goto retry_open;
 	}
 
+	lockdep_assert_held(&tty->legacy_mutex);
+
 	tty_add_file(tty, filp);
 
 	check_tty_count(tty, __func__);
@@ -2486,7 +2492,7 @@  static int send_break(struct tty_struct *tty, unsigned int duration)
 		return tty->ops->break_ctl(tty, duration);
 
 	/* Do the work ourselves */
-	if (tty_write_lock(tty, false) < 0)
+	if (tty_write_lock(tty, false))
 		return -EINTR;
 
 	retval = tty->ops->break_ctl(tty, -1);
diff --git a/drivers/tty/tty_ioctl.c b/drivers/tty/tty_ioctl.c
index 85de90eebc7b..a7ae6cbf3450 100644
--- a/drivers/tty/tty_ioctl.c
+++ b/drivers/tty/tty_ioctl.c
@@ -489,7 +489,7 @@  static int set_termios(struct tty_struct *tty, void __user *arg, int opt)
 		if (retval < 0)
 			return retval;
 
-		if (tty_write_lock(tty, false) < 0)
+		if (tty_write_lock(tty, false))
 			goto retry_write_wait;
 
 		/* Racing writer? */
diff --git a/drivers/tty/tty_ldisc.c b/drivers/tty/tty_ldisc.c
index d80e9d4c974b..e07a5980604e 100644
--- a/drivers/tty/tty_ldisc.c
+++ b/drivers/tty/tty_ldisc.c
@@ -237,6 +237,7 @@  const struct seq_operations tty_ldiscs_seq_ops = {
  * to wait for any ldisc lifetime events to finish.
  */
 struct tty_ldisc *tty_ldisc_ref_wait(struct tty_struct *tty)
+	__cond_acquires_shared(nonnull, &tty->ldisc_sem)
 {
 	struct tty_ldisc *ld;
 
@@ -257,6 +258,7 @@  EXPORT_SYMBOL_GPL(tty_ldisc_ref_wait);
  * and timer functions.
  */
 struct tty_ldisc *tty_ldisc_ref(struct tty_struct *tty)
+	__cond_acquires_shared(nonnull, &tty->ldisc_sem)
 {
 	struct tty_ldisc *ld = NULL;
 
@@ -277,26 +279,43 @@  EXPORT_SYMBOL_GPL(tty_ldisc_ref);
  * in IRQ context.
  */
 void tty_ldisc_deref(struct tty_ldisc *ld)
+	__releases_shared(&ld->tty->ldisc_sem)
 {
 	ldsem_up_read(&ld->tty->ldisc_sem);
 }
 EXPORT_SYMBOL_GPL(tty_ldisc_deref);
 
+/*
+ * Note: Capability analysis does not like asymmetric interfaces (above types
+ * for ref and deref are tty_struct and tty_ldisc respectively -- which are
+ * dependent, but the compiler cannot figure that out); in this case, work
+ * around that with this helper which takes an unused @tty argument but tells
+ * the analysis which lock is released.
+ */
+static inline void __tty_ldisc_deref(struct tty_struct *tty, struct tty_ldisc *ld)
+	__releases_shared(&tty->ldisc_sem)
+	__capability_unsafe(/* matches released with tty_ldisc_ref() */)
+{
+	tty_ldisc_deref(ld);
+}
 
 static inline int
 __tty_ldisc_lock(struct tty_struct *tty, unsigned long timeout)
+	__cond_acquires(true, &tty->ldisc_sem)
 {
 	return ldsem_down_write(&tty->ldisc_sem, timeout);
 }
 
 static inline int
 __tty_ldisc_lock_nested(struct tty_struct *tty, unsigned long timeout)
+	__cond_acquires(true, &tty->ldisc_sem)
 {
 	return ldsem_down_write_nested(&tty->ldisc_sem,
 				       LDISC_SEM_OTHER, timeout);
 }
 
 static inline void __tty_ldisc_unlock(struct tty_struct *tty)
+	__releases(&tty->ldisc_sem)
 {
 	ldsem_up_write(&tty->ldisc_sem);
 }
@@ -328,6 +347,8 @@  void tty_ldisc_unlock(struct tty_struct *tty)
 static int
 tty_ldisc_lock_pair_timeout(struct tty_struct *tty, struct tty_struct *tty2,
 			    unsigned long timeout)
+	__cond_acquires(0, &tty->ldisc_sem)
+	__cond_acquires(0, &tty2->ldisc_sem)
 {
 	int ret;
 
@@ -362,16 +383,23 @@  tty_ldisc_lock_pair_timeout(struct tty_struct *tty, struct tty_struct *tty2,
 }
 
 static void tty_ldisc_lock_pair(struct tty_struct *tty, struct tty_struct *tty2)
+	__acquires(&tty->ldisc_sem)
+	__acquires(&tty2->ldisc_sem)
+	__capability_unsafe(/* MAX_SCHEDULE_TIMEOUT ensures acquisition */)
 {
 	tty_ldisc_lock_pair_timeout(tty, tty2, MAX_SCHEDULE_TIMEOUT);
 }
 
 static void tty_ldisc_unlock_pair(struct tty_struct *tty,
 				  struct tty_struct *tty2)
+	__releases(&tty->ldisc_sem)
+	__releases(&tty2->ldisc_sem)
 {
 	__tty_ldisc_unlock(tty);
 	if (tty2)
 		__tty_ldisc_unlock(tty2);
+	else
+		__release(&tty2->ldisc_sem);
 }
 
 /**
@@ -387,7 +415,7 @@  void tty_ldisc_flush(struct tty_struct *tty)
 
 	tty_buffer_flush(tty, ld);
 	if (ld)
-		tty_ldisc_deref(ld);
+		__tty_ldisc_deref(tty, ld);
 }
 EXPORT_SYMBOL_GPL(tty_ldisc_flush);
 
@@ -694,7 +722,7 @@  void tty_ldisc_hangup(struct tty_struct *tty, bool reinit)
 	tty_ldisc_debug(tty, "%p: hangup\n", tty->ldisc);
 
 	ld = tty_ldisc_ref(tty);
-	if (ld != NULL) {
+	if (ld) {
 		if (ld->ops->flush_buffer)
 			ld->ops->flush_buffer(tty);
 		tty_driver_flush_buffer(tty);
@@ -703,7 +731,7 @@  void tty_ldisc_hangup(struct tty_struct *tty, bool reinit)
 			ld->ops->write_wakeup(tty);
 		if (ld->ops->hangup)
 			ld->ops->hangup(tty);
-		tty_ldisc_deref(ld);
+		__tty_ldisc_deref(tty, ld);
 	}
 
 	wake_up_interruptible_poll(&tty->write_wait, EPOLLOUT);
@@ -716,6 +744,7 @@  void tty_ldisc_hangup(struct tty_struct *tty, bool reinit)
 	 * Avoid racing set_ldisc or tty_ldisc_release
 	 */
 	tty_ldisc_lock(tty, MAX_SCHEDULE_TIMEOUT);
+	lockdep_assert_held_write(&tty->ldisc_sem);
 
 	if (tty->driver->flags & TTY_DRIVER_RESET_TERMIOS)
 		tty_reset_termios(tty);
diff --git a/drivers/tty/tty_ldsem.c b/drivers/tty/tty_ldsem.c
index 3be428c16260..26d924bb5a46 100644
--- a/drivers/tty/tty_ldsem.c
+++ b/drivers/tty/tty_ldsem.c
@@ -390,6 +390,7 @@  void ldsem_up_read(struct ld_semaphore *sem)
 {
 	long count;
 
+	__release_shared(sem);
 	rwsem_release(&sem->dep_map, _RET_IP_);
 
 	count = atomic_long_add_return(-LDSEM_READ_BIAS, &sem->count);
@@ -404,6 +405,7 @@  void ldsem_up_write(struct ld_semaphore *sem)
 {
 	long count;
 
+	__release(sem);
 	rwsem_release(&sem->dep_map, _RET_IP_);
 
 	count = atomic_long_add_return(-LDSEM_WRITE_BIAS, &sem->count);
diff --git a/drivers/tty/tty_mutex.c b/drivers/tty/tty_mutex.c
index 784e46a0a3b1..e5576fd6f5a4 100644
--- a/drivers/tty/tty_mutex.c
+++ b/drivers/tty/tty_mutex.c
@@ -41,12 +41,16 @@  void tty_lock_slave(struct tty_struct *tty)
 {
 	if (tty && tty != tty->link)
 		tty_lock(tty);
+	else
+		__acquire(&tty->legacy_mutex);
 }
 
 void tty_unlock_slave(struct tty_struct *tty)
 {
 	if (tty && tty != tty->link)
 		tty_unlock(tty);
+	else
+		__release(&tty->legacy_mutex);
 }
 
 void tty_set_lock_subclass(struct tty_struct *tty)
diff --git a/drivers/tty/tty_port.c b/drivers/tty/tty_port.c
index 14cca33d2269..bcb65a26a6bf 100644
--- a/drivers/tty/tty_port.c
+++ b/drivers/tty/tty_port.c
@@ -509,6 +509,7 @@  EXPORT_SYMBOL(tty_port_lower_dtr_rts);
  */
 int tty_port_block_til_ready(struct tty_port *port,
 				struct tty_struct *tty, struct file *filp)
+	__must_hold(&tty->legacy_mutex)
 {
 	int do_clocal = 0, retval;
 	unsigned long flags;
@@ -764,6 +765,7 @@  EXPORT_SYMBOL_GPL(tty_port_install);
  */
 int tty_port_open(struct tty_port *port, struct tty_struct *tty,
 							struct file *filp)
+	__must_hold(&tty->legacy_mutex)
 {
 	spin_lock_irq(&port->lock);
 	++port->count;
diff --git a/include/linux/tty.h b/include/linux/tty.h
index 2372f9357240..ee1ba62fc398 100644
--- a/include/linux/tty.h
+++ b/include/linux/tty.h
@@ -234,8 +234,8 @@  struct tty_struct {
 	void *disc_data;
 	void *driver_data;
 	spinlock_t files_lock;
-	int write_cnt;
-	u8 *write_buf;
+	int write_cnt __guarded_by(&atomic_write_lock);
+	u8 *write_buf __guarded_by(&atomic_write_lock);
 
 	struct list_head tty_files;
 
@@ -500,11 +500,11 @@  long vt_compat_ioctl(struct tty_struct *tty, unsigned int cmd,
 
 /* tty_mutex.c */
 /* functions for preparation of BKL removal */
-void tty_lock(struct tty_struct *tty);
-int  tty_lock_interruptible(struct tty_struct *tty);
-void tty_unlock(struct tty_struct *tty);
-void tty_lock_slave(struct tty_struct *tty);
-void tty_unlock_slave(struct tty_struct *tty);
+void tty_lock(struct tty_struct *tty) __acquires(&tty->legacy_mutex);
+int  tty_lock_interruptible(struct tty_struct *tty) __cond_acquires(0, &tty->legacy_mutex);
+void tty_unlock(struct tty_struct *tty) __releases(&tty->legacy_mutex);
+void tty_lock_slave(struct tty_struct *tty) __acquires(&tty->legacy_mutex);
+void tty_unlock_slave(struct tty_struct *tty) __releases(&tty->legacy_mutex);
 void tty_set_lock_subclass(struct tty_struct *tty);
 
 #endif
diff --git a/include/linux/tty_flip.h b/include/linux/tty_flip.h
index af4fce98f64e..2214714059f8 100644
--- a/include/linux/tty_flip.h
+++ b/include/linux/tty_flip.h
@@ -86,7 +86,7 @@  static inline size_t tty_insert_flip_string(struct tty_port *port,
 size_t tty_ldisc_receive_buf(struct tty_ldisc *ld, const u8 *p, const u8 *f,
 			     size_t count);
 
-void tty_buffer_lock_exclusive(struct tty_port *port);
-void tty_buffer_unlock_exclusive(struct tty_port *port);
+void tty_buffer_lock_exclusive(struct tty_port *port) __acquires(&port->buf.lock);
+void tty_buffer_unlock_exclusive(struct tty_port *port) __releases(&port->buf.lock);
 
 #endif /* _LINUX_TTY_FLIP_H */
diff --git a/include/linux/tty_ldisc.h b/include/linux/tty_ldisc.h
index af01e89074b2..d834cf115d52 100644
--- a/include/linux/tty_ldisc.h
+++ b/include/linux/tty_ldisc.h
@@ -14,7 +14,7 @@  struct tty_struct;
 /*
  * the semaphore definition
  */
-struct ld_semaphore {
+struct_with_capability(ld_semaphore) {
 	atomic_long_t		count;
 	raw_spinlock_t		wait_lock;
 	unsigned int		wait_readers;
@@ -33,21 +33,22 @@  do {								\
 	static struct lock_class_key __key;			\
 								\
 	__init_ldsem((sem), #sem, &__key);			\
+	__assert_cap(sem);					\
 } while (0)
 
 
-int ldsem_down_read(struct ld_semaphore *sem, long timeout);
-int ldsem_down_read_trylock(struct ld_semaphore *sem);
-int ldsem_down_write(struct ld_semaphore *sem, long timeout);
-int ldsem_down_write_trylock(struct ld_semaphore *sem);
-void ldsem_up_read(struct ld_semaphore *sem);
-void ldsem_up_write(struct ld_semaphore *sem);
+int ldsem_down_read(struct ld_semaphore *sem, long timeout) __cond_acquires_shared(true, sem);
+int ldsem_down_read_trylock(struct ld_semaphore *sem) __cond_acquires_shared(true, sem);
+int ldsem_down_write(struct ld_semaphore *sem, long timeout) __cond_acquires(true, sem);
+int ldsem_down_write_trylock(struct ld_semaphore *sem) __cond_acquires(true, sem);
+void ldsem_up_read(struct ld_semaphore *sem) __releases_shared(sem);
+void ldsem_up_write(struct ld_semaphore *sem) __releases(sem);
 
 #ifdef CONFIG_DEBUG_LOCK_ALLOC
 int ldsem_down_read_nested(struct ld_semaphore *sem, int subclass,
-		long timeout);
+		long timeout) __cond_acquires_shared(true, sem);
 int ldsem_down_write_nested(struct ld_semaphore *sem, int subclass,
-		long timeout);
+		long timeout) __cond_acquires(true, sem);
 #else
 # define ldsem_down_read_nested(sem, subclass, timeout)		\
 		ldsem_down_read(sem, timeout)