static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old) { char buf[64]; struct tty_ldisc *new_ldisc; int r; /* There is an outstanding reference here so this is safe */ old = tty_ldisc_get(tty, old->ops->num); WARN_ON(IS_ERR(old)); tty->ldisc = old; tty_set_termios_ldisc(tty, old->ops->num); if (tty_ldisc_open(tty, old) < 0) { tty_ldisc_put(old); /* This driver is always present */ new_ldisc = tty_ldisc_get(tty, N_TTY); if (IS_ERR(new_ldisc)) panic("n_tty: get"); tty->ldisc = new_ldisc; tty_set_termios_ldisc(tty, N_TTY); r = tty_ldisc_open(tty, new_ldisc); if (r < 0) panic("Couldn't open N_TTY ldisc for " "%s --- error %d.", tty_name(tty, buf), 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) { int ret; struct tty_ldisc *new_ldisc; /* There is an outstanding reference here so this is safe */ old = tty_ldisc_get(tty, old->ops->num); WARN_ON(IS_ERR(old)); tty->ldisc = old; tty_set_termios_ldisc(tty, old->ops->num); if (tty_ldisc_open(tty, old) < 0) { tty_ldisc_put(old); /* This driver is always present */ new_ldisc = tty_ldisc_get(tty, N_TTY); if (IS_ERR(new_ldisc)) panic("n_tty: get"); tty->ldisc = new_ldisc; tty_set_termios_ldisc(tty, N_TTY); ret = tty_ldisc_open(tty, new_ldisc); if (ret < 0) panic("n_tty: open"); } }
void tty_ldisc_hangup(struct tty_struct *tty) { struct tty_ldisc *ld; int reset = tty->driver->flags & TTY_DRIVER_RESET_TERMIOS; int err = 0; /* * FIXME! What are the locking issues here? This may me overdoing * things... This question is especially important now that we've * removed the irqlock. */ ld = tty_ldisc_ref(tty); if (ld != NULL) { /* We may have no line discipline at this point */ 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); } /* * FIXME: Once we trust the LDISC code better we can wait here for * ldisc completion and fix the driver call race */ wake_up_interruptible_poll(&tty->write_wait, POLLOUT); wake_up_interruptible_poll(&tty->read_wait, POLLIN); /* * Shutdown the current line discipline, and reset it to * N_TTY if need be. * * Avoid racing set_ldisc or tty_ldisc_release */ mutex_lock(&tty->ldisc_mutex); tty_ldisc_halt(tty); /* 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) { /* Not yet closed */ if (reset == 0) { tty_ldisc_reinit(tty, tty->termios->c_line); err = tty_ldisc_open(tty, tty->ldisc); } /* If the re-open fails or we reset then go to N_TTY. The N_TTY open cannot fail */ if (reset || err) { tty_ldisc_reinit(tty, N_TTY); WARN_ON(tty_ldisc_open(tty, tty->ldisc)); } tty_ldisc_enable(tty); } mutex_unlock(&tty->ldisc_mutex); if (reset) tty_reset_termios(tty); }
int tty_ldisc_setup(struct tty_struct *tty, struct tty_struct *o_tty) { struct tty_ldisc *ld = tty->ldisc; int retval; retval = tty_ldisc_open(tty, ld); if (retval) return retval; if (o_tty) { retval = tty_ldisc_open(o_tty, o_tty->ldisc); if (retval) { tty_ldisc_close(tty, ld); return retval; } } return 0; }
/** * tty_ldisc_change - change line discipline * @tty: tty to change * @disc: new line discipine number * * Change the line discipline of the tty. The tty must have an old * ldisc attached to, we must handle it properly. Sandix implement * it quite simple, but please look at linux, see how complex it is! * XXX tty shoud be locked */ int tty_ldisc_change(struct tty_struct *tty, int disc) { int ret; struct tty_ldisc *old_ldisc, *new_ldisc; BUG_ON(!tty->ldisc); if (tty->ldisc->ops->num == disc) return 0; tty_lock(tty); new_ldisc = tty_ldisc_get(tty, disc); if (IS_ERR(new_ldisc)) { tty_unlock(tty); return PTR_ERR(new_ldisc); } old_ldisc = tty->ldisc; /* Close the old one */ tty_ldisc_close(tty, old_ldisc); /* Set up the new one */ tty->ldisc = new_ldisc; tty_set_termios_ldisc(tty, disc); ret = tty_ldisc_open(tty, new_ldisc); if (ret) { /* Back to the old one or N_TTY */ tty_ldisc_put(new_ldisc); tty_ldisc_restore(tty, old_ldisc); } /* * 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. */ tty_ldisc_put(old_ldisc); tty_unlock(tty); return ret; }
void tty_ldisc_hangup(struct tty_struct *tty) { struct tty_ldisc *ld; int reset = tty->driver->flags & TTY_DRIVER_RESET_TERMIOS; int err = 0; tty_ldisc_debug(tty, "closing ldisc: %p\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, POLLOUT); wake_up_interruptible_poll(&tty->read_wait, POLLIN); tty_unlock(tty); /* * 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_pair(tty, tty->link); tty_lock(tty); if (tty->ldisc) { /* 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)) err = tty_ldisc_open(tty, tty->ldisc); else err = 1; } /* If the re-open fails or we reset then go to N_TTY. The N_TTY open cannot fail */ if (reset || err) { BUG_ON(tty_ldisc_reinit(tty, N_TTY)); WARN_ON(tty_ldisc_open(tty, tty->ldisc)); } } tty_ldisc_enable_pair(tty, tty->link); if (reset) tty_reset_termios(tty); tty_ldisc_debug(tty, "re-opened ldisc: %p\n", tty->ldisc); }
int tty_set_ldisc(struct tty_struct *tty, int ldisc) { int retval; struct tty_ldisc *old_ldisc, *new_ldisc; struct tty_struct *o_tty = tty->link; new_ldisc = tty_ldisc_get(tty, ldisc); if (IS_ERR(new_ldisc)) return PTR_ERR(new_ldisc); retval = tty_ldisc_lock_pair_timeout(tty, o_tty, 5 * HZ); if (retval) { tty_ldisc_put(new_ldisc); return retval; } /* * Check the no-op case */ if (tty->ldisc->ops->num == ldisc) { tty_ldisc_enable_pair(tty, o_tty); tty_ldisc_put(new_ldisc); return 0; } old_ldisc = tty->ldisc; tty_lock(tty); if (test_bit(TTY_HUPPING, &tty->flags) || test_bit(TTY_HUPPED, &tty->flags)) { /* We were raced by the hangup method. It will have stomped the ldisc data and closed the ldisc down */ tty_ldisc_enable_pair(tty, o_tty); tty_ldisc_put(new_ldisc); tty_unlock(tty); return -EIO; } /* 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, ldisc); 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) tty->ops->set_ldisc(tty); /* 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. */ tty_ldisc_put(old_ldisc); /* * Allow ldisc referencing to occur again */ tty_ldisc_enable_pair(tty, o_tty); /* Restart the work queue in case no characters kick it off. Safe if already running */ schedule_work(&tty->port->buf.work); if (o_tty) schedule_work(&o_tty->port->buf.work); tty_unlock(tty); return retval; }
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); if (IS_ERR(new_ldisc)) return PTR_ERR(new_ldisc); /* * We need to look at the tty locking here for pty/tty pairs * when both sides try to change in parallel. */ o_tty = tty->link; /* o_tty is the pty side or NULL */ /* * Check the no-op case */ if (tty->ldisc->ops->num == ldisc) { tty_ldisc_put(new_ldisc); return 0; } /* * Problem: What do we do if this blocks ? * We could deadlock here */ tty_wait_until_sent(tty, 0); mutex_lock(&tty->ldisc_mutex); /* * We could be midstream of another ldisc change which has * dropped the lock during processing. If so we need to wait. */ while (test_bit(TTY_LDISC_CHANGING, &tty->flags)) { mutex_unlock(&tty->ldisc_mutex); wait_event(tty_ldisc_wait, test_bit(TTY_LDISC_CHANGING, &tty->flags) == 0); mutex_lock(&tty->ldisc_mutex); } set_bit(TTY_LDISC_CHANGING, &tty->flags); /* * No more input please, we are switching. The new ldisc * will update this value in the ldisc open function */ tty->receive_room = 0; o_ldisc = tty->ldisc; /* * Make sure we don't change while someone holds a * reference to the line discipline. The TTY_LDISC bit * prevents anyone taking a reference once it is clear. * We need the lock to avoid racing reference takers. * * We must clear the TTY_LDISC bit here to avoid a livelock * with a userspace app continually trying to use the tty in * parallel to the change and re-referencing the tty. */ work = tty_ldisc_halt(tty); if (o_tty) o_work = tty_ldisc_halt(o_tty); /* * Wait for ->hangup_work and ->buf.work handlers to terminate. * We must drop the mutex here in case a hangup is also in process. */ mutex_unlock(&tty->ldisc_mutex); flush_scheduled_work(); mutex_lock(&tty->ldisc_mutex); if (test_bit(TTY_HUPPED, &tty->flags)) { /* We were raced by the hangup method. It will have stomped the ldisc data and closed the ldisc down */ clear_bit(TTY_LDISC_CHANGING, &tty->flags); mutex_unlock(&tty->ldisc_mutex); tty_ldisc_put(new_ldisc); return -EIO; } /* Shutdown the current discipline. */ tty_ldisc_close(tty, o_ldisc); /* Now set up the new line discipline. */ tty_ldisc_assign(tty, new_ldisc); tty_set_termios_ldisc(tty, ldisc); 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, o_ldisc); } /* At this point we hold a reference to the new ldisc and a a reference to the old ldisc. If we ended up flipping back to the existing ldisc we have two references to it */ if (tty->ldisc->ops->num != o_ldisc->ops->num && tty->ops->set_ldisc) tty->ops->set_ldisc(tty); tty_ldisc_put(o_ldisc); /* * Allow ldisc referencing to occur again */ tty_ldisc_enable(tty); if (o_tty) tty_ldisc_enable(o_tty); /* Restart the work queue in case no characters kick it off. Safe if already running */ if (work) schedule_delayed_work(&tty->buf.work, 1); if (o_work) schedule_delayed_work(&o_tty->buf.work, 1); mutex_unlock(&tty->ldisc_mutex); return retval; }
void tty_ldisc_hangup(struct tty_struct *tty) { struct tty_ldisc *ld; int reset = tty->driver->flags & TTY_DRIVER_RESET_TERMIOS; int err = 0; /* * FIXME! What are the locking issues here? This may me overdoing * things... This question is especially important now that we've * removed the irqlock. */ ld = tty_ldisc_ref(tty); if (ld != NULL) { /* We may have no line discipline at this point */ 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); } /* * FIXME: Once we trust the LDISC code better we can wait here for * ldisc completion and fix the driver call race */ wake_up_interruptible_poll(&tty->write_wait, POLLOUT); wake_up_interruptible_poll(&tty->read_wait, POLLIN); /* * Shutdown the current line discipline, and reset it to * N_TTY if need be. * * Avoid racing set_ldisc or tty_ldisc_release */ mutex_lock(&tty->ldisc_mutex); /* * this is like tty_ldisc_halt, but we need to give up * the BTM before calling cancel_work_sync, which may * need to wait for another function taking the BTM */ clear_bit(TTY_LDISC, &tty->flags); tty_unlock(tty); cancel_work_sync(&tty->port->buf.work); mutex_unlock(&tty->ldisc_mutex); retry: tty_lock(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) { /* Not yet closed */ if (atomic_read(&tty->ldisc->users) != 1) { char cur_n[TASK_COMM_LEN], tty_n[64]; long timeout = 3 * HZ; tty_unlock(tty); while (tty_ldisc_wait_idle(tty, timeout) == -EBUSY) { timeout = MAX_SCHEDULE_TIMEOUT; printk_ratelimited(KERN_WARNING "%s: waiting (%s) for %s took too long, but we keep waiting...\n", __func__, get_task_comm(cur_n, current), tty_name(tty, tty_n)); } mutex_unlock(&tty->ldisc_mutex); goto retry; } if (reset == 0) { if (!tty_ldisc_reinit(tty, tty->termios.c_line)) err = tty_ldisc_open(tty, tty->ldisc); else err = 1; } /* If the re-open fails or we reset then go to N_TTY. The N_TTY open cannot fail */ if (reset || err) { BUG_ON(tty_ldisc_reinit(tty, N_TTY)); WARN_ON(tty_ldisc_open(tty, tty->ldisc)); } tty_ldisc_enable(tty); } mutex_unlock(&tty->ldisc_mutex); if (reset) tty_reset_termios(tty); }
void tty_ldisc_hangup(struct tty_struct *tty) { struct tty_ldisc *ld; int reset = tty->driver->flags & TTY_DRIVER_RESET_TERMIOS; int err = 0; ld = tty_ldisc_ref(tty); if (ld != NULL) { /* We may have no line discipline at this point */ 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, POLLOUT); wake_up_interruptible_poll(&tty->read_wait, POLLIN); /* * Shutdown the current line discipline, and reset it to * N_TTY if need be. * * Avoid racing set_ldisc or tty_ldisc_release */ mutex_lock(&tty->ldisc_mutex); /* * this is like tty_ldisc_halt, but we need to give up * the BTM before calling cancel_delayed_work_sync, * which may need to wait for another function taking the BTM */ clear_bit(TTY_LDISC, &tty->flags); tty_unlock(); cancel_delayed_work_sync(&tty->buf.work); mutex_unlock(&tty->ldisc_mutex); tty_lock(); mutex_lock(&tty->ldisc_mutex); if (tty->ldisc) { /* Not yet closed */ if (reset == 0) { if (!tty_ldisc_reinit(tty, tty->termios->c_line)) err = tty_ldisc_open(tty, tty->ldisc); else err = 1; } /* If the re-open fails or we reset then go to N_TTY. The N_TTY open cannot fail */ if (reset || err) { BUG_ON(tty_ldisc_reinit(tty, N_TTY)); WARN_ON(tty_ldisc_open(tty, tty->ldisc)); } tty_ldisc_enable(tty); } mutex_unlock(&tty->ldisc_mutex); if (reset) tty_reset_termios(tty); }
int tty_set_ldisc(struct tty_struct *tty, int ldisc) { int retval; struct tty_ldisc *o_ldisc, *new_ldisc; struct tty_struct *o_tty = tty->link; new_ldisc = tty_ldisc_get(tty, ldisc); if (IS_ERR(new_ldisc)) return PTR_ERR(new_ldisc); retval = tty_ldisc_lock_pair_timeout(tty, o_tty, 5 * HZ); if (retval) { tty_ldisc_put(new_ldisc); return retval; } /* * Check the no-op case */ if (tty->ldisc->ops->num == ldisc) { tty_ldisc_enable_pair(tty, o_tty); tty_ldisc_put(new_ldisc); return 0; } /* FIXME: why 'shutoff' input if the ldisc is locked? */ tty->receive_room = 0; o_ldisc = tty->ldisc; tty_lock(tty); /* FIXME: for testing only */ WARN_ON(test_bit(TTY_HUPPED, &tty->flags)); if (test_bit(TTY_HUPPING, &tty->flags)) { /* We were raced by the hangup method. It will have stomped the ldisc data and closed the ldisc down */ tty_ldisc_enable_pair(tty, o_tty); tty_ldisc_put(new_ldisc); tty_unlock(tty); return -EIO; } /* Shutdown the current discipline. */ tty_ldisc_close(tty, o_ldisc); /* Now set up the new line discipline. */ tty->ldisc = new_ldisc; tty_set_termios_ldisc(tty, ldisc); 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, o_ldisc); } /* At this point we hold a reference to the new ldisc and a a reference to the old ldisc. If we ended up flipping back to the existing ldisc we have two references to it */ if (tty->ldisc->ops->num != o_ldisc->ops->num && tty->ops->set_ldisc) tty->ops->set_ldisc(tty); tty_ldisc_put(o_ldisc); /* * Allow ldisc referencing to occur again */ tty_ldisc_enable_pair(tty, o_tty); /* Restart the work queue in case no characters kick it off. Safe if already running */ schedule_work(&tty->port->buf.work); if (o_tty) schedule_work(&o_tty->port->buf.work); tty_unlock(tty); return retval; }