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