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_read() and pty_write() functions as the "keyboard" and
13 * "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 and the pty end of the pseudo tty. Writes to one
16 * end are to be read at the other end and vice-versa.
19 #include <minix/drivers.h>
23 #include <minix/com.h>
24 #include <minix/callnr.h>
25 #include <sys/select.h>
30 /* PTY bookkeeping structure, one per pty/tty pair. */
32 tty_t
*tty
; /* associated TTY structure */
33 char state
; /* flags: busy, closed, ... */
35 /* Read call on /dev/ptypX. */
36 char rdsendreply
; /* send a reply (instead of notify) */
37 int rdcaller
; /* process making the call (usually FS) */
38 int rdproc
; /* process that wants to read from the pty */
39 cp_grant_id_t rdgrant
; /* grant for readers address space */
40 vir_bytes rdoffset
; /* offset in above grant */
41 int rdleft
; /* # bytes yet to be read */
42 int rdcum
; /* # bytes written so far */
44 /* Write call to /dev/ptypX. */
45 char wrsendreply
; /* send a reply (instead of notify) */
46 int wrcaller
; /* process making the call (usually FS) */
47 int wrproc
; /* process that wants to write to the pty */
48 cp_grant_id_t wrgrant
; /* grant for writers address space */
49 vir_bytes wroffset
; /* offset in above grant */
50 int wrleft
; /* # bytes yet to be written */
51 int wrcum
; /* # bytes written so far */
54 int ocount
; /* # characters in the buffer */
55 char *ohead
, *otail
; /* head and tail of the circular buffer */
56 char obuf
[2048]; /* buffer for bytes going to the pty reader */
59 int select_ops
, /* Which operations do we want to know about? */
60 select_proc
, /* Who wants to know about it? */
61 select_ready_ops
; /* For callback. */
64 #define PTY_ACTIVE 0x01 /* pty is open/active */
65 #define TTY_CLOSED 0x02 /* tty side has closed down */
66 #define PTY_CLOSED 0x04 /* pty side has closed down */
68 static pty_t pty_table
[NR_PTYS
]; /* PTY bookkeeping */
70 static int pty_write(tty_t
*tp
, int try);
71 static void pty_echo(tty_t
*tp
, int c
);
72 static void pty_start(pty_t
*pp
);
73 static void pty_finish(pty_t
*pp
);
74 static int pty_read(tty_t
*tp
, int try);
75 static int pty_close(tty_t
*tp
, int try);
76 static int pty_icancel(tty_t
*tp
, int try);
77 static int pty_ocancel(tty_t
*tp
, int try);
78 static int pty_select(tty_t
*tp
, message
*m
);
80 /*===========================================================================*
82 *===========================================================================*/
83 void do_pty(tty_t
*tp
, message
*m_ptr
)
85 /* Perform an open/close/read/write call on a /dev/ptypX device. */
86 pty_t
*pp
= tp
->tty_priv
;
89 switch (m_ptr
->m_type
) {
91 /* Check, store information on the reader, do I/O. */
92 if (pp
->state
& TTY_CLOSED
) {
96 if (pp
->rdleft
!= 0 || pp
->rdcum
!= 0) {
100 if (m_ptr
->COUNT
<= 0) {
104 if (pp
->rdgrant
!= GRANT_INVALID
) {
108 pp
->rdsendreply
= TRUE
;
109 pp
->rdcaller
= m_ptr
->m_source
;
110 pp
->rdproc
= m_ptr
->USER_ENDPT
;
111 pp
->rdgrant
= (cp_grant_id_t
) m_ptr
->IO_GRANT
;
113 pp
->rdleft
= m_ptr
->COUNT
;
116 if (pp
->rdleft
== 0) {
117 pp
->rdgrant
= GRANT_INVALID
;
118 return; /* already done */
122 r
= SUSPEND
; /* do suspend */
123 pp
->rdsendreply
= FALSE
;
128 /* Check, store information on the writer, do I/O. */
129 if (pp
->state
& TTY_CLOSED
) {
133 if (pp
->wrleft
!= 0 || pp
->wrcum
!= 0) {
137 if (m_ptr
->COUNT
<= 0) {
141 if (pp
->wrgrant
!= GRANT_INVALID
) {
145 pp
->wrsendreply
= TRUE
;
146 pp
->wrcaller
= m_ptr
->m_source
;
147 pp
->wrproc
= m_ptr
->USER_ENDPT
;
148 pp
->wrgrant
= (cp_grant_id_t
) m_ptr
->IO_GRANT
;
150 pp
->wrleft
= m_ptr
->COUNT
;
152 if (pp
->wrleft
== 0) {
153 pp
->wrgrant
= GRANT_INVALID
;
154 return; /* already done */
158 pp
->wrsendreply
= FALSE
; /* do suspend */
164 r
= pp
->state
!= 0 ? EIO
: OK
;
165 pp
->state
|= PTY_ACTIVE
;
172 if (pp
->state
& TTY_CLOSED
) {
175 pp
->state
|= PTY_CLOSED
;
176 sigchar(tp
, SIGHUP
, 1);
181 r
= pty_select(tp
, m_ptr
);
186 if (m_ptr
->USER_ENDPT
== pp
->rdproc
) {
187 /* Cancel a read from a PTY. */
188 r
= pp
->rdcum
> 0 ? pp
->rdcum
: EAGAIN
;
189 pp
->rdleft
= pp
->rdcum
= 0;
190 pp
->rdgrant
= GRANT_INVALID
;
192 if (m_ptr
->USER_ENDPT
== pp
->wrproc
) {
193 /* Cancel a write to a PTY. */
194 r
= pp
->wrcum
> 0 ? pp
->wrcum
: EAGAIN
;
195 pp
->wrleft
= pp
->wrcum
= 0;
196 pp
->wrgrant
= GRANT_INVALID
;
203 tty_reply(TASK_REPLY
, m_ptr
->m_source
, m_ptr
->USER_ENDPT
, r
);
206 /*===========================================================================*
208 *===========================================================================*/
209 static int pty_write(tty_t
*tp
, int try)
211 /* (*dev_write)() routine for PTYs. Transfer bytes from the writer on
212 * /dev/ttypX to the output buffer.
214 pty_t
*pp
= tp
->tty_priv
;
215 int count
, ocount
, s
;
218 /* PTY closed down? */
219 if (pp
->state
& PTY_CLOSED
) {
221 if (tp
->tty_outleft
> 0) {
222 if(tp
->tty_outrepcode
== TTY_REVIVE
) {
223 notify(tp
->tty_outcaller
);
224 tp
->tty_outrevived
= 1;
226 tty_reply(tp
->tty_outrepcode
, tp
->tty_outcaller
,
227 tp
->tty_outproc
, EIO
);
228 tp
->tty_outleft
= tp
->tty_outcum
= 0;
234 /* While there is something to do. */
236 ocount
= buflen(pp
->obuf
) - pp
->ocount
;
237 if (try) return (ocount
> 0);
238 count
= bufend(pp
->obuf
) - pp
->ohead
;
239 if (count
> ocount
) count
= ocount
;
240 if (count
> tp
->tty_outleft
) count
= tp
->tty_outleft
;
241 if (count
== 0 || tp
->tty_inhibited
)
244 /* Copy from user space to the PTY output buffer. */
245 if (tp
->tty_outcaller
== KERNEL
) {
246 /* We're trying to print on kernel's behalf */
247 memcpy(pp
->ohead
, (void *) tp
->tty_outgrant
+ tp
->tty_outoffset
,
250 if ((s
= sys_safecopyfrom(tp
->tty_outcaller
, tp
->tty_outgrant
,
251 tp
->tty_outoffset
, (vir_bytes
) pp
->ohead
,
257 /* Perform output processing on the output buffer. */
258 out_process(tp
, pp
->obuf
, pp
->ohead
, bufend(pp
->obuf
), &count
, &ocount
);
259 if (count
== 0) break;
261 /* Assume echoing messed up by output. */
262 tp
->tty_reprint
= TRUE
;
265 pp
->ocount
+= ocount
;
266 if ((pp
->ohead
+= ocount
) >= bufend(pp
->obuf
))
267 pp
->ohead
-= buflen(pp
->obuf
);
270 tp
->tty_outoffset
+= count
;
272 tp
->tty_outcum
+= count
;
273 if ((tp
->tty_outleft
-= count
) == 0) {
274 /* Output is finished, reply to the writer. */
275 if(tp
->tty_outrepcode
== TTY_REVIVE
) {
276 notify(tp
->tty_outcaller
);
277 tp
->tty_outrevived
= 1;
279 tty_reply(tp
->tty_outrepcode
, tp
->tty_outcaller
,
280 tp
->tty_outproc
, tp
->tty_outcum
);
289 /*===========================================================================*
291 *===========================================================================*/
292 static void pty_echo(tty_t
*tp
, int c
)
294 /* Echo one character. (Like pty_write, but only one character, optionally.) */
296 pty_t
*pp
= tp
->tty_priv
;
299 ocount
= buflen(pp
->obuf
) - pp
->ocount
;
300 if (ocount
== 0) return; /* output buffer full */
302 *pp
->ohead
= c
; /* add one character */
304 out_process(tp
, pp
->obuf
, pp
->ohead
, bufend(pp
->obuf
), &count
, &ocount
);
305 if (count
== 0) return;
307 pp
->ocount
+= ocount
;
308 if ((pp
->ohead
+= ocount
) >= bufend(pp
->obuf
)) pp
->ohead
-= buflen(pp
->obuf
);
312 /*===========================================================================*
314 *===========================================================================*/
315 static void pty_start(pty_t
*pp
)
317 /* Transfer bytes written to the output buffer to the PTY reader. */
320 /* While there are things to do. */
323 count
= bufend(pp
->obuf
) - pp
->otail
;
324 if (count
> pp
->ocount
) count
= pp
->ocount
;
325 if (count
> pp
->rdleft
) count
= pp
->rdleft
;
326 if (count
== 0) break;
328 /* Copy from the output buffer to the readers address space. */
329 if((s
= sys_safecopyto(pp
->rdcaller
, pp
->rdgrant
,
330 pp
->rdoffset
, (vir_bytes
) pp
->otail
, count
)) != OK
) {
333 pp
->rdoffset
+= count
;
337 if ((pp
->otail
+= count
) == bufend(pp
->obuf
)) pp
->otail
= pp
->obuf
;
343 /*===========================================================================*
345 *===========================================================================*/
346 static void pty_finish(pty_t
*pp
)
348 /* Finish the read request of a PTY reader if there is at least one byte
352 if (pp
->rdsendreply
) {
353 tty_reply(TASK_REPLY
, pp
->rdcaller
, pp
->rdproc
, pp
->rdcum
);
354 pp
->rdleft
= pp
->rdcum
= 0;
357 notify(pp
->rdcaller
);
362 /*===========================================================================*
364 *===========================================================================*/
365 static int pty_read(tty_t
*tp
, int try)
367 /* Offer bytes from the PTY writer for input on the TTY. (Do it one byte at
368 * a time, 99% of the writes will be for one byte, so no sense in being smart.)
370 pty_t
*pp
= tp
->tty_priv
;
373 if (pp
->state
& PTY_CLOSED
) {
375 if (tp
->tty_inleft
> 0) {
376 if(tp
->tty_inrepcode
== TTY_REVIVE
) {
377 notify(tp
->tty_incaller
);
378 tp
->tty_inrevived
= 1;
380 tty_reply(tp
->tty_inrepcode
, tp
->tty_incaller
,
381 tp
->tty_inproc
, tp
->tty_incum
);
382 tp
->tty_inleft
= tp
->tty_incum
= 0;
394 while (pp
->wrleft
> 0) {
397 /* Transfer one character to 'c'. */
398 if ((s
= sys_safecopyfrom(pp
->wrcaller
, pp
->wrgrant
, pp
->wroffset
,
399 (vir_bytes
) &c
, 1)) != OK
) {
400 printf("pty: safecopy failed (error %d)\n", s
);
405 /* Input processing. */
406 if (in_process(tp
, &c
, 1, -1) == 0) break;
408 /* PTY writer bookkeeping. */
410 if (--pp
->wrleft
== 0) {
411 if (pp
->wrsendreply
) {
412 tty_reply(TASK_REPLY
, pp
->wrcaller
, pp
->wrproc
,
417 notify(pp
->wrcaller
);
424 /*===========================================================================*
426 *===========================================================================*/
427 static int pty_close(tty_t
*tp
, int UNUSED(try))
429 /* The tty side has closed, so shut down the pty side. */
430 pty_t
*pp
= tp
->tty_priv
;
432 if (!(pp
->state
& PTY_ACTIVE
)) return 0;
434 if (pp
->rdleft
> 0) {
435 assert(!pp
->rdsendreply
);
436 notify(pp
->rdcaller
);
439 if (pp
->wrleft
> 0) {
440 assert(!pp
->wrsendreply
);
441 notify(pp
->wrcaller
);
444 if (pp
->state
& PTY_CLOSED
) pp
->state
= 0; else pp
->state
|= TTY_CLOSED
;
449 /*===========================================================================*
451 *===========================================================================*/
452 static int pty_icancel(tty_t
*tp
, int UNUSED(try))
454 /* Discard waiting input. */
455 pty_t
*pp
= tp
->tty_priv
;
457 if (pp
->wrleft
> 0) {
458 pp
->wrcum
+= pp
->wrleft
;
460 notify(pp
->wrcaller
);
466 /*===========================================================================*
468 *===========================================================================*/
469 static int pty_ocancel(tty_t
*tp
, int UNUSED(try))
471 /* Drain the output buffer. */
472 pty_t
*pp
= tp
->tty_priv
;
475 pp
->otail
= pp
->ohead
;
480 /*===========================================================================*
482 *===========================================================================*/
483 void pty_init(tty_t
*tp
)
488 /* Associate PTY and TTY structures. */
489 line
= tp
- &tty_table
[NR_CONS
+ NR_RS_LINES
];
490 pp
= tp
->tty_priv
= &pty_table
[line
];
493 pp
->rdgrant
= GRANT_INVALID
;
494 pp
->wrgrant
= GRANT_INVALID
;
496 /* Set up output queue. */
497 pp
->ohead
= pp
->otail
= pp
->obuf
;
499 /* Fill in TTY function hooks. */
500 tp
->tty_devread
= pty_read
;
501 tp
->tty_devwrite
= pty_write
;
502 tp
->tty_echo
= pty_echo
;
503 tp
->tty_icancel
= pty_icancel
;
504 tp
->tty_ocancel
= pty_ocancel
;
505 tp
->tty_close
= pty_close
;
506 tp
->tty_select_ops
= 0;
509 /*===========================================================================*
511 *===========================================================================*/
512 int pty_status(message
*m_ptr
)
518 for (i
= 0, pp
= pty_table
; i
<NR_PTYS
; i
++, pp
++) {
519 if ((((pp
->state
& TTY_CLOSED
) && pp
->rdleft
> 0) ||
521 pp
->rdcaller
== m_ptr
->m_source
)
523 m_ptr
->m_type
= DEV_REVIVE
;
524 m_ptr
->REP_ENDPT
= pp
->rdproc
;
525 m_ptr
->REP_IO_GRANT
= pp
->rdgrant
;
526 m_ptr
->REP_STATUS
= pp
->rdcum
;
527 pp
->rdleft
= pp
->rdcum
= 0;
528 pp
->rdgrant
= GRANT_INVALID
;
533 if ((((pp
->state
& TTY_CLOSED
) && pp
->wrleft
> 0) ||
535 pp
->wrcaller
== m_ptr
->m_source
)
537 m_ptr
->m_type
= DEV_REVIVE
;
538 m_ptr
->REP_ENDPT
= pp
->wrproc
;
539 m_ptr
->REP_IO_GRANT
= pp
->wrgrant
;
541 m_ptr
->REP_STATUS
= EIO
;
543 m_ptr
->REP_STATUS
= pp
->wrcum
;
545 pp
->wrleft
= pp
->wrcum
= 0;
546 pp
->wrgrant
= GRANT_INVALID
;
551 if (pp
->select_ready_ops
&& pp
->select_proc
== m_ptr
->m_source
) {
552 m_ptr
->m_type
= DEV_IO_READY
;
553 m_ptr
->DEV_MINOR
= PTYPX_MINOR
+ i
;
554 m_ptr
->DEV_SEL_OPS
= pp
->select_ready_ops
;
555 pp
->select_ready_ops
= 0;
563 /*===========================================================================*
565 *===========================================================================*/
566 static int select_try_pty(tty_t
*tp
, int ops
)
568 pty_t
*pp
= tp
->tty_priv
;
572 /* Write won't block on error. */
573 if (pp
->state
& TTY_CLOSED
) r
|= SEL_WR
;
574 else if (pp
->wrleft
!= 0 || pp
->wrcum
!= 0) r
|= SEL_WR
;
579 /* Read won't block on error. */
580 if (pp
->state
& TTY_CLOSED
) r
|= SEL_RD
;
581 else if (pp
->rdleft
!= 0 || pp
->rdcum
!= 0) r
|= SEL_RD
;
582 else if (pp
->ocount
> 0) r
|= SEL_RD
; /* Actual data. */
588 /*===========================================================================*
590 *===========================================================================*/
591 void select_retry_pty(tty_t
*tp
)
593 pty_t
*pp
= tp
->tty_priv
;
596 /* See if the pty side of a pty is ready to return a select. */
597 if (pp
->select_ops
&& (r
=select_try_pty(tp
, pp
->select_ops
))) {
598 pp
->select_ops
&= ~r
;
599 pp
->select_ready_ops
|= r
;
600 notify(pp
->select_proc
);
604 /*===========================================================================*
606 *===========================================================================*/
607 static int pty_select(tty_t
*tp
, message
*m
)
609 pty_t
*pp
= tp
->tty_priv
;
610 int ops
, ready_ops
= 0, watch
;
612 ops
= m
->USER_ENDPT
& (SEL_RD
|SEL_WR
|SEL_ERR
);
613 watch
= (m
->USER_ENDPT
& SEL_NOTIFY
) ? 1 : 0;
615 ready_ops
= select_try_pty(tp
, ops
);
617 if (!ready_ops
&& ops
&& watch
) {
618 pp
->select_ops
|= ops
;
619 pp
->select_proc
= m
->m_source
;
625 #endif /* NR_PTYS > 0 */