xref: /illumos-gate/usr/src/uts/common/io/tty_common.c (revision a6bde1a23b60f140c7ed78df979c2e22b1ed9b2c)
1 /*
2  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 /*
7  * Copyright (c) 1983 Regents of the University of California.
8  * All rights reserved. The Berkeley software License Agreement
9  * specifies the terms and conditions for redistribution.
10  */
11 
12 #include <sys/types.h>
13 #include <sys/param.h>
14 #include <sys/signal.h>
15 #include <sys/systm.h>
16 #include <sys/termio.h>
17 #include <sys/ttold.h>
18 #include <sys/stropts.h>
19 #include <sys/stream.h>
20 #include <sys/strsubr.h>
21 #include <sys/strsun.h>
22 #include <sys/tty.h>
23 #include <sys/kmem.h>
24 #include <sys/errno.h>
25 #include <sys/ddi.h>
26 #include <sys/sunddi.h>
27 #include <sys/esunddi.h>
28 
29 /*
30  * The default (sane) set of termios values, unless
31  * otherwise set by the user.
32  */
33 static struct termios default_termios = {
34 	BRKINT|ICRNL|IXON|IMAXBEL,		/* c_iflag */
35 	OPOST|ONLCR|TAB3,			/* c_oflag */
36 	B9600|CS8|CREAD,			/* c_cflag */
37 	ISIG|ICANON|IEXTEN|ECHO|ECHOK|ECHOE|ECHOKE|ECHOCTL, /* c_lflag */
38 	{
39 		CINTR,
40 		CQUIT,
41 		CERASE,
42 		CKILL,
43 		CEOF,
44 		CEOL,
45 		CEOL2,
46 		CNSWTCH,
47 		CSTART,
48 		CSTOP,
49 		CSUSP,
50 		CDSUSP,
51 		CRPRNT,
52 		CFLUSH,
53 		CWERASE,
54 		CLNEXT,
55 		CSTATUS
56 	}
57 };
58 
59 
60 static int termioval(char **, uint_t *, char *);
61 
62 void
63 ttycommon_close(tty_common_t *tc)
64 {
65 	mutex_enter(&tc->t_excl);
66 	tc->t_flags &= ~TS_XCLUDE;
67 	tc->t_readq = NULL;
68 	tc->t_writeq = NULL;
69 	if (tc->t_iocpending != NULL) {
70 		mblk_t *mp;
71 
72 		mp = tc->t_iocpending;
73 		tc->t_iocpending = NULL;
74 		mutex_exit(&tc->t_excl);
75 		/*
76 		 * We were holding an "ioctl" response pending the
77 		 * availability of an "mblk" to hold data to be passed up;
78 		 * another "ioctl" came through, which means that "ioctl"
79 		 * must have timed out or been aborted.
80 		 */
81 		freemsg(mp);
82 	} else
83 		mutex_exit(&tc->t_excl);
84 }
85 
86 /*
87  * A "line discipline" module's queue is full.
88  * Check whether IMAXBEL is set; if so, output a ^G, otherwise send an M_FLUSH
89  * upstream flushing all the read queues.
90  */
91 void
92 ttycommon_qfull(tty_common_t *tc, queue_t *q)
93 {
94 	mblk_t *mp;
95 
96 	if (tc->t_iflag & IMAXBEL) {
97 		if (canput(WR(q))) {
98 			if ((mp = allocb(1, BPRI_HI)) != NULL) {
99 				*mp->b_wptr++ = CTRL('g');
100 				(void) putq(WR(q), mp);
101 			}
102 		}
103 	} else {
104 		flushq(q, FLUSHDATA);
105 		(void) putnextctl1(q, M_FLUSH, FLUSHR);
106 	}
107 }
108 
109 /*
110  * Process an "ioctl" message sent down to us, and return a reply message,
111  * even if we don't understand the "ioctl".  Our client may want to use
112  * that reply message for its own purposes if we don't understand it but
113  * they do, and may want to modify it if we both understand it but they
114  * understand it better than we do.
115  * If the "ioctl" reply requires additional data to be passed up to the
116  * caller, and we cannot allocate an mblk to hold the data, we return the
117  * amount of data to be sent, so that our caller can do a "bufcall" and try
118  * again later; otherwise, we return 0.
119  */
120 size_t
121 ttycommon_ioctl(tty_common_t *tc, queue_t *q, mblk_t *mp, int *errorp)
122 {
123 	struct iocblk *iocp;
124 	size_t ioctlrespsize;
125 	mblk_t *tmp;
126 
127 	*errorp = 0;	/* no error detected yet */
128 
129 	iocp = (struct iocblk *)mp->b_rptr;
130 
131 	if (iocp->ioc_count == TRANSPARENT) {
132 		*errorp = -1;	/* we don't understand it, maybe they do */
133 		return (0);
134 	}
135 
136 	switch (iocp->ioc_cmd) {
137 
138 	case TCSETSF:
139 		/*
140 		 * Flush the driver's queue, and send an M_FLUSH upstream
141 		 * to flush everybody above us.
142 		 */
143 		flushq(RD(q), FLUSHDATA);
144 		(void) putnextctl1(RD(q), M_FLUSH, FLUSHR);
145 		/* FALLTHROUGH */
146 
147 	case TCSETSW:
148 	case TCSETS: {
149 		struct termios *cb;
150 
151 		if (miocpullup(mp, sizeof (struct termios)) != 0) {
152 			*errorp = -1;
153 			break;
154 		}
155 
156 		/*
157 		 * The only information we look at are the iflag word,
158 		 * the cflag word, and the start and stop characters.
159 		 */
160 		cb = (struct termios *)mp->b_cont->b_rptr;
161 		mutex_enter(&tc->t_excl);
162 		tc->t_iflag = cb->c_iflag;
163 		tc->t_cflag = cb->c_cflag;
164 		tc->t_stopc = cb->c_cc[VSTOP];
165 		tc->t_startc = cb->c_cc[VSTART];
166 		mutex_exit(&tc->t_excl);
167 		break;
168 	}
169 
170 	case TCSETAF:
171 		/*
172 		 * Flush the driver's queue, and send an M_FLUSH upstream
173 		 * to flush everybody above us.
174 		 */
175 		flushq(RD(q), FLUSHDATA);
176 		(void) putnextctl1(RD(q), M_FLUSH, FLUSHR);
177 		/* FALLTHROUGH */
178 
179 	case TCSETAW:
180 	case TCSETA: {
181 		struct termio *cb;
182 
183 		if (miocpullup(mp, sizeof (struct termio)) != 0) {
184 			*errorp = -1;
185 			break;
186 		}
187 
188 		/*
189 		 * The only information we look at are the iflag word
190 		 * and the cflag word.  Don't touch the unset portions.
191 		 */
192 		cb = (struct termio *)mp->b_cont->b_rptr;
193 		mutex_enter(&tc->t_excl);
194 		tc->t_iflag = (tc->t_iflag & 0xffff0000 | cb->c_iflag);
195 		tc->t_cflag = (tc->t_cflag & 0xffff0000 | cb->c_cflag);
196 		mutex_exit(&tc->t_excl);
197 		break;
198 	}
199 
200 	case TIOCSWINSZ: {
201 		struct winsize *ws;
202 
203 		if (miocpullup(mp, sizeof (struct winsize)) != 0) {
204 			*errorp = -1;
205 			break;
206 		}
207 
208 		/*
209 		 * If the window size changed, send a SIGWINCH.
210 		 */
211 		ws = (struct winsize *)mp->b_cont->b_rptr;
212 		mutex_enter(&tc->t_excl);
213 		if (bcmp(&tc->t_size, ws, sizeof (struct winsize)) != 0) {
214 			tc->t_size = *ws;
215 			mutex_exit(&tc->t_excl);
216 			(void) putnextctl1(RD(q), M_PCSIG, SIGWINCH);
217 		} else
218 			mutex_exit(&tc->t_excl);
219 		break;
220 	}
221 
222 	/*
223 	 * Prevent more opens.
224 	 */
225 	case TIOCEXCL:
226 		mutex_enter(&tc->t_excl);
227 		tc->t_flags |= TS_XCLUDE;
228 		mutex_exit(&tc->t_excl);
229 		break;
230 
231 	/*
232 	 * Permit more opens.
233 	 */
234 	case TIOCNXCL:
235 		mutex_enter(&tc->t_excl);
236 		tc->t_flags &= ~TS_XCLUDE;
237 		mutex_exit(&tc->t_excl);
238 		break;
239 
240 	/*
241 	 * Set or clear the "soft carrier" flag.
242 	 */
243 	case TIOCSSOFTCAR:
244 		if (miocpullup(mp, sizeof (int)) != 0) {
245 			*errorp = -1;
246 			break;
247 		}
248 
249 		mutex_enter(&tc->t_excl);
250 		if (*(int *)mp->b_cont->b_rptr)
251 			tc->t_flags |= TS_SOFTCAR;
252 		else
253 			tc->t_flags &= ~TS_SOFTCAR;
254 		mutex_exit(&tc->t_excl);
255 		break;
256 
257 	/*
258 	 * The permission checking has already been done at the stream
259 	 * head, since it has to be done in the context of the process
260 	 * doing the call.
261 	 */
262 	case TIOCSTI: {
263 		mblk_t *bp;
264 
265 		if (miocpullup(mp, sizeof (char)) != 0) {
266 			*errorp = -1;
267 			break;
268 		}
269 
270 		/*
271 		 * Simulate typing of a character at the terminal.
272 		 */
273 		if ((bp = allocb(1, BPRI_MED)) != NULL) {
274 			if (!canput(tc->t_readq->q_next))
275 				freemsg(bp);
276 			else {
277 				*bp->b_wptr++ = *mp->b_cont->b_rptr;
278 				putnext(tc->t_readq, bp);
279 			}
280 		}
281 		break;
282 	}
283 	}
284 
285 	/*
286 	 * Turn the ioctl message into an ioctl ACK message.
287 	 */
288 	iocp->ioc_count = 0;	/* no data returned unless we say so */
289 	mp->b_datap->db_type = M_IOCACK;
290 
291 	switch (iocp->ioc_cmd) {
292 
293 	case TCSETSF:
294 	case TCSETSW:
295 	case TCSETS:
296 	case TCSETAF:
297 	case TCSETAW:
298 	case TCSETA:
299 	case TIOCSWINSZ:
300 	case TIOCEXCL:
301 	case TIOCNXCL:
302 	case TIOCSSOFTCAR:
303 	case TIOCSTI:
304 		/*
305 		 * We've done all the important work on these already;
306 		 * just reply with an ACK.
307 		 */
308 		break;
309 
310 	case TCGETS: {
311 		struct termios *cb;
312 		mblk_t *datap;
313 
314 		if ((datap = allocb(sizeof (struct termios),
315 		    BPRI_HI)) == NULL) {
316 			ioctlrespsize = sizeof (struct termios);
317 			goto allocfailure;
318 		}
319 		cb = (struct termios *)datap->b_wptr;
320 		/*
321 		 * The only information we supply is the cflag word.
322 		 * Our copy of the iflag word is just that, a copy.
323 		 */
324 		bzero(cb, sizeof (struct termios));
325 		cb->c_cflag = tc->t_cflag;
326 		datap->b_wptr += sizeof (struct termios);
327 		iocp->ioc_count = sizeof (struct termios);
328 		if (mp->b_cont != NULL)
329 			freemsg(mp->b_cont);
330 		mp->b_cont = datap;
331 		break;
332 	}
333 
334 	case TCGETA: {
335 		struct termio *cb;
336 		mblk_t *datap;
337 
338 		if ((datap = allocb(sizeof (struct termio), BPRI_HI)) == NULL) {
339 			ioctlrespsize = sizeof (struct termio);
340 			goto allocfailure;
341 		}
342 
343 		cb = (struct termio *)datap->b_wptr;
344 		/*
345 		 * The only information we supply is the cflag word.
346 		 * Our copy of the iflag word is just that, a copy.
347 		 */
348 		bzero(cb, sizeof (struct termio));
349 		cb->c_cflag = tc->t_cflag;
350 		datap->b_wptr += sizeof (struct termio);
351 		iocp->ioc_count = sizeof (struct termio);
352 		if (mp->b_cont != NULL)
353 			freemsg(mp->b_cont);
354 		mp->b_cont = datap;
355 		break;
356 	}
357 
358 	/*
359 	 * Get the "soft carrier" flag.
360 	 */
361 	case TIOCGSOFTCAR: {
362 		mblk_t *datap;
363 
364 		if ((datap = allocb(sizeof (int), BPRI_HI)) == NULL) {
365 			ioctlrespsize = sizeof (int);
366 			goto allocfailure;
367 		}
368 		if (tc->t_flags & TS_SOFTCAR)
369 			*(int *)datap->b_wptr = 1;
370 		else
371 			*(int *)datap->b_wptr = 0;
372 		datap->b_wptr += sizeof (int);
373 		iocp->ioc_count = sizeof (int);
374 		if (mp->b_cont != NULL)
375 			freemsg(mp->b_cont);
376 		mp->b_cont = datap;
377 		break;
378 	}
379 
380 	case TIOCGWINSZ: {
381 		mblk_t *datap;
382 
383 		if ((datap = allocb(sizeof (struct winsize),
384 		    BPRI_HI)) == NULL) {
385 			ioctlrespsize = sizeof (struct winsize);
386 			goto allocfailure;
387 		}
388 		/*
389 		 * Return the current size.
390 		 */
391 		*(struct winsize *)datap->b_wptr = tc->t_size;
392 		datap->b_wptr += sizeof (struct winsize);
393 		iocp->ioc_count = sizeof (struct winsize);
394 		if (mp->b_cont != NULL)
395 			freemsg(mp->b_cont);
396 		mp->b_cont = datap;
397 		break;
398 	}
399 
400 	default:
401 		*errorp = -1;	/* we don't understand it, maybe they do */
402 		break;
403 	}
404 	return (0);
405 
406 allocfailure:
407 
408 	mutex_enter(&tc->t_excl);
409 	tmp = tc->t_iocpending;
410 	tc->t_iocpending = mp;	/* hold this ioctl */
411 	mutex_exit(&tc->t_excl);
412 	/*
413 	 * We needed to allocate something to handle this "ioctl", but
414 	 * couldn't; save this "ioctl" and arrange to get called back when
415 	 * it's more likely that we can get what we need.
416 	 * If there's already one being saved, throw it out, since it
417 	 * must have timed out.
418 	 */
419 	if (tmp != NULL)
420 		freemsg(tmp);
421 	return (ioctlrespsize);
422 }
423 
424 #define	NFIELDS	21	/* 16 control characters + 4 sets of modes */
425 
426 /*
427  * Init routine run from main at boot time.
428  * Creates a property in the "options" node that is
429  * the default set of termios modes upon driver open.
430  * If the property already existed, then it was
431  * defined in the options.conf file.  In this case we
432  * need to convert this string (stty -g style) to an
433  * actual termios structure and store the new property
434  * value.
435  */
436 
437 void
438 ttyinit()
439 {
440 	dev_info_t *dip;
441 	struct termios new_termios;
442 	struct termios *tp;
443 	char *property = "ttymodes";
444 	char **modesp, *cp;
445 	int i;
446 	uint_t val;
447 	uint_t len;
448 
449 
450 	/*
451 	 * If the termios defaults were NOT set up by the
452 	 * user via the options.conf file, create it using the
453 	 * "sane" set of termios modes.
454 	 * Note that if the property had been created via the
455 	 * options.conf file, it would have been created as
456 	 * a string property.  Since we would like to store
457 	 * a structure (termios) in this property, we need
458 	 * to change the property type to byte array.
459 	 */
460 	if (ddi_prop_lookup_string_array(DDI_DEV_T_ANY, ddi_root_node(), 0,
461 	    property, (char ***)&modesp, &len) != DDI_PROP_SUCCESS) {
462 
463 		if ((dip = ddi_find_devinfo("options", -1, 0)) == NULL) {
464 			cmn_err(CE_PANIC,
465 			    "ttyinit: Can't find options node!\n");
466 		}
467 		/*
468 		 * Create the property.
469 		 */
470 		if (ddi_prop_update_byte_array(DDI_DEV_T_NONE, dip,
471 		    property, (uchar_t *)&default_termios,
472 		    sizeof (struct termios)) != DDI_PROP_SUCCESS) {
473 			cmn_err(CE_PANIC, "ttyinit: can't create %s property\n",
474 			    property);
475 		}
476 		return;
477 	}
478 
479 	/*
480 	 * This property was already set in the options.conf
481 	 * file.  We must convert it from a "stty -g" string
482 	 * to an actual termios structure.
483 	 */
484 	bzero(&new_termios, sizeof (struct termios));
485 	tp = &new_termios;
486 	cp = *modesp;
487 	for (i = 0; i < NFIELDS; i++) {
488 		/*
489 		 * Check for bad field/string.
490 		 */
491 		if (termioval(&cp, &val, *modesp+strlen(*modesp)) == -1) {
492 			cmn_err(CE_WARN,
493 			    "ttyinit: property '%s' %s\n", property,
494 			    "set incorrectly, using sane value");
495 			tp = &default_termios;
496 			break;
497 		}
498 		switch (i) {
499 		case 0:
500 			new_termios.c_iflag = (tcflag_t)val;
501 			break;
502 		case 1:
503 			new_termios.c_oflag = (tcflag_t)val;
504 			break;
505 		case 2:
506 			new_termios.c_cflag = (tcflag_t)val;
507 			break;
508 		case 3:
509 			new_termios.c_lflag = (tcflag_t)val;
510 			break;
511 		default:
512 			new_termios.c_cc[i - 4] = (cc_t)val;
513 		}
514 	}
515 	if ((dip = ddi_find_devinfo("options", -1, 0)) == NULL) {
516 		cmn_err(CE_PANIC, "ttyinit: Can't find options node!\n");
517 	}
518 
519 	/*
520 	 * We need to create ttymode property as a byte array
521 	 * since it will be interpreted as a termios struct.
522 	 * The property was created as a string by default.
523 	 * So remove the old property and add the new one -
524 	 * otherwise we end up with two ttymodes properties.
525 	 */
526 	if (e_ddi_prop_remove(DDI_DEV_T_NONE, dip, property)
527 	    != DDI_PROP_SUCCESS) {
528 		cmn_err(CE_WARN, "ttyinit: cannot remove '%s' property\n",
529 		    property);
530 	}
531 	/*
532 	 * Store the new defaults.  Since, this property was
533 	 * autoconfig'ed, we must use e_ddi_prop_update_byte_array().
534 	 */
535 	if (e_ddi_prop_update_byte_array(DDI_DEV_T_NONE, dip, property,
536 	    (uchar_t *)tp, sizeof (struct termios)) != DDI_PROP_SUCCESS) {
537 		cmn_err(CE_PANIC, "ttyinit: cannot modify '%s' property\n",
538 		    property);
539 	}
540 	ddi_prop_free(modesp);
541 }
542 
543 /*
544  * Convert hex string representation of termios field
545  * to a uint_t.  Increments string pointer to the next
546  * field, and assigns value. Returns -1 if no more fields
547  * or an error.
548  */
549 
550 static int
551 termioval(char **sp, uint_t *valp, char *ep)
552 {
553 	char *s = *sp;
554 	uint_t digit;
555 
556 	if (s == 0)
557 		return (-1);
558 	*valp = 0;
559 	while (s < ep) {
560 		if (*s >= '0' && *s <= '9')
561 			digit = *s++ - '0';
562 		else if (*s >= 'a' && *s <= 'f')
563 			digit = *s++ - 'a' + 10;
564 		else if (*s >= 'A' && *s <= 'F')
565 			digit = *s++ - 'A' + 10;
566 		else if (*s == ':' || *s == '\0')
567 			break;
568 		else
569 			return (-1);
570 		*valp = (*valp * 16) + digit;
571 	}
572 	/*
573 	 * Null string or empty field.
574 	 */
575 	if (s == *sp)
576 		return (-1);
577 
578 	if (s < ep && *s == ':')
579 		s++;
580 
581 	*sp = s;
582 	return (0);
583 }
584