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