diff --git a/sys/dev/cons.c b/sys/dev/cons.c index ee5b15ec2d55..f2c59099b39b 100644 --- a/sys/dev/cons.c +++ b/sys/dev/cons.c @@ -1,4 +1,4 @@ -/* $NetBSD: cons.c,v 1.90 2022/10/07 18:59:37 riastradh Exp $ */ +/* $NetBSD: cons.c,v 1.91 2022/10/25 23:21:13 riastradh Exp $ */ /* * Copyright (c) 1988 University of Utah. @@ -39,7 +39,7 @@ */ #include -__KERNEL_RCSID(0, "$NetBSD: cons.c,v 1.90 2022/10/07 18:59:37 riastradh Exp $"); +__KERNEL_RCSID(0, "$NetBSD: cons.c,v 1.91 2022/10/25 23:21:13 riastradh Exp $"); #include #include @@ -54,6 +54,8 @@ __KERNEL_RCSID(0, "$NetBSD: cons.c,v 1.90 2022/10/07 18:59:37 riastradh Exp $"); #include #include #include +#include +#include #include @@ -67,7 +69,8 @@ dev_type_ioctl(cnioctl); dev_type_poll(cnpoll); dev_type_kqfilter(cnkqfilter); -static bool cn_redirect(dev_t *, int, int *); +static bool cn_redirect(dev_t *, int, int *, struct tty **); +static void cn_release(struct tty *); const struct cdevsw cons_cdevsw = { .d_open = cnopen, @@ -86,7 +89,7 @@ const struct cdevsw cons_cdevsw = { static struct kmutex cn_lock; -struct tty *constty = NULL; /* virtual console output device */ +struct tty *volatile constty; /* virtual console output device */ struct consdev *cn_tab; /* physical console device info */ struct vnode *cn_devvp[2]; /* vnode for underlying device. */ @@ -199,6 +202,7 @@ out: mutex_exit(&cn_lock); int cnread(dev_t dev, struct uio *uio, int flag) { + struct tty *ctp = NULL; int error; /* @@ -208,25 +212,31 @@ cnread(dev_t dev, struct uio *uio, int flag) * input (except a shell in single-user mode, but then, * one wouldn't TIOCCONS then). */ - if (!cn_redirect(&dev, 1, &error)) + if (!cn_redirect(&dev, 1, &error, &ctp)) return error; - return cdev_read(dev, uio, flag); + error = cdev_read(dev, uio, flag); + cn_release(ctp); + return error; } int cnwrite(dev_t dev, struct uio *uio, int flag) { + struct tty *ctp = NULL; int error; /* Redirect output, if that's appropriate. */ - if (!cn_redirect(&dev, 0, &error)) + if (!cn_redirect(&dev, 0, &error, &ctp)) return error; - return cdev_write(dev, uio, flag); + error = cdev_write(dev, uio, flag); + cn_release(ctp); + return error; } int cnioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l) { + struct tty *ctp = NULL; int error; error = 0; @@ -235,29 +245,41 @@ cnioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l) * Superuser can always use this to wrest control of console * output from the "virtual" console. */ - if (cmd == TIOCCONS && constty != NULL) { - error = kauth_authorize_device_tty(l->l_cred, - KAUTH_DEVICE_TTY_VIRTUAL, constty); - if (!error) - constty = NULL; - return (error); - } + if (cmd == TIOCCONS) { + struct tty *tp; + mutex_enter(&constty_lock); + tp = atomic_load_relaxed(&constty); + if (tp == NULL) { + mutex_exit(&constty_lock); + goto passthrough; /* XXX ??? */ + } + error = kauth_authorize_device_tty(l->l_cred, + KAUTH_DEVICE_TTY_VIRTUAL, tp); + if (!error) + atomic_store_relaxed(&constty, NULL); + mutex_exit(&constty_lock); + return error; + } +passthrough: /* * Redirect the ioctl, if that's appropriate. * Note that strange things can happen, if a program does * ioctls on /dev/console, then the console is redirected * out from under it. */ - if (!cn_redirect(&dev, 0, &error)) + if (!cn_redirect(&dev, 0, &error, &ctp)) return error; - return cdev_ioctl(dev, cmd, data, flag, l); + error = cdev_ioctl(dev, cmd, data, flag, l); + cn_release(ctp); + return error; } /*ARGSUSED*/ int cnpoll(dev_t dev, int events, struct lwp *l) { + struct tty *ctp = NULL; int error; /* @@ -265,15 +287,18 @@ cnpoll(dev_t dev, int events, struct lwp *l) * I don't want to think of the possible side effects * of console redirection here. */ - if (!cn_redirect(&dev, 0, &error)) + if (!cn_redirect(&dev, 0, &error, &ctp)) return POLLHUP; - return cdev_poll(dev, events, l); + error = cdev_poll(dev, events, l); + cn_release(ctp); + return error; } /*ARGSUSED*/ int cnkqfilter(dev_t dev, struct knote *kn) { + struct tty *ctp = NULL; int error; /* @@ -281,9 +306,11 @@ cnkqfilter(dev_t dev, struct knote *kn) * I don't want to think of the possible side effects * of console redirection here. */ - if (!cn_redirect(&dev, 0, &error)) + if (!cn_redirect(&dev, 0, &error, &ctp)) return error; - return cdev_kqfilter(dev, kn); + error = cdev_kqfilter(dev, kn); + cn_release(ctp); + return error; } int @@ -429,28 +456,44 @@ cnhalt(void) /* * Redirect output, if that's appropriate. If there's no real console, * return ENXIO. - * - * Call with tty_mutex held. */ static bool -cn_redirect(dev_t *devp, int is_read, int *error) +cn_redirect(dev_t *devp, int is_read, int *error, struct tty **ctpp) { dev_t dev = *devp; + struct tty *ctp; + int s; + bool ok = false; *error = ENXIO; - if (constty != NULL && minor(dev) == 0 && + *ctpp = NULL; + s = pserialize_read_enter(); + if ((ctp = atomic_load_consume(&constty)) != NULL && minor(dev) == 0 && (cn_tab == NULL || (cn_tab->cn_pri != CN_REMOTE))) { if (is_read) { *error = 0; - return false; + goto out; } - dev = constty->t_dev; + tty_acquire(ctp); + *ctpp = ctp; + dev = ctp->t_dev; } else if (cn_tab == NULL) - return false; + goto out; else dev = cn_tab->cn_dev; + ok = true; *devp = dev; - return true; +out: pserialize_read_exit(s); + return ok; +} + +static void +cn_release(struct tty *ctp) +{ + + if (ctp == NULL) + return; + tty_release(ctp); } MODULE(MODULE_CLASS_DRIVER, cons, NULL); diff --git a/sys/kern/subr_prf.c b/sys/kern/subr_prf.c index c3fa75325129..9ad6f019c1f3 100644 --- a/sys/kern/subr_prf.c +++ b/sys/kern/subr_prf.c @@ -1,4 +1,4 @@ -/* $NetBSD: subr_prf.c,v 1.193 2022/10/07 18:59:37 riastradh Exp $ */ +/* $NetBSD: subr_prf.c,v 1.194 2022/10/25 23:21:13 riastradh Exp $ */ /*- * Copyright (c) 1986, 1988, 1991, 1993 @@ -37,7 +37,7 @@ */ #include -__KERNEL_RCSID(0, "$NetBSD: subr_prf.c,v 1.193 2022/10/07 18:59:37 riastradh Exp $"); +__KERNEL_RCSID(0, "$NetBSD: subr_prf.c,v 1.194 2022/10/25 23:21:13 riastradh Exp $"); #ifdef _KERNEL_OPT #include "opt_ddb.h" @@ -103,7 +103,6 @@ static void kprintf_internal(const char *, int, void *, char *, ...); * globals */ -extern struct tty *constty; /* pointer to console "window" tty */ extern int log_open; /* subr_log: is /dev/klog open? */ const char *panicstr; /* arg to first call to panic (used as a flag to indicate that panic has already been called). */ @@ -401,22 +400,35 @@ addlog(const char *fmt, ...) static void putone(int c, int flags, struct tty *tp) { - if (panicstr) - constty = NULL; + struct tty *ctp; + int s; - if ((flags & TOCONS) && tp == NULL && constty) { - tp = constty; + /* + * Ensure whatever constty points to can't go away while we're + * trying to use it. + */ + s = pserialize_read_enter(); + + if (panicstr) + atomic_store_relaxed(&constty, NULL); + + if ((flags & TOCONS) && + (ctp = atomic_load_consume(&constty)) != NULL && + tp == NULL) { + tp = ctp; flags |= TOTTY; } if ((flags & TOTTY) && tp && tputchar(c, flags, tp) < 0 && - (flags & TOCONS) && tp == constty) - constty = NULL; + (flags & TOCONS)) + atomic_cas_ptr(&constty, tp, NULL); if ((flags & TOLOG) && c != '\0' && c != '\r' && c != 0177) logputchar(c); - if ((flags & TOCONS) && constty == NULL && c != '\0') + if ((flags & TOCONS) && ctp == NULL && c != '\0') (*v_putc)(c); + + pserialize_read_exit(s); } static void diff --git a/sys/kern/tty.c b/sys/kern/tty.c index 4c81ced32837..1302996b7a3a 100644 --- a/sys/kern/tty.c +++ b/sys/kern/tty.c @@ -1,4 +1,4 @@ -/* $NetBSD: tty.c,v 1.305 2022/10/07 18:59:37 riastradh Exp $ */ +/* $NetBSD: tty.c,v 1.306 2022/10/25 23:21:13 riastradh Exp $ */ /*- * Copyright (c) 2008, 2020 The NetBSD Foundation, Inc. @@ -63,7 +63,7 @@ */ #include -__KERNEL_RCSID(0, "$NetBSD: tty.c,v 1.305 2022/10/07 18:59:37 riastradh Exp $"); +__KERNEL_RCSID(0, "$NetBSD: tty.c,v 1.306 2022/10/25 23:21:13 riastradh Exp $"); #ifdef _KERNEL_OPT #include "opt_compat_netbsd.h" @@ -99,6 +99,9 @@ __KERNEL_RCSID(0, "$NetBSD: tty.c,v 1.305 2022/10/07 18:59:37 riastradh Exp $"); #include #include #include +#include +#include +#include #ifdef COMPAT_60 #include @@ -209,6 +212,9 @@ static void *tty_sigsih; struct ttylist_head ttylist = TAILQ_HEAD_INITIALIZER(ttylist); int tty_count; kmutex_t tty_lock; +kmutex_t constty_lock; +static struct pserialize *constty_psz; +static kcondvar_t ttyref_cv; struct ptm_pty *ptm = NULL; @@ -442,13 +448,28 @@ ttycancel(struct tty *tp) int ttyclose(struct tty *tp) { - extern struct tty *constty; /* Temporary virtual console. */ struct session *sess; - mutex_spin_enter(&tty_lock); + /* + * Make sure this is not the constty. Without constty_lock it + * is always allowed to transition from nonnull to null. + */ + (void)atomic_cas_ptr(&constty, tp, NULL); - if (constty == tp) - constty = NULL; + /* + * We don't know if this has _ever_ been the constty: another + * thread may have kicked it out as constty before we started + * to close. + * + * So we wait for all users that might be acquiring references + * to finish doing so -- after that, no more references can be + * made, at which point we can safely flush the tty, wait for + * the existing references to drain, and finally free or reuse + * the tty. + */ + pserialize_perform(constty_psz); + + mutex_spin_enter(&tty_lock); ttyflush(tp, FREAD | FWRITE); @@ -458,6 +479,9 @@ ttyclose(struct tty *tp) sess = tp->t_session; tp->t_session = NULL; + while (tp->t_refcnt) + cv_wait(&ttyref_cv, &tty_lock); + mutex_spin_exit(&tty_lock); if (sess != NULL) { @@ -473,6 +497,44 @@ ttyclose(struct tty *tp) ndflush(q, (q)->c_cc); \ } +/* + * tty_acquire(tp), tty_release(tp) + * + * Acquire a reference to tp that prevents it from being closed + * until released. Caller must guarantee tp has not yet been + * closed, e.g. by obtaining tp from constty during a pserialize + * read section. Caller must not hold tty_lock. + */ +void +tty_acquire(struct tty *tp) +{ + unsigned refcnt __diagused; + + refcnt = atomic_inc_uint_nv(&tp->t_refcnt); + KASSERT(refcnt < UINT_MAX); +} + +void +tty_release(struct tty *tp) +{ + unsigned old, new; + + KDASSERT(mutex_ownable(&tty_lock)); + + do { + old = atomic_load_relaxed(&tp->t_refcnt); + if (old == 1) { + mutex_spin_enter(&tty_lock); + if (atomic_dec_uint_nv(&tp->t_refcnt) == 0) + cv_broadcast(&ttyref_cv); + mutex_spin_exit(&tty_lock); + return; + } + KASSERT(old != 0); + new = old - 1; + } while (atomic_cas_uint(&tp->t_refcnt, old, new) != old); +} + /* * This macro is used in canonical mode input processing, where a read * request shall not return unless a 'line delimiter' ('\n') or 'break' @@ -941,7 +1003,6 @@ ttyoutput(int c, struct tty *tp) int ttioctl(struct tty *tp, u_long cmd, void *data, int flag, struct lwp *l) { - extern struct tty *constty; /* Temporary virtual console. */ struct proc *p; struct linesw *lp; int s, error; @@ -1044,32 +1105,47 @@ ttioctl(struct tty *tp, u_long cmd, void *data, int flag, struct lwp *l) mutex_spin_exit(&tty_lock); break; } - case TIOCCONS: /* become virtual console */ + case TIOCCONS: { /* become virtual console */ + struct tty *ctp; + + mutex_enter(&constty_lock); + error = 0; + ctp = atomic_load_relaxed(&constty); if (*(int *)data) { - if (constty && constty != tp && - ISSET(constty->t_state, TS_CARR_ON | TS_ISOPEN) == - (TS_CARR_ON | TS_ISOPEN)) - return EBUSY; + if (ctp != NULL && ctp != tp && + ISSET(ctp->t_state, TS_CARR_ON | TS_ISOPEN) == + (TS_CARR_ON | TS_ISOPEN)) { + error = EBUSY; + goto unlock_constty; + } pb = pathbuf_create("/dev/console"); if (pb == NULL) { - return ENOMEM; + error = ENOMEM; + goto unlock_constty; } NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF, pb); if ((error = namei(&nd)) != 0) { pathbuf_destroy(pb); - return error; + goto unlock_constty; } error = VOP_ACCESS(nd.ni_vp, VREAD, l->l_cred); vput(nd.ni_vp); pathbuf_destroy(pb); if (error) - return error; + goto unlock_constty; - constty = tp; - } else if (tp == constty) - constty = NULL; + KASSERT(atomic_load_relaxed(&constty) == ctp || + atomic_load_relaxed(&constty) == NULL); + atomic_store_release(&constty, tp); + } else if (tp == ctp) { + atomic_store_relaxed(&constty, NULL); + } +unlock_constty: mutex_exit(&constty_lock); + if (error) + return error; break; + } case TIOCDRAIN: /* wait till output drained */ if ((error = ttywait(tp)) != 0) return (error); @@ -2968,6 +3044,9 @@ tty_init(void) { mutex_init(&tty_lock, MUTEX_DEFAULT, IPL_VM); + mutex_init(&constty_lock, MUTEX_DEFAULT, IPL_NONE); + constty_psz = pserialize_create(); + cv_init(&ttyref_cv, "ttyref"); tty_sigsih = softint_establish(SOFTINT_CLOCK, ttysigintr, NULL); KASSERT(tty_sigsih != NULL); diff --git a/sys/sys/tty.h b/sys/sys/tty.h index dba93913bcec..82e9608d4c02 100644 --- a/sys/sys/tty.h +++ b/sys/sys/tty.h @@ -1,4 +1,4 @@ -/* $NetBSD: tty.h,v 1.101 2022/10/07 18:59:37 riastradh Exp $ */ +/* $NetBSD: tty.h,v 1.102 2022/10/25 23:21:13 riastradh Exp $ */ /*- * Copyright (c) 2008 The NetBSD Foundation, Inc. @@ -149,6 +149,7 @@ struct tty { int t_sigcount; /* # pending signals */ TAILQ_ENTRY(tty) t_sigqueue; /* entry on pending signal list */ void *t_softc; /* pointer to driver's softc. */ + volatile unsigned t_refcnt; /* reference count for constty */ }; #ifdef TTY_ALLOW_PRIVATE @@ -252,6 +253,8 @@ TAILQ_HEAD(ttylist_head, tty); /* the ttylist is a TAILQ */ #ifdef _KERNEL extern kmutex_t tty_lock; +extern kmutex_t constty_lock; +extern struct tty *volatile constty; extern int tty_count; /* number of ttys in global ttylist */ extern struct ttychars ttydefaults; @@ -313,6 +316,8 @@ void tty_free(struct tty *); u_char *firstc(struct clist *, int *); bool ttypull(struct tty *); int tty_unit(dev_t); +void tty_acquire(struct tty *); +void tty_release(struct tty *); int clalloc(struct clist *, int, int); void clfree(struct clist *);