/* Copyright 2002 Johan Rydberg, jrydberg@rtmk.org. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include /* For _POSIX_VDISABLE. */ #include #include "term.h" #include "io-uio.h" #include "module.h" struct ptyterm { /* Set if we need a wakeup when tty output has been done */ int pty_read_blocked; /* Wake this up when tty output occurs and pty_read_blocked is set */ pthread_cond_t pty_read_wakeup; pthread_cond_t pty_select_wakeup; /* Set if "dtr" is on. */ int dtr_on; /* Set if packet mode is on. */ int packet_mode; /* Set if user ioctl mode is on. */ int user_ioctl_mode; /* Byte to send to user in packet mode or user ioctl mode. */ char control_byte; int output_stopped; int pktnostop; int ptyopen; int nptyperopens; struct term *term; }; #define NPTYS 128 #define ptyminor(DEV) (minor(DEV) % NPTYS) #define master_p(DEV) (minor(DEV) < 128) static struct ptyterm *ptys [NPTYS]; #define lookup_pty(DEV) ptys [ptyminor (DEV)] static inline void wakeup_reader (struct ptyterm *ptyterm) { if (ptyterm->pty_read_blocked) { ptyterm->pty_read_blocked = 0; pthread_cond_broadcast (&ptyterm->pty_read_wakeup); } } /* Lower half for tty node */ static void pty_start_output (struct term *term) { struct ptyterm *ptyterm = term->term_aux; if (ptyterm->packet_mode && ptyterm->output_stopped && (!(term->termflags & USER_OUTPUT_SUSP))) { ptyterm->control_byte &= ~TIOCPKT_STOP; ptyterm->control_byte |= TIOCPKT_START; ptyterm->output_stopped = 0; } wakeup_reader (ptyterm); } static void pty_abandon_physical_output (struct term *term) { struct ptyterm *ptyterm = term->term_aux; if (ptyterm->packet_mode) { ptyterm->control_byte |= TIOCPKT_FLUSHWRITE; wakeup_reader (ptyterm); } } static void pty_suspend_physical_output (struct term *term) { struct ptyterm *ptyterm = term->term_aux; if (ptyterm->packet_mode) { ptyterm->control_byte &= ~TIOCPKT_START; ptyterm->control_byte |= TIOCPKT_STOP; ptyterm->output_stopped = 1; wakeup_reader (ptyterm); } } static int pty_pending_output_size (struct term *term) { /* We don't maintain any pending output buffer separate from the outputq. */ return 0; } static void pty_notice_input_flushed (struct term *term) { struct ptyterm *ptyterm = term->term_aux; if (ptyterm->packet_mode) { ptyterm->control_byte |= TIOCPKT_FLUSHREAD; wakeup_reader (ptyterm); } } static error_t pty_assert_dtr (struct term *term) { struct ptyterm *ptyterm = term->term_aux; ptyterm->dtr_on = 1; return 0; } static void pty_desert_dtr (struct term *term) { struct ptyterm *ptyterm = term->term_aux; ptyterm->dtr_on = 0; wakeup_reader (ptyterm); } static void pty_set_bits (struct term *term) { struct ptyterm *ptyterm = term->term_aux; if (ptyterm->packet_mode) { int wakeup = 0; int stop = ((term->termstate.c_iflag & IXON) && CCEQ (term->termstate.c_cc[VSTOP], CHAR_DC3) && CCEQ (term->termstate.c_cc[VSTART], CHAR_DC1)); if (term->external_processing) { ptyterm->control_byte |= TIOCPKT_IOCTL; wakeup = 1; } if (ptyterm->pktnostop && stop) { ptyterm->pktnostop = 0; ptyterm->control_byte |= TIOCPKT_DOSTOP; ptyterm->control_byte &= ~TIOCPKT_NOSTOP; wakeup = 1; } else if (!ptyterm->pktnostop && !stop) { ptyterm->pktnostop = 1; ptyterm->control_byte |= TIOCPKT_NOSTOP; ptyterm->control_byte &= ~TIOCPKT_DOSTOP; wakeup = 1; } if (wakeup) wakeup_reader (ptyterm); } } /* These do nothing. In BSD the associated ioctls get errors, but I'd rather just ignore them. */ static void pty_set_break (struct term *term) { } static void pty_clear_break (struct term *term) { } static void pty_mdmctl (struct term *term, int a, int b) { } static int pty_mdmstate (struct term *term) { return 0; } static struct bottomhalf pty_bottom = { pty_start_output, pty_set_break, pty_clear_break, pty_abandon_physical_output, pty_suspend_physical_output, pty_pending_output_size, pty_notice_input_flushed, pty_assert_dtr, pty_desert_dtr, pty_set_bits, pty_mdmctl, pty_mdmstate, }; static int pty_master_read (struct ptyterm *ptyterm, struct io_uio *uio, struct proc *p) { struct term *term = ptyterm->term; int size, amount = uio->uio_resid; pthread_mutex_lock (&term->global_lock); while (!ptyterm->control_byte && (term->termflags & TTY_OPEN) && (!qsize (term->outputq) || (term->termflags & USER_OUTPUT_SUSP))) { /* ??? handle O_NONBLOCK here. */ ptyterm->pty_read_blocked = 1; pthread_cond_wait (&ptyterm->pty_read_wakeup, &term->global_lock); } if (ptyterm->control_byte) { size = 1; if (ptyterm->packet_mode && (ptyterm->control_byte & TIOCPKT_IOCTL)) size += sizeof (struct termios); } else { size = qsize (term->outputq); if (ptyterm->packet_mode || ptyterm->user_ioctl_mode) size++; } if (size > amount) size = amount; if (ptyterm->control_byte) { io_uio_putc (uio, ptyterm->control_byte); if (ptyterm->packet_mode && (ptyterm->control_byte & TIOCPKT_IOCTL)) io_uio_move (uio, &term->termstate, size - 1); ptyterm->control_byte = 0; } else { if (ptyterm->packet_mode || ptyterm->user_ioctl_mode) { io_uio_putc (uio, TIOCPKT_DATA); --size; } while (size--) io_uio_putc (uio, dequeue (term, term->outputq)); } pthread_mutex_unlock (&term->global_lock); return 0; } static int pty_master_write (struct ptyterm *ptyterm, struct io_uio *uio, struct proc *p) { struct term *term = ptyterm->term; int i, flush; pthread_mutex_lock (&term->global_lock); if (term->remote_input_mode) { /* Wait for the queue to be empty */ while (qsize (term->inputq)) { /* ??? handle O_NONBLOCK. */ pthread_cond_wait (&term->inputq->wait, &term->global_lock); } while (uio->uio_resid) { struct io_iovec *iov = uio->uio_iov++; --uio->uio_iovcnt; assert (uio->uio_iovcnt >= 0); for (i = 0; i < iov->iov_len; i++) enqueue (term, &term->inputq, ((char *) iov->iov_base) [i]); uio->uio_resid -= iov->iov_len; } /* Extra garbage charater */ enqueue (term, &term->inputq, 0); } else if (term->termstate.c_cflag & CREAD) while (uio->uio_resid) { struct io_iovec *iov = uio->uio_iov++; --uio->uio_iovcnt; assert (uio->uio_iovcnt >= 0); for (i = 0; i < iov->iov_len; i++) { flush = input_character (term, ((char *) iov->iov_base) [i]); if (flush) { if (ptyterm->packet_mode) { ptyterm->control_byte |= TIOCPKT_FLUSHREAD; wakeup_reader (ptyterm); } break; } } uio->uio_resid -= iov->iov_len; } pthread_mutex_unlock (&term->global_lock); return 0; } static int pty_slave_read (struct ptyterm *ptyterm, struct io_uio *uio, struct proc *p) { struct term *term = ptyterm->term; int i, max, avail, amount; pthread_mutex_lock (& term->global_lock); while (! qsize (term->inputq)) { if ((term->termflags & NO_CARRIER) && !(term->termstate.c_cflag & CLOCAL)) { /* Return EOF, Posix.1 7.1.1.10. */ pthread_mutex_unlock (& term->global_lock); return 0; } /* ??? somehow handle O_NOBLOCK here. */ pthread_cond_wait (&term->inputq->wait, &term->global_lock); } amount = uio->uio_resid; avail = qsize (term->inputq); if (term->remote_input_mode) avail--; max = (amount < avail) ? amount : avail; for (i = 0; i < max; i++) { char c = dequeue (term, term->inputq); if (term->remote_input_mode) io_uio_putc (uio, c); else { /* Unless this is EOF, add it to the response. */ if (!(term->termstate.c_lflag & ICANON) || !CCEQ (term->termstate.c_cc[VEOF], c)) io_uio_putc (uio, c); /* If this is a break character, then finish now. */ if ((term->termstate.c_lflag & ICANON) && (c == '\n' || CCEQ (term->termstate.c_cc[VEOF], c) || CCEQ (term->termstate.c_cc[VEOL], c) || CCEQ (term->termstate.c_cc[VEOL2], c))) break; #if 0 /* If this is the delayed suspend character, then signal now. */ if ((term->termstate.c_lflag & ISIG) && CCEQ (term->termstate.c_cc[VDSUSP], c)) { /* The CANCEL flag is being used here to tell the return below to make sure we don't signal EOF on a VDUSP that happens at the front of a line. */ send_signal (SIGTSTP); cancel = 1; break; } #endif } } if (term->remote_input_mode && qsize (term->inputq) == 1) dequeue (term, term->inputq); pthread_mutex_unlock (&term->global_lock); return 0; } static int pty_slave_write (struct ptyterm *ptyterm, struct io_uio *uio, struct proc *p) { struct term *term = ptyterm->term; pthread_mutex_lock (&term->global_lock); int i; #if 0 if ((termstate.c_lflag & TOSTOP) && !fg_p (cred)) { mutex_unlock (&global_lock); return EBACKGROUND; } #endif if ((term->termflags & NO_CARRIER) && !(term->termstate.c_cflag & CLOCAL)) { pthread_mutex_unlock (&term->global_lock); return EIO; } while (uio->uio_resid) { struct io_iovec *iov = uio->uio_iov++; --uio->uio_iovcnt; assert (uio->uio_iovcnt >= 0); while (!qavail (term->outputq)) { (*term->bottom->start_output) (term); if (!qavail (term->outputq)) pthread_cond_wait (&term->outputq->wait, &term->global_lock); } for (i = 0; i < iov->iov_len; i++) write_character (term, ((char *) iov->iov_base) [i]); uio->uio_resid -= iov->iov_len; } (*term->bottom->start_output) (term); pthread_mutex_unlock (&term->global_lock); return 0; } /* Open device DEV. We just pass it to the TTY. */ static int pty_open (dev_t dev, int flags, struct proc *p) { struct ptyterm *ptyterm = lookup_pty (dev); if (! ptyterm) { ptyterm = malloc (sizeof (struct ptyterm)); assert (ptyterm); memset (ptyterm, 0, sizeof (struct ptyterm)); pthread_cond_init (&ptyterm->pty_read_wakeup, 0); pthread_cond_init (&ptyterm->pty_select_wakeup, 0); ptyterm->term = term_allocate (&pty_bottom, ptyterm); ptys [ptyminor (dev)] = ptyterm; } return 0; } /* Called for user writes to the terminal. */ static int pty_write (dev_t dev, struct io_uio *uio, struct proc *p) { struct ptyterm *ptyterm = lookup_pty (dev); if (! ptyterm) return EBADF; return master_p (dev) ? pty_master_write (ptyterm, uio, p) : pty_slave_write (ptyterm, uio, p); } /* Called for user reads from the terminal. */ static int pty_read (dev_t dev, struct io_uio *uio, struct proc *p) { struct ptyterm *ptyterm = lookup_pty (dev); if (! ptyterm) return EBADF; return master_p (dev) ? pty_master_read (ptyterm, uio, p) : pty_slave_read (ptyterm, uio, p); } static struct cdev_ops pty_ops = { pty_open, pty_read, pty_write }; static int pty_probe (struct md_descriptor *mdd) { md_attach (mdd, (void *) & pty_ops); return 0; } struct md_descriptor dv_pty_descriptor = { "pty", MD_TYPE_CHAR_DEVICE, 2, pty_probe };