Changeset View
Changeset View
Standalone View
Standalone View
sys/kern/tty_pts.c
Show First 20 Lines • Show All 75 Lines • ▼ Show 20 Lines | |||||
static struct unrhdr *pts_pool; | static struct unrhdr *pts_pool; | ||||
static MALLOC_DEFINE(M_PTS, "pts", "pseudo tty device"); | static MALLOC_DEFINE(M_PTS, "pts", "pseudo tty device"); | ||||
/* | /* | ||||
* Per-PTS structure. | * Per-PTS structure. | ||||
* | * | ||||
* List of locks | * List of locks | ||||
* (t) locked by tty_lock() | * (d) locked by ttydisc_lock() | ||||
* (c) const until freeing | * (c) const until freeing | ||||
*/ | */ | ||||
struct pts_softc { | struct pts_softc { | ||||
int pts_unit; /* (c) Device unit number. */ | int pts_unit; /* (c) Device unit number. */ | ||||
unsigned int pts_flags; /* (t) Device flags. */ | unsigned int pts_flags; /* (d) Device flags. */ | ||||
#define PTS_PKT 0x1 /* Packet mode. */ | #define PTS_PKT 0x1 /* Packet mode. */ | ||||
#define PTS_FINISHED 0x2 /* Return errors on read()/write(). */ | #define PTS_FINISHED 0x2 /* Return errors on read()/write(). */ | ||||
char pts_pkt; /* (t) Unread packet mode data. */ | char pts_pkt; /* (d) Unread packet mode data. */ | ||||
struct cv pts_inwait; /* (t) Blocking write() on master. */ | struct cv pts_inwait; /* (d) Blocking write() on master. */ | ||||
struct selinfo pts_inpoll; /* (t) Select queue for write(). */ | struct selinfo pts_inpoll; /* (d) Select queue for write(). */ | ||||
struct cv pts_outwait; /* (t) Blocking read() on master. */ | struct cv pts_outwait; /* (d) Blocking read() on master. */ | ||||
struct selinfo pts_outpoll; /* (t) Select queue for read(). */ | struct selinfo pts_outpoll; /* (d) Select queue for read(). */ | ||||
#ifdef PTS_EXTERNAL | #ifdef PTS_EXTERNAL | ||||
struct cdev *pts_cdev; /* (c) Master device node. */ | struct cdev *pts_cdev; /* (c) Master device node. */ | ||||
#endif /* PTS_EXTERNAL */ | #endif /* PTS_EXTERNAL */ | ||||
struct ucred *pts_cred; /* (c) Resource limit. */ | struct ucred *pts_cred; /* (c) Resource limit. */ | ||||
}; | }; | ||||
/* | /* | ||||
* Controller-side file operations. | * Controller-side file operations. | ||||
*/ | */ | ||||
static int | static int | ||||
ptsdev_read(struct file *fp, struct uio *uio, struct ucred *active_cred, | ptsdev_read(struct file *fp, struct uio *uio, struct ucred *active_cred, | ||||
int flags, struct thread *td) | int flags, struct thread *td) | ||||
{ | { | ||||
struct tty *tp = fp->f_data; | struct tty *tp = fp->f_data; | ||||
struct pts_softc *psc = tty_softc(tp); | struct pts_softc *psc = tty_softc(tp); | ||||
int error = 0; | int error = 0; | ||||
char pkt; | char pkt; | ||||
if (uio->uio_resid == 0) | if (uio->uio_resid == 0) | ||||
return (0); | return (0); | ||||
tty_lock(tp); | ttydisc_lock(tp); | ||||
for (;;) { | for (;;) { | ||||
/* | /* | ||||
* Implement packet mode. When packet mode is turned on, | * Implement packet mode. When packet mode is turned on, | ||||
* the first byte contains a bitmask of events that | * the first byte contains a bitmask of events that | ||||
* occurred (start, stop, flush, window size, etc). | * occurred (start, stop, flush, window size, etc). | ||||
*/ | */ | ||||
if (psc->pts_flags & PTS_PKT && psc->pts_pkt) { | if (psc->pts_flags & PTS_PKT && psc->pts_pkt) { | ||||
pkt = psc->pts_pkt; | pkt = psc->pts_pkt; | ||||
psc->pts_pkt = 0; | psc->pts_pkt = 0; | ||||
tty_unlock(tp); | ttydisc_unlock(tp); | ||||
error = ureadc(pkt, uio); | error = ureadc(pkt, uio); | ||||
return (error); | return (error); | ||||
} | } | ||||
/* | /* | ||||
* Transmit regular data. | * Transmit regular data. | ||||
* | * | ||||
* XXX: We shouldn't use ttydisc_getc_poll()! Even | * XXX: We shouldn't use ttydisc_getc_poll()! Even | ||||
* though in this implementation, there is likely going | * though in this implementation, there is likely going | ||||
* to be data, we should just call ttydisc_getc_uio() | * to be data, we should just call ttydisc_getc_uio() | ||||
* and use its return value to sleep. | * and use its return value to sleep. | ||||
*/ | */ | ||||
if (ttydisc_getc_poll(tp)) { | if (ttydisc_getc_poll(tp)) { | ||||
if (psc->pts_flags & PTS_PKT) { | if (psc->pts_flags & PTS_PKT) { | ||||
/* | /* | ||||
* XXX: Small race. Fortunately PTY | * XXX: Small race. Fortunately PTY | ||||
* consumers aren't multithreaded. | * consumers aren't multithreaded. | ||||
*/ | */ | ||||
tty_unlock(tp); | ttydisc_unlock(tp); | ||||
error = ureadc(TIOCPKT_DATA, uio); | error = ureadc(TIOCPKT_DATA, uio); | ||||
if (error) | if (error) | ||||
return (error); | return (error); | ||||
tty_lock(tp); | ttydisc_lock(tp); | ||||
} | } | ||||
error = ttydisc_getc_uio(tp, uio); | error = ttydisc_getc_uio(tp, uio); | ||||
break; | break; | ||||
} | } | ||||
/* Maybe the device isn't used anyway. */ | /* Maybe the device isn't used anyway. */ | ||||
if (psc->pts_flags & PTS_FINISHED) | if (psc->pts_flags & PTS_FINISHED) | ||||
break; | break; | ||||
/* Wait for more data. */ | /* Wait for more data. */ | ||||
if (fp->f_flag & O_NONBLOCK) { | if (fp->f_flag & O_NONBLOCK) { | ||||
error = EWOULDBLOCK; | error = EWOULDBLOCK; | ||||
break; | break; | ||||
} | } | ||||
error = cv_wait_sig(&psc->pts_outwait, tp->t_mtx); | error = cv_wait_sig(&psc->pts_outwait, ttydisc_getlock(tp)); | ||||
if (error != 0) | if (error != 0) | ||||
break; | break; | ||||
} | } | ||||
tty_unlock(tp); | ttydisc_unlock(tp); | ||||
return (error); | return (error); | ||||
} | } | ||||
static int | static int | ||||
ptsdev_write(struct file *fp, struct uio *uio, struct ucred *active_cred, | ptsdev_write(struct file *fp, struct uio *uio, struct ucred *active_cred, | ||||
int flags, struct thread *td) | int flags, struct thread *td) | ||||
{ | { | ||||
struct tty *tp = fp->f_data; | struct tty *tp = fp->f_data; | ||||
struct pts_softc *psc = tty_softc(tp); | struct pts_softc *psc = tty_softc(tp); | ||||
char ib[256], *ibstart; | char ib[256], *ibstart; | ||||
size_t iblen, rintlen; | size_t iblen, rintlen; | ||||
int error = 0; | int error = 0; | ||||
if (uio->uio_resid == 0) | if (uio->uio_resid == 0) | ||||
return (0); | return (0); | ||||
for (;;) { | for (;;) { | ||||
ibstart = ib; | ibstart = ib; | ||||
iblen = MIN(uio->uio_resid, sizeof ib); | iblen = MIN(uio->uio_resid, sizeof ib); | ||||
error = uiomove(ib, iblen, uio); | error = uiomove(ib, iblen, uio); | ||||
tty_lock(tp); | ttydisc_lock(tp); | ||||
if (error != 0) { | if (error != 0) { | ||||
iblen = 0; | iblen = 0; | ||||
goto done; | goto done; | ||||
} | } | ||||
/* | /* | ||||
* When possible, avoid the slow path. rint_bypass() | * When possible, avoid the slow path. rint_bypass() | ||||
* copies all input to the input queue at once. | * copies all input to the input queue at once. | ||||
Show All 17 Lines | do { | ||||
/* Wait for more data. */ | /* Wait for more data. */ | ||||
if (fp->f_flag & O_NONBLOCK) { | if (fp->f_flag & O_NONBLOCK) { | ||||
error = EWOULDBLOCK; | error = EWOULDBLOCK; | ||||
goto done; | goto done; | ||||
} | } | ||||
/* Wake up users on the slave side. */ | /* Wake up users on the slave side. */ | ||||
ttydisc_rint_done(tp); | ttydisc_rint_done(tp); | ||||
error = cv_wait_sig(&psc->pts_inwait, tp->t_mtx); | error = cv_wait_sig(&psc->pts_inwait, | ||||
ttydisc_getlock(tp)); | |||||
if (error != 0) | if (error != 0) | ||||
goto done; | goto done; | ||||
} while (iblen > 0); | } while (iblen > 0); | ||||
if (uio->uio_resid == 0) | if (uio->uio_resid == 0) | ||||
break; | break; | ||||
tty_unlock(tp); | ttydisc_unlock(tp); | ||||
} | } | ||||
done: ttydisc_rint_done(tp); | done: ttydisc_rint_done(tp); | ||||
tty_unlock(tp); | ttydisc_unlock(tp); | ||||
/* | /* | ||||
* Don't account for the part of the buffer that we couldn't | * Don't account for the part of the buffer that we couldn't | ||||
* pass to the TTY. | * pass to the TTY. | ||||
*/ | */ | ||||
uio->uio_resid += iblen; | uio->uio_resid += iblen; | ||||
return (error); | return (error); | ||||
} | } | ||||
Show All 9 Lines | ptsdev_ioctl(struct file *fp, u_long cmd, void *data, | ||||
switch (cmd) { | switch (cmd) { | ||||
case FIODTYPE: | case FIODTYPE: | ||||
*(int *)data = D_TTY; | *(int *)data = D_TTY; | ||||
return (0); | return (0); | ||||
case FIONBIO: | case FIONBIO: | ||||
/* This device supports non-blocking operation. */ | /* This device supports non-blocking operation. */ | ||||
return (0); | return (0); | ||||
case FIONREAD: | case FIONREAD: | ||||
tty_lock(tp); | ttydisc_lock(tp); | ||||
*(int *)data = ttydisc_getc_poll(tp); | *(int *)data = ttydisc_getc_poll(tp); | ||||
tty_unlock(tp); | ttydisc_unlock(tp); | ||||
return (0); | return (0); | ||||
case FIODGNAME: | case FIODGNAME: | ||||
#ifdef COMPAT_FREEBSD32 | #ifdef COMPAT_FREEBSD32 | ||||
case FIODGNAME_32: | case FIODGNAME_32: | ||||
#endif | #endif | ||||
{ | { | ||||
struct fiodgname_arg *fgn; | struct fiodgname_arg *fgn; | ||||
const char *p; | const char *p; | ||||
Show All 15 Lines | #endif | ||||
* process. | * process. | ||||
* | * | ||||
* TIOCGETA is also implemented here. Various Linux PTY routines | * TIOCGETA is also implemented here. Various Linux PTY routines | ||||
* often call isatty(), which is implemented by tcgetattr(). | * often call isatty(), which is implemented by tcgetattr(). | ||||
*/ | */ | ||||
#ifdef PTS_LINUX | #ifdef PTS_LINUX | ||||
case TIOCGETA: | case TIOCGETA: | ||||
/* Obtain terminal flags through tcgetattr(). */ | /* Obtain terminal flags through tcgetattr(). */ | ||||
tty_lock(tp); | ttydisc_lock(tp); | ||||
*(struct termios*)data = tp->t_termios; | *(struct termios*)data = tp->t_termios; | ||||
tty_unlock(tp); | ttydisc_unlock(tp); | ||||
return (0); | return (0); | ||||
#endif /* PTS_LINUX */ | #endif /* PTS_LINUX */ | ||||
case TIOCSETAF: | case TIOCSETAF: | ||||
case TIOCSETAW: | case TIOCSETAW: | ||||
/* | /* | ||||
* We must make sure we turn tcsetattr() calls of TCSAFLUSH and | * We must make sure we turn tcsetattr() calls of TCSAFLUSH and | ||||
* TCSADRAIN into something different. If an application would | * TCSADRAIN into something different. If an application would | ||||
* call TCSAFLUSH or TCSADRAIN on the master descriptor, it may | * call TCSAFLUSH or TCSADRAIN on the master descriptor, it may | ||||
* deadlock waiting for all data to be read. | * deadlock waiting for all data to be read. | ||||
*/ | */ | ||||
cmd = TIOCSETA; | cmd = TIOCSETA; | ||||
break; | break; | ||||
#if defined(PTS_COMPAT) || defined(PTS_LINUX) | #if defined(PTS_COMPAT) || defined(PTS_LINUX) | ||||
case TIOCGPTN: | case TIOCGPTN: | ||||
/* | /* | ||||
* Get the device unit number. | * Get the device unit number. | ||||
*/ | */ | ||||
if (psc->pts_unit < 0) | if (psc->pts_unit < 0) | ||||
return (ENOTTY); | return (ENOTTY); | ||||
*(unsigned int *)data = psc->pts_unit; | *(unsigned int *)data = psc->pts_unit; | ||||
return (0); | return (0); | ||||
#endif /* PTS_COMPAT || PTS_LINUX */ | #endif /* PTS_COMPAT || PTS_LINUX */ | ||||
case TIOCGPGRP: | case TIOCGPGRP: | ||||
/* Get the foreground process group ID. */ | /* Get the foreground process group ID. */ | ||||
tty_lock(tp); | ttydisc_lock(tp); | ||||
if (tp->t_pgrp != NULL) | if (tp->t_pgrp != NULL) | ||||
*(int *)data = tp->t_pgrp->pg_id; | *(int *)data = tp->t_pgrp->pg_id; | ||||
else | else | ||||
*(int *)data = NO_PID; | *(int *)data = NO_PID; | ||||
tty_unlock(tp); | ttydisc_unlock(tp); | ||||
return (0); | return (0); | ||||
case TIOCGSID: | case TIOCGSID: | ||||
/* Get the session leader process ID. */ | /* Get the session leader process ID. */ | ||||
tty_lock(tp); | ttydisc_lock(tp); | ||||
if (tp->t_session == NULL) | if (tp->t_session == NULL) | ||||
error = ENOTTY; | error = ENOTTY; | ||||
else | else | ||||
*(int *)data = tp->t_session->s_sid; | *(int *)data = tp->t_session->s_sid; | ||||
tty_unlock(tp); | ttydisc_unlock(tp); | ||||
return (error); | return (error); | ||||
case TIOCPTMASTER: | case TIOCPTMASTER: | ||||
/* Yes, we are a pseudo-terminal master. */ | /* Yes, we are a pseudo-terminal master. */ | ||||
return (0); | return (0); | ||||
case TIOCSIG: | case TIOCSIG: | ||||
/* Signal the foreground process group. */ | /* Signal the foreground process group. */ | ||||
sig = *(int *)data; | sig = *(int *)data; | ||||
if (sig < 1 || sig >= NSIG) | if (sig < 1 || sig >= NSIG) | ||||
return (EINVAL); | return (EINVAL); | ||||
tty_lock(tp); | ttydisc_lock(tp); | ||||
tty_signal_pgrp(tp, sig); | tty_signal_pgrp(tp, sig); | ||||
tty_unlock(tp); | ttydisc_unlock(tp); | ||||
return (0); | return (0); | ||||
case TIOCPKT: | case TIOCPKT: | ||||
/* Enable/disable packet mode. */ | /* Enable/disable packet mode. */ | ||||
tty_lock(tp); | ttydisc_lock(tp); | ||||
if (*(int *)data) | if (*(int *)data) | ||||
psc->pts_flags |= PTS_PKT; | psc->pts_flags |= PTS_PKT; | ||||
else | else | ||||
psc->pts_flags &= ~PTS_PKT; | psc->pts_flags &= ~PTS_PKT; | ||||
tty_unlock(tp); | ttydisc_unlock(tp); | ||||
return (0); | return (0); | ||||
} | } | ||||
/* Just redirect this ioctl to the slave device. */ | /* Just redirect this ioctl to the slave device. */ | ||||
tty_lock(tp); | tty_lock(tp); | ||||
error = tty_ioctl(tp, cmd, data, fp->f_flag, td); | error = tty_ioctl(tp, cmd, data, fp->f_flag, td); | ||||
tty_unlock(tp); | tty_unlock(tp); | ||||
if (error == ENOIOCTL) | if (error == ENOIOCTL) | ||||
error = ENOTTY; | error = ENOTTY; | ||||
return (error); | return (error); | ||||
} | } | ||||
static int | static int | ||||
ptsdev_poll(struct file *fp, int events, struct ucred *active_cred, | ptsdev_poll(struct file *fp, int events, struct ucred *active_cred, | ||||
struct thread *td) | struct thread *td) | ||||
{ | { | ||||
struct tty *tp = fp->f_data; | struct tty *tp = fp->f_data; | ||||
struct pts_softc *psc = tty_softc(tp); | struct pts_softc *psc = tty_softc(tp); | ||||
int revents = 0; | int revents = 0; | ||||
tty_lock(tp); | ttydisc_lock(tp); | ||||
if (psc->pts_flags & PTS_FINISHED) { | if (psc->pts_flags & PTS_FINISHED) { | ||||
/* Slave device is not opened. */ | /* Slave device is not opened. */ | ||||
tty_unlock(tp); | ttydisc_unlock(tp); | ||||
return ((events & (POLLIN|POLLRDNORM)) | POLLHUP); | return ((events & (POLLIN|POLLRDNORM)) | POLLHUP); | ||||
} | } | ||||
if (events & (POLLIN|POLLRDNORM)) { | if (events & (POLLIN|POLLRDNORM)) { | ||||
/* See if we can getc something. */ | /* See if we can getc something. */ | ||||
if (ttydisc_getc_poll(tp) || | if (ttydisc_getc_poll(tp) || | ||||
(psc->pts_flags & PTS_PKT && psc->pts_pkt)) | (psc->pts_flags & PTS_PKT && psc->pts_pkt)) | ||||
revents |= events & (POLLIN|POLLRDNORM); | revents |= events & (POLLIN|POLLRDNORM); | ||||
Show All 17 Lines | if (revents == 0) { | ||||
* device. | * device. | ||||
*/ | */ | ||||
if (events & (POLLIN|POLLRDNORM)) | if (events & (POLLIN|POLLRDNORM)) | ||||
selrecord(td, &psc->pts_outpoll); | selrecord(td, &psc->pts_outpoll); | ||||
if (events & (POLLOUT|POLLWRNORM)) | if (events & (POLLOUT|POLLWRNORM)) | ||||
selrecord(td, &psc->pts_inpoll); | selrecord(td, &psc->pts_inpoll); | ||||
} | } | ||||
tty_unlock(tp); | ttydisc_unlock(tp); | ||||
return (revents); | return (revents); | ||||
} | } | ||||
/* | /* | ||||
* kqueue support. | * kqueue support. | ||||
*/ | */ | ||||
▲ Show 20 Lines • Show All 324 Lines • ▼ Show 20 Lines | pts_alloc(int fflags, struct thread *td, struct file *fp) | ||||
psc = malloc(sizeof(struct pts_softc), M_PTS, M_WAITOK|M_ZERO); | psc = malloc(sizeof(struct pts_softc), M_PTS, M_WAITOK|M_ZERO); | ||||
cv_init(&psc->pts_inwait, "ptsin"); | cv_init(&psc->pts_inwait, "ptsin"); | ||||
cv_init(&psc->pts_outwait, "ptsout"); | cv_init(&psc->pts_outwait, "ptsout"); | ||||
psc->pts_unit = unit; | psc->pts_unit = unit; | ||||
psc->pts_cred = crhold(cred); | psc->pts_cred = crhold(cred); | ||||
tp = tty_alloc(&pts_class, psc); | tp = tty_alloc(&pts_class, psc); | ||||
knlist_init_mtx(&psc->pts_inpoll.si_note, tp->t_mtx); | knlist_init_mtx(&psc->pts_inpoll.si_note, ttydisc_getlock(tp)); | ||||
knlist_init_mtx(&psc->pts_outpoll.si_note, tp->t_mtx); | knlist_init_mtx(&psc->pts_outpoll.si_note, ttydisc_getlock(tp)); | ||||
/* Expose the slave device as well. */ | /* Expose the slave device as well. */ | ||||
tty_makedev(tp, td->td_ucred, "pts/%u", psc->pts_unit); | tty_makedev(tp, td->td_ucred, "pts/%u", psc->pts_unit); | ||||
finit(fp, fflags, DTYPE_PTS, tp, &ptsdev_ops); | finit(fp, fflags, DTYPE_PTS, tp, &ptsdev_ops); | ||||
return (0); | return (0); | ||||
} | } | ||||
Show All 29 Lines | pts_alloc_external(int fflags, struct thread *td, struct file *fp, | ||||
cv_init(&psc->pts_inwait, "ptsin"); | cv_init(&psc->pts_inwait, "ptsin"); | ||||
cv_init(&psc->pts_outwait, "ptsout"); | cv_init(&psc->pts_outwait, "ptsout"); | ||||
psc->pts_unit = -1; | psc->pts_unit = -1; | ||||
psc->pts_cdev = dev; | psc->pts_cdev = dev; | ||||
psc->pts_cred = crhold(cred); | psc->pts_cred = crhold(cred); | ||||
tp = tty_alloc(&pts_class, psc); | tp = tty_alloc(&pts_class, psc); | ||||
knlist_init_mtx(&psc->pts_inpoll.si_note, tp->t_mtx); | knlist_init_mtx(&psc->pts_inpoll.si_note, ttydisc_getlock(tp)); | ||||
knlist_init_mtx(&psc->pts_outpoll.si_note, tp->t_mtx); | knlist_init_mtx(&psc->pts_outpoll.si_note, ttydisc_getlock(tp)); | ||||
/* Expose the slave device as well. */ | /* Expose the slave device as well. */ | ||||
tty_makedev(tp, td->td_ucred, "%s", name); | tty_makedev(tp, td->td_ucred, "%s", name); | ||||
finit(fp, fflags, DTYPE_PTS, tp, &ptsdev_ops); | finit(fp, fflags, DTYPE_PTS, tp, &ptsdev_ops); | ||||
return (0); | return (0); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 42 Lines • Show Last 20 Lines |