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