X-Git-Url: http://demsky.eecs.uci.edu/git/?a=blobdiff_plain;f=drivers%2Ftty%2Ftty_ldisc.c;h=1afe192bef6a7e8a795c91a3cf38a26615b6f2a1;hb=39b2f8656e2af4d5d490ce6e33e4ba229cda3e33;hp=77120911e01620f1c1f0c513181d54b74c8d2c7b;hpb=f4cf7a384587c16c59565dd6930dd763f243dba4;p=firefly-linux-kernel-4.4.55.git diff --git a/drivers/tty/tty_ldisc.c b/drivers/tty/tty_ldisc.c index 77120911e016..1afe192bef6a 100644 --- a/drivers/tty/tty_ldisc.c +++ b/drivers/tty/tty_ldisc.c @@ -20,6 +20,17 @@ #include #include +#undef LDISC_DEBUG_HANGUP + +#ifdef LDISC_DEBUG_HANGUP +#define tty_ldisc_debug(tty, f, args...) ({ \ + char __b[64]; \ + printk(KERN_DEBUG "%s: %s: " f, __func__, tty_name(tty, __b), ##args); \ +}) +#else +#define tty_ldisc_debug(tty, f, args...) +#endif + /* * This guards the refcounted line discipline lists. The lock * must be taken with irqs off because there are hangup path @@ -31,44 +42,6 @@ static DECLARE_WAIT_QUEUE_HEAD(tty_ldisc_wait); /* Line disc dispatch table */ static struct tty_ldisc_ops *tty_ldiscs[NR_LDISCS]; -static inline struct tty_ldisc *get_ldisc(struct tty_ldisc *ld) -{ - if (ld) - atomic_inc(&ld->users); - 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 @@ -206,6 +179,29 @@ static struct tty_ldisc *tty_ldisc_get(int disc) return ld; } +/** + * tty_ldisc_put - release the ldisc + * + * Complement of tty_ldisc_get(). + */ +static inline void tty_ldisc_put(struct tty_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); +} + static void *tty_ldiscs_seq_start(struct seq_file *m, loff_t *pos) { return (*pos < NR_LDISCS) ? pos : NULL; @@ -254,24 +250,6 @@ const struct file_operations tty_ldiscs_proc_fops = { .release = seq_release, }; -/** - * tty_ldisc_assign - set ldisc on a tty - * @tty: tty to assign - * @ld: line discipline - * - * Install an instance of a line discipline into a tty structure. The - * ldisc must have a reference count above zero to ensure it remains. - * The tty instance refcount starts at zero. - * - * Locking: - * Caller must hold references - */ - -static void tty_ldisc_assign(struct tty_struct *tty, struct tty_ldisc *ld) -{ - tty->ldisc = ld; -} - /** * tty_ldisc_try - internal helper * @tty: the tty @@ -289,10 +267,13 @@ static struct tty_ldisc *tty_ldisc_try(struct tty_struct *tty) unsigned long flags; struct tty_ldisc *ld; + /* FIXME: this allows reference acquire after TTY_LDISC is cleared */ raw_spin_lock_irqsave(&tty_ldisc_lock, flags); ld = NULL; - if (test_bit(TTY_LDISC, &tty->flags)) - ld = get_ldisc(tty->ldisc); + if (test_bit(TTY_LDISC, &tty->flags) && tty->ldisc) { + ld = tty->ldisc; + atomic_inc(&ld->users); + } raw_spin_unlock_irqrestore(&tty_ldisc_lock, flags); return ld; } @@ -352,14 +333,23 @@ EXPORT_SYMBOL_GPL(tty_ldisc_ref); void tty_ldisc_deref(struct tty_ldisc *ld) { - put_ldisc(ld); -} -EXPORT_SYMBOL_GPL(tty_ldisc_deref); + unsigned long flags; -static inline void tty_ldisc_put(struct tty_ldisc *ld) -{ - put_ldisc(ld); + 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_enable - allow ldisc use @@ -373,7 +363,7 @@ static inline void tty_ldisc_put(struct tty_ldisc *ld) * Clearing directly is allowed. */ -void tty_ldisc_enable(struct tty_struct *tty) +static void tty_ldisc_enable(struct tty_struct *tty) { clear_bit(TTY_LDISC_HALTED, &tty->flags); set_bit(TTY_LDISC, &tty->flags); @@ -480,7 +470,7 @@ static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old) /* There is an outstanding reference here so this is safe */ old = tty_ldisc_get(old->ops->num); WARN_ON(IS_ERR(old)); - tty_ldisc_assign(tty, old); + tty->ldisc = old; tty_set_termios_ldisc(tty, old->ops->num); if (tty_ldisc_open(tty, old) < 0) { tty_ldisc_put(old); @@ -488,7 +478,7 @@ static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old) new_ldisc = tty_ldisc_get(N_TTY); if (IS_ERR(new_ldisc)) panic("n_tty: get"); - tty_ldisc_assign(tty, new_ldisc); + tty->ldisc = new_ldisc; tty_set_termios_ldisc(tty, N_TTY); r = tty_ldisc_open(tty, new_ldisc); if (r < 0) @@ -498,19 +488,6 @@ static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old) } } -/** - * tty_ldisc_flush_works - flush all works of a tty - * @tty: tty device to flush works for - * - * Sync flush all works belonging to @tty. - */ -static void tty_ldisc_flush_works(struct tty_struct *tty) -{ - flush_work(&tty->hangup_work); - flush_work(&tty->SAK_work); - flush_work(&tty->port->buf.work); -} - /** * tty_ldisc_wait_idle - wait for the ldisc to become idle * @tty: tty to wait for @@ -531,20 +508,12 @@ static int tty_ldisc_wait_idle(struct tty_struct *tty, long timeout) * tty_ldisc_halt - shut down the line discipline * @tty: tty device * @o_tty: paired pty device (can be NULL) - * @pending: returns true if work was scheduled when cancelled - * (can be set to NULL) - * @o_pending: returns true if work was scheduled when cancelled - * (can be set to NULL) * @timeout: # of jiffies to wait for ldisc refs to be released * * Shut down the line discipline and work queue for this tty device and * its paired pty (if exists). Clearing the TTY_LDISC flag ensures - * no further references can be obtained while the work queue halt - * ensures that no more data is fed to the ldisc. - * - * Furthermore, guarantee that existing ldisc references have been - * released, which in turn, guarantees that no future buffer work - * can be rescheduled. + * no further references can be obtained, while waiting for existing + * references to be released ensures no more data is fed to the ldisc. * * You need to do a 'flush_scheduled_work()' (outside the ldisc_mutex) * in order to make sure any currently executing ldisc work is also @@ -552,9 +521,9 @@ static int tty_ldisc_wait_idle(struct tty_struct *tty, long timeout) */ static int tty_ldisc_halt(struct tty_struct *tty, struct tty_struct *o_tty, - int *pending, int *o_pending, long timeout) + long timeout) { - int scheduled, o_scheduled, retval; + int retval; clear_bit(TTY_LDISC, &tty->flags); if (o_tty) @@ -566,17 +535,10 @@ static int tty_ldisc_halt(struct tty_struct *tty, struct tty_struct *o_tty, if (retval) return retval; - scheduled = cancel_work_sync(&tty->port->buf.work); set_bit(TTY_LDISC_HALTED, &tty->flags); - if (o_tty) { - o_scheduled = cancel_work_sync(&o_tty->port->buf.work); + if (o_tty) set_bit(TTY_LDISC_HALTED, &o_tty->flags); - } - if (pending) - *pending = scheduled; - if (o_tty && o_pending) - *o_pending = o_scheduled; return 0; } @@ -586,17 +548,12 @@ static int tty_ldisc_halt(struct tty_struct *tty, struct tty_struct *o_tty, * * Shut down the line discipline and work queue for the tty device * being hungup. Clear the TTY_LDISC flag to ensure no further - * references can be obtained, wait for remaining references to be - * released, and cancel pending buffer work to ensure no more - * data is fed to this ldisc. + * references can be obtained and wait for remaining references to be + * released to ensure no more data is fed to this ldisc. * Caller must hold legacy and ->ldisc_mutex. * * NB: tty_set_ldisc() is prevented from changing the ldisc concurrently * with this function by checking the TTY_HUPPING flag. - * - * NB: if tty->ldisc is NULL then buffer work does not need to be - * cancelled because it must already have done as a precondition - * of closing the ldisc and setting tty->ldisc to NULL */ static bool tty_ldisc_hangup_halt(struct tty_struct *tty) { @@ -616,7 +573,6 @@ static bool tty_ldisc_hangup_halt(struct tty_struct *tty) tty_name(tty, tty_n)); } - cancel_work_sync(&tty->port->buf.work); set_bit(TTY_LDISC_HALTED, &tty->flags); /* must reacquire both locks and preserve lock order */ @@ -644,7 +600,6 @@ int tty_set_ldisc(struct tty_struct *tty, int ldisc) { int retval; struct tty_ldisc *o_ldisc, *new_ldisc; - int work, o_work = 0; struct tty_struct *o_tty; new_ldisc = tty_ldisc_get(ldisc); @@ -670,15 +625,6 @@ int tty_set_ldisc(struct tty_struct *tty, int ldisc) return 0; } - tty_unlock(tty); - /* - * Problem: What do we do if this blocks ? - * We could deadlock here - */ - - tty_wait_until_sent(tty, 0); - - tty_lock(tty); mutex_lock(&tty->ldisc_mutex); /* @@ -718,16 +664,16 @@ int tty_set_ldisc(struct tty_struct *tty, int ldisc) * parallel to the change and re-referencing the tty. */ - retval = tty_ldisc_halt(tty, o_tty, &work, &o_work, 5 * HZ); + retval = tty_ldisc_halt(tty, o_tty, 5 * HZ); /* - * Wait for ->hangup_work and ->buf.work handlers to terminate. + * Wait for hangup to complete, if pending. * We must drop the mutex here in case a hangup is also in process. */ mutex_unlock(&tty->ldisc_mutex); - tty_ldisc_flush_works(tty); + flush_work(&tty->hangup_work); tty_lock(tty); mutex_lock(&tty->ldisc_mutex); @@ -752,7 +698,7 @@ int tty_set_ldisc(struct tty_struct *tty, int ldisc) tty_ldisc_close(tty, o_ldisc); /* Now set up the new line discipline. */ - tty_ldisc_assign(tty, new_ldisc); + tty->ldisc = new_ldisc; tty_set_termios_ldisc(tty, ldisc); retval = tty_ldisc_open(tty, new_ldisc); @@ -782,10 +728,10 @@ enable: /* Restart the work queue in case no characters kick it off. Safe if already running */ - if (work) - schedule_work(&tty->port->buf.work); - if (o_work) + schedule_work(&tty->port->buf.work); + if (o_tty) schedule_work(&o_tty->port->buf.work); + mutex_unlock(&tty->ldisc_mutex); tty_unlock(tty); return retval; @@ -826,11 +772,10 @@ static int tty_ldisc_reinit(struct tty_struct *tty, int ldisc) tty_ldisc_close(tty, tty->ldisc); tty_ldisc_put(tty->ldisc); - tty->ldisc = NULL; /* * Switch the line discipline back */ - tty_ldisc_assign(tty, ld); + tty->ldisc = ld; tty_set_termios_ldisc(tty, ldisc); return 0; @@ -857,6 +802,8 @@ void tty_ldisc_hangup(struct tty_struct *tty) int reset = tty->driver->flags & TTY_DRIVER_RESET_TERMIOS; int err = 0; + tty_ldisc_debug(tty, "closing ldisc: %p\n", tty->ldisc); + /* * FIXME! What are the locking issues here? This may me overdoing * things... This question is especially important now that we've @@ -889,11 +836,12 @@ void tty_ldisc_hangup(struct tty_struct *tty) */ mutex_lock(&tty->ldisc_mutex); - /* At this point we have a closed ldisc and we want to - reopen it. We could defer this to the next open but - it means auditing a lot of other paths so this is - a FIXME */ if (tty_ldisc_hangup_halt(tty)) { + + /* At this point we have a halted ldisc; we want to close it and + reopen a new ldisc. We could defer the reopen to the next + open but it means auditing a lot of other paths so this is + a FIXME */ if (reset == 0) { if (!tty_ldisc_reinit(tty, tty->termios.c_line)) @@ -912,6 +860,8 @@ void tty_ldisc_hangup(struct tty_struct *tty) mutex_unlock(&tty->ldisc_mutex); if (reset) tty_reset_termios(tty); + + tty_ldisc_debug(tty, "re-opened ldisc: %p\n", tty->ldisc); } /** @@ -974,15 +924,13 @@ static void tty_ldisc_kill(struct tty_struct *tty) void tty_ldisc_release(struct tty_struct *tty, struct tty_struct *o_tty) { /* - * Prevent flush_to_ldisc() from rescheduling the work for later. Then - * kill any delayed work. As this is the final close it does not - * race with the set_ldisc code path. + * Shutdown this line discipline. As this is the final close, + * it does not race with the set_ldisc code path. */ - tty_ldisc_halt(tty, o_tty, NULL, NULL, MAX_SCHEDULE_TIMEOUT); - tty_ldisc_flush_works(tty); - if (o_tty) - tty_ldisc_flush_works(o_tty); + tty_ldisc_debug(tty, "closing ldisc: %p\n", tty->ldisc); + + tty_ldisc_halt(tty, o_tty, MAX_SCHEDULE_TIMEOUT); tty_lock_pair(tty, o_tty); /* This will need doing differently if we need to lock */ @@ -993,6 +941,8 @@ void tty_ldisc_release(struct tty_struct *tty, struct tty_struct *o_tty) tty_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, "ldisc closed\n"); } /** @@ -1008,7 +958,7 @@ void tty_ldisc_init(struct tty_struct *tty) struct tty_ldisc *ld = tty_ldisc_get(N_TTY); if (IS_ERR(ld)) panic("n_tty: init_tty"); - tty_ldisc_assign(tty, ld); + tty->ldisc = ld; } /** @@ -1020,8 +970,8 @@ void tty_ldisc_init(struct tty_struct *tty) */ void tty_ldisc_deinit(struct tty_struct *tty) { - put_ldisc(tty->ldisc); - tty_ldisc_assign(tty, NULL); + tty_ldisc_put(tty->ldisc); + tty->ldisc = NULL; } void tty_ldisc_begin(void)