Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ebc9baed authored by Peter Hurley's avatar Peter Hurley Committed by Greg Kroah-Hartman
Browse files

tty: Separate release semantics of ldisc reference



tty_ldisc_ref()/tty_ldisc_unref() have usage semantics
equivalent to down_read_trylock()/up_read(). Only
callers of tty_ldisc_put() are performing the additional
operations necessary for proper ldisc teardown, and then only
after ensuring no outstanding 'read lock' remains.

Thus, tty_ldisc_unref() should never be the last reference;
WARN if it is. Conversely, tty_ldisc_put() should never be
destructing if the use count != 1.

Signed-off-by: default avatarPeter Hurley <peter@hurleysoftware.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 8842dda2
Loading
Loading
Loading
Loading
+35 −34
Original line number Diff line number Diff line
@@ -49,37 +49,6 @@ static inline struct tty_ldisc *get_ldisc(struct tty_ldisc *ld)
	return ld;
}

static void put_ldisc(struct tty_ldisc *ld)
{
	unsigned long flags;

	if (WARN_ON_ONCE(!ld))
		return;

	/*
	 * If this is the last user, free the ldisc, and
	 * release the ldisc ops.
	 *
	 * We really want an "atomic_dec_and_raw_lock_irqsave()",
	 * but we don't have it, so this does it by hand.
	 */
	raw_spin_lock_irqsave(&tty_ldisc_lock, flags);
	if (atomic_dec_and_test(&ld->users)) {
		struct tty_ldisc_ops *ldo = ld->ops;

		ldo->refcount--;
		module_put(ldo->owner);
		raw_spin_unlock_irqrestore(&tty_ldisc_lock, flags);

		kfree(ld);
		return;
	}
	raw_spin_unlock_irqrestore(&tty_ldisc_lock, flags);

	if (waitqueue_active(&ld->wq_idle))
		wake_up(&ld->wq_idle);
}

/**
 *	tty_register_ldisc	-	install a line discipline
 *	@disc: ldisc number
@@ -363,13 +332,45 @@ EXPORT_SYMBOL_GPL(tty_ldisc_ref);

void tty_ldisc_deref(struct tty_ldisc *ld)
{
	put_ldisc(ld);
	unsigned long flags;

	if (WARN_ON_ONCE(!ld))
		return;

	raw_spin_lock_irqsave(&tty_ldisc_lock, flags);
	/*
	 * WARNs if one-too-many reader references were released
	 * - the last reference must be released with tty_ldisc_put
	 */
	WARN_ON(atomic_dec_and_test(&ld->users));
	raw_spin_unlock_irqrestore(&tty_ldisc_lock, flags);

	if (waitqueue_active(&ld->wq_idle))
		wake_up(&ld->wq_idle);
}
EXPORT_SYMBOL_GPL(tty_ldisc_deref);

/**
 *	tty_ldisc_put		-	release the ldisc
 *
 *	Complement of tty_ldisc_get().
 */
static inline void tty_ldisc_put(struct tty_ldisc *ld)
{
	put_ldisc(ld);
	unsigned long flags;

	if (WARN_ON_ONCE(!ld))
		return;

	raw_spin_lock_irqsave(&tty_ldisc_lock, flags);

	/* unreleased reader reference(s) will cause this WARN */
	WARN_ON(!atomic_dec_and_test(&ld->users));

	ld->ops->refcount--;
	module_put(ld->ops->owner);
	kfree(ld);
	raw_spin_unlock_irqrestore(&tty_ldisc_lock, flags);
}

/**
@@ -1001,7 +1002,7 @@ void tty_ldisc_init(struct tty_struct *tty)
 */
void tty_ldisc_deinit(struct tty_struct *tty)
{
	put_ldisc(tty->ldisc);
	tty_ldisc_put(tty->ldisc);
	tty_ldisc_assign(tty, NULL);
}