1 /* pty.c - pseudo terminal driver Author: Kees J. Bot
3 * PTYs can be seen as a bidirectional pipe with TTY
4 * input and output processing. For example a simple rlogin session:
6 * keyboard -> rlogin -> in.rld -> /dev/ptypX -> /dev/ttypX -> shell
7 * shell -> /dev/ttypX -> /dev/ptypX -> in.rld -> rlogin -> screen
9 * This file takes care of copying data between the tty/pty device pairs and
10 * the open/read/write/close calls on the pty devices. The TTY task takes
11 * care of the input and output processing (interrupt, backspace, raw I/O,
12 * etc.) using the pty_slave_read() and pty_slave_write() functions as the
13 * "keyboard" and "screen" functions of the ttypX devices.
14 * Be careful when reading this code, the terms "reading" and "writing" are
15 * used both for the tty (slave) and the pty (master) end of the pseudo tty.
16 * Writes to one end are to be read at the other end and vice-versa.
19 #include <minix/drivers.h>
22 #include <sys/termios.h>
26 /* PTY bookkeeping structure, one per pty/tty pair. */
28 tty_t
*tty
; /* associated TTY structure */
29 char state
; /* flags: busy, closed, ... */
31 /* Read call on master (/dev/ptypX). */
32 endpoint_t rdcaller
; /* process making the call, or NONE if none */
33 cdev_id_t rdid
; /* ID of suspended read request */
34 cp_grant_id_t rdgrant
; /* grant for reader's address space */
35 size_t rdleft
; /* # bytes yet to be read */
36 size_t rdcum
; /* # bytes written so far */
38 /* Write call to master (/dev/ptypX). */
39 endpoint_t wrcaller
; /* process making the call, or NONE if none*/
40 cdev_id_t wrid
; /* ID of suspended write request */
41 cp_grant_id_t wrgrant
; /* grant for writer's address space */
42 size_t wrleft
; /* # bytes yet to be written */
43 size_t wrcum
; /* # bytes written so far */
46 int ocount
; /* # characters in the buffer */
47 char *ohead
, *otail
; /* head and tail of the circular buffer */
48 char obuf
[2048]; /* buffer for bytes going to the pty reader */
51 unsigned int select_ops
; /* Which operations do we want to know about? */
52 endpoint_t select_proc
; /* Who wants to know about it? */
55 #define TTY_ACTIVE 0x01 /* tty is open/active */
56 #define PTY_ACTIVE 0x02 /* pty is open/active */
57 #define TTY_CLOSED 0x04 /* tty side has closed down */
58 #define PTY_CLOSED 0x08 /* pty side has closed down */
60 static pty_t pty_table
[NR_PTYS
]; /* PTY bookkeeping */
62 static void pty_start(pty_t
*pp
);
63 static void pty_finish(pty_t
*pp
);
65 static int pty_master_open(devminor_t minor
, int access
,
66 endpoint_t user_endpt
);
67 static int pty_master_close(devminor_t minor
);
68 static ssize_t
pty_master_read(devminor_t minor
, u64_t position
,
69 endpoint_t endpt
, cp_grant_id_t grant
, size_t size
, int flags
,
71 static ssize_t
pty_master_write(devminor_t minor
, u64_t position
,
72 endpoint_t endpt
, cp_grant_id_t grant
, size_t size
, int flags
,
74 static int pty_master_cancel(devminor_t minor
, endpoint_t endpt
, cdev_id_t id
);
75 static int pty_master_select(devminor_t minor
, unsigned int ops
,
78 static struct chardriver pty_master_tab
= {
79 .cdr_open
= pty_master_open
,
80 .cdr_close
= pty_master_close
,
81 .cdr_read
= pty_master_read
,
82 .cdr_write
= pty_master_write
,
83 .cdr_cancel
= pty_master_cancel
,
84 .cdr_select
= pty_master_select
87 /*===========================================================================*
89 *===========================================================================*/
90 static int pty_master_open(devminor_t minor
, int UNUSED(access
),
91 endpoint_t
UNUSED(user_endpt
))
96 assert(minor
>= PTYPX_MINOR
&& minor
< PTYPX_MINOR
+ NR_PTYS
);
98 if ((tp
= line2tty(minor
)) == NULL
)
102 if (pp
->state
& PTY_ACTIVE
)
105 pp
->state
|= PTY_ACTIVE
;
112 /*===========================================================================*
114 *===========================================================================*/
115 static int pty_master_close(devminor_t minor
)
120 if ((tp
= line2tty(minor
)) == NULL
)
124 if ((pp
->state
& (TTY_ACTIVE
| TTY_CLOSED
)) != TTY_ACTIVE
) {
127 pp
->state
|= PTY_CLOSED
;
128 sigchar(tp
, SIGHUP
, 1);
134 /*===========================================================================*
136 *===========================================================================*/
137 static ssize_t
pty_master_read(devminor_t minor
, u64_t
UNUSED(position
),
138 endpoint_t endpt
, cp_grant_id_t grant
, size_t size
, int flags
,
145 if ((tp
= line2tty(minor
)) == NULL
)
149 /* Check, store information on the reader, do I/O. */
150 if (pp
->state
& TTY_CLOSED
)
153 if (pp
->rdcaller
!= NONE
|| pp
->rdleft
!= 0 || pp
->rdcum
!= 0)
159 pp
->rdcaller
= endpt
;
167 if (pp
->rdleft
== 0) {
169 return EDONTREPLY
; /* already done */
172 if (flags
& CDEV_NONBLOCK
) {
173 r
= pp
->rdcum
> 0 ? pp
->rdcum
: EAGAIN
;
174 pp
->rdleft
= pp
->rdcum
= 0;
179 return EDONTREPLY
; /* do suspend */
182 /*===========================================================================*
184 *===========================================================================*/
185 static ssize_t
pty_master_write(devminor_t minor
, u64_t
UNUSED(position
),
186 endpoint_t endpt
, cp_grant_id_t grant
, size_t size
, int flags
,
193 if ((tp
= line2tty(minor
)) == NULL
)
197 /* Check, store information on the writer, do I/O. */
198 if (pp
->state
& TTY_CLOSED
)
201 if (pp
->wrcaller
!= NONE
|| pp
->wrleft
!= 0 || pp
->wrcum
!= 0)
207 pp
->wrcaller
= endpt
;
214 if (pp
->wrleft
== 0) {
216 return EDONTREPLY
; /* already done */
219 if (flags
& CDEV_NONBLOCK
) {
220 r
= pp
->wrcum
> 0 ? pp
->wrcum
: EAGAIN
;
221 pp
->wrleft
= pp
->wrcum
= 0;
226 return EDONTREPLY
; /* do suspend */
229 /*===========================================================================*
230 * pty_master_cancel *
231 *===========================================================================*/
232 static int pty_master_cancel(devminor_t minor
, endpoint_t endpt
, cdev_id_t id
)
238 if ((tp
= line2tty(minor
)) == NULL
)
242 if (pp
->rdcaller
== endpt
&& pp
->rdid
== id
) {
243 /* Cancel a read from a PTY. */
244 r
= pp
->rdcum
> 0 ? pp
->rdcum
: EINTR
;
245 pp
->rdleft
= pp
->rdcum
= 0;
250 if (pp
->wrcaller
== endpt
&& pp
->wrid
== id
) {
251 /* Cancel a write to a PTY. */
252 r
= pp
->wrcum
> 0 ? pp
->wrcum
: EINTR
;
253 pp
->wrleft
= pp
->wrcum
= 0;
258 /* Request not found. */
262 /*===========================================================================*
264 *===========================================================================*/
265 static int select_try_pty(tty_t
*tp
, int ops
)
267 pty_t
*pp
= tp
->tty_priv
;
270 if (ops
& CDEV_OP_WR
) {
271 /* Write won't block on error. */
272 if (pp
->state
& TTY_CLOSED
) r
|= CDEV_OP_WR
;
273 else if (pp
->wrleft
!= 0 || pp
->wrcum
!= 0) r
|= CDEV_OP_WR
;
274 else if (tp
->tty_incount
< buflen(tp
->tty_inbuf
)) r
|= CDEV_OP_WR
;
277 if (ops
& CDEV_OP_RD
) {
278 /* Read won't block on error. */
279 if (pp
->state
& TTY_CLOSED
) r
|= CDEV_OP_RD
;
280 else if (pp
->rdleft
!= 0 || pp
->rdcum
!= 0) r
|= CDEV_OP_RD
;
281 else if (pp
->ocount
> 0) r
|= CDEV_OP_RD
; /* Actual data. */
287 /*===========================================================================*
289 *===========================================================================*/
290 void select_retry_pty(tty_t
*tp
)
292 pty_t
*pp
= tp
->tty_priv
;
296 /* See if the pty side of a pty is ready to return a select. */
297 if (pp
->select_ops
&& (r
= select_try_pty(tp
, pp
->select_ops
))) {
298 minor
= PTYPX_MINOR
+ (int) (pp
- pty_table
);
299 chardriver_reply_select(pp
->select_proc
, minor
, r
);
300 pp
->select_ops
&= ~r
;
304 /*===========================================================================*
305 * pty_master_select *
306 *===========================================================================*/
307 static int pty_master_select(devminor_t minor
, unsigned int ops
,
312 int ready_ops
, watch
;
314 if ((tp
= line2tty(minor
)) == NULL
)
318 watch
= (ops
& CDEV_NOTIFY
);
319 ops
&= (CDEV_OP_RD
| CDEV_OP_WR
| CDEV_OP_ERR
);
321 ready_ops
= select_try_pty(tp
, ops
);
325 pp
->select_ops
|= ops
;
326 pp
->select_proc
= endpt
;
332 /*===========================================================================*
334 *===========================================================================*/
335 void do_pty(message
*m_ptr
, int ipc_status
)
337 /* Process a request for a PTY master (/dev/ptypX) device. */
339 chardriver_process(&pty_master_tab
, m_ptr
, ipc_status
);
342 /*===========================================================================*
344 *===========================================================================*/
345 static int pty_slave_write(tty_t
*tp
, int try)
347 /* (*dev_write)() routine for PTYs. Transfer bytes from the writer on
348 * /dev/ttypX to the output buffer.
350 pty_t
*pp
= tp
->tty_priv
;
351 int count
, ocount
, s
;
353 /* PTY closed down? */
354 if (pp
->state
& PTY_CLOSED
) {
356 if (tp
->tty_outleft
> 0) {
357 chardriver_reply_task(tp
->tty_outcaller
, tp
->tty_outid
, EIO
);
358 tp
->tty_outleft
= tp
->tty_outcum
= 0;
359 tp
->tty_outcaller
= NONE
;
364 /* While there is something to do. */
366 ocount
= buflen(pp
->obuf
) - pp
->ocount
;
367 if (try) return (ocount
> 0);
368 count
= bufend(pp
->obuf
) - pp
->ohead
;
369 if (count
> ocount
) count
= ocount
;
370 if (count
> tp
->tty_outleft
) count
= tp
->tty_outleft
;
371 if (count
== 0 || tp
->tty_inhibited
)
374 /* Copy from user space to the PTY output buffer. */
375 if (tp
->tty_outcaller
== KERNEL
) {
376 /* We're trying to print on kernel's behalf */
377 memcpy(pp
->ohead
, (void *) tp
->tty_outgrant
+ tp
->tty_outcum
,
380 if ((s
= sys_safecopyfrom(tp
->tty_outcaller
, tp
->tty_outgrant
,
381 tp
->tty_outcum
, (vir_bytes
) pp
->ohead
,
387 /* Perform output processing on the output buffer. */
388 out_process(tp
, pp
->obuf
, pp
->ohead
, bufend(pp
->obuf
), &count
, &ocount
);
389 if (count
== 0) break;
391 /* Assume echoing messed up by output. */
392 tp
->tty_reprint
= TRUE
;
395 pp
->ocount
+= ocount
;
396 if ((pp
->ohead
+= ocount
) >= bufend(pp
->obuf
))
397 pp
->ohead
-= buflen(pp
->obuf
);
400 tp
->tty_outcum
+= count
;
401 if ((tp
->tty_outleft
-= count
) == 0) {
402 /* Output is finished, reply to the writer. */
403 chardriver_reply_task(tp
->tty_outcaller
, tp
->tty_outid
,
406 tp
->tty_outcaller
= NONE
;
413 /*===========================================================================*
415 *===========================================================================*/
416 static void pty_slave_echo(tty_t
*tp
, int c
)
418 /* Echo one character. (Like pty_write, but only one character, optionally.) */
420 pty_t
*pp
= tp
->tty_priv
;
423 ocount
= buflen(pp
->obuf
) - pp
->ocount
;
424 if (ocount
== 0) return; /* output buffer full */
426 *pp
->ohead
= c
; /* add one character */
428 out_process(tp
, pp
->obuf
, pp
->ohead
, bufend(pp
->obuf
), &count
, &ocount
);
429 if (count
== 0) return;
431 pp
->ocount
+= ocount
;
432 if ((pp
->ohead
+= ocount
) >= bufend(pp
->obuf
)) pp
->ohead
-= buflen(pp
->obuf
);
436 /*===========================================================================*
438 *===========================================================================*/
439 static void pty_start(pty_t
*pp
)
441 /* Transfer bytes written to the output buffer to the PTY reader. */
444 /* While there are things to do. */
447 count
= bufend(pp
->obuf
) - pp
->otail
;
448 if (count
> pp
->ocount
) count
= pp
->ocount
;
449 if (count
> pp
->rdleft
) count
= pp
->rdleft
;
450 if (count
== 0) break;
452 /* Copy from the output buffer to the readers address space. */
453 if((s
= sys_safecopyto(pp
->rdcaller
, pp
->rdgrant
, pp
->rdcum
,
454 (vir_bytes
) pp
->otail
, count
)) != OK
) {
460 if ((pp
->otail
+= count
) == bufend(pp
->obuf
)) pp
->otail
= pp
->obuf
;
466 /*===========================================================================*
468 *===========================================================================*/
469 static void pty_finish(pty_t
*pp
)
471 /* Finish the read request of a PTY reader if there is at least one byte
476 chardriver_reply_task(pp
->rdcaller
, pp
->rdid
, pp
->rdcum
);
477 pp
->rdleft
= pp
->rdcum
= 0;
482 /*===========================================================================*
484 *===========================================================================*/
485 static int pty_slave_read(tty_t
*tp
, int try)
487 /* Offer bytes from the PTY writer for input on the TTY. (Do it one byte at
488 * a time, 99% of the writes will be for one byte, so no sense in being smart.)
490 pty_t
*pp
= tp
->tty_priv
;
493 if (pp
->state
& PTY_CLOSED
) {
495 if (tp
->tty_inleft
> 0) {
496 chardriver_reply_task(tp
->tty_incaller
, tp
->tty_inid
,
498 tp
->tty_inleft
= tp
->tty_incum
= 0;
499 tp
->tty_incaller
= NONE
;
510 while (pp
->wrleft
> 0) {
513 /* Transfer one character to 'c'. */
514 if ((s
= sys_safecopyfrom(pp
->wrcaller
, pp
->wrgrant
, pp
->wrcum
,
515 (vir_bytes
) &c
, 1)) != OK
) {
516 printf("pty: safecopy failed (error %d)\n", s
);
520 /* Input processing. */
521 if (in_process(tp
, &c
, 1) == 0) break;
523 /* PTY writer bookkeeping. */
525 if (--pp
->wrleft
== 0) {
526 chardriver_reply_task(pp
->wrcaller
, pp
->wrid
, pp
->wrcum
);
535 /*===========================================================================*
537 *===========================================================================*/
538 static int pty_slave_open(tty_t
*tp
, int UNUSED(try))
540 /* The tty side has been opened. */
541 pty_t
*pp
= tp
->tty_priv
;
543 assert(tp
->tty_minor
>= TTYPX_MINOR
&& tp
->tty_minor
< TTYPX_MINOR
+ NR_PTYS
);
545 /* TTY_ACTIVE may already be set, which would indicate that the slave is
546 * reopened after being fully closed while the master is still open. In that
547 * case TTY_CLOSED will also be set, so clear that one.
549 pp
->state
|= TTY_ACTIVE
;
550 pp
->state
&= ~TTY_CLOSED
;
555 /*===========================================================================*
557 *===========================================================================*/
558 static int pty_slave_close(tty_t
*tp
, int UNUSED(try))
560 /* The tty side has closed, so shut down the pty side. */
561 pty_t
*pp
= tp
->tty_priv
;
563 if (!(pp
->state
& PTY_ACTIVE
)) return 0;
565 if (pp
->rdleft
> 0) {
566 chardriver_reply_task(pp
->rdcaller
, pp
->rdid
, pp
->rdcum
);
567 pp
->rdleft
= pp
->rdcum
= 0;
571 if (pp
->wrleft
> 0) {
572 chardriver_reply_task(pp
->wrcaller
, pp
->wrid
, pp
->wrcum
);
573 pp
->wrleft
= pp
->wrcum
= 0;
577 if (pp
->state
& PTY_CLOSED
) pp
->state
= 0;
578 else pp
->state
|= TTY_CLOSED
;
583 /*===========================================================================*
584 * pty_slave_icancel *
585 *===========================================================================*/
586 static int pty_slave_icancel(tty_t
*tp
, int UNUSED(try))
588 /* Discard waiting input. */
589 pty_t
*pp
= tp
->tty_priv
;
591 if (pp
->wrleft
> 0) {
592 chardriver_reply_task(pp
->wrcaller
, pp
->wrid
, pp
->wrcum
+ pp
->wrleft
);
593 pp
->wrcum
= pp
->wrleft
= 0;
600 /*===========================================================================*
601 * pty_slave_ocancel *
602 *===========================================================================*/
603 static int pty_slave_ocancel(tty_t
*tp
, int UNUSED(try))
605 /* Drain the output buffer. */
606 pty_t
*pp
= tp
->tty_priv
;
609 pp
->otail
= pp
->ohead
;
614 /*===========================================================================*
616 *===========================================================================*/
617 void pty_init(tty_t
*tp
)
622 /* Associate PTY and TTY structures. */
623 line
= tp
- tty_table
;
624 pp
= tp
->tty_priv
= &pty_table
[line
];
630 /* Set up output queue. */
631 pp
->ohead
= pp
->otail
= pp
->obuf
;
633 /* Fill in TTY function hooks. */
634 tp
->tty_devread
= pty_slave_read
;
635 tp
->tty_devwrite
= pty_slave_write
;
636 tp
->tty_echo
= pty_slave_echo
;
637 tp
->tty_icancel
= pty_slave_icancel
;
638 tp
->tty_ocancel
= pty_slave_ocancel
;
639 tp
->tty_open
= pty_slave_open
;
640 tp
->tty_close
= pty_slave_close
;
641 tp
->tty_select_ops
= 0;