1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) International Business Machines Corp., 2001
4 * Copyright (c) 2020 Petr Vorel <[email protected]>
5 * Copyright (c) 2023 Marius Kittler <[email protected]>
6 */
7
8 /*\
9 * [Description]
10 *
11 * Test TCGETA/TCGETS and TCSETA/TCSETS ioctl implementations for tty driver.
12 *
13 * In this test, the parent and child open the parentty and the childtty
14 * respectively. After opening the childtty the child flushes the stream
15 * and wakes the parent (thereby asking it to continue its testing). The
16 * parent, then starts the testing. It issues a TCGETA/TCGETS ioctl to
17 * get all the tty parameters. It then changes them to known values by
18 * issuing a TCSETA/TCSETS ioctl. Then the parent issues a TCSETA/TCGETS
19 * ioctl again and compares the received values with what it had set
20 * earlier. The test fails if TCGETA/TCGETS or TCSETA/TCSETS fails, or if
21 * the received values don't match those that were set. The parent does
22 * all the testing, the requirement of the child process is to moniter
23 * the testing done by the parent, and hence the child just waits for the
24 * parent.
25 */
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <asm/termbits.h>
30
31 #include "lapi/ioctl.h"
32 #include "tst_test.h"
33
34 static struct termio termio, termio_exp;
35 static struct termios termios, termios_exp, termios_bak;
36
37 static char *parenttty, *childtty;
38 static int parentfd = -1;
39 static int parentpid, childpid;
40
41 static void do_child(void);
42 static void prepare_termio(void);
43 static void run_ptest(void);
44 static void chk_tty_parms_termio(void);
45 static void chk_tty_parms_termios(void);
46 static void setup(void);
47 static void cleanup(void);
48
49 static char *device;
50
51 static struct variant {
52 const char *name;
53 void *termio, *termio_exp, *termio_bak;
54 size_t termio_size;
55 void (*check)(void);
56 int tcget, tcset;
57 } variants[] = {
58 {
59 .name = "termio",
60 .termio = &termio,
61 .termio_exp = &termio_exp,
62 .termio_size = sizeof(termio),
63 .check = &chk_tty_parms_termio,
64 .tcget = TCGETA,
65 .tcset = TCSETA,
66 },
67 {
68 .name = "termios",
69 .termio = &termios,
70 .termio_exp = &termios_exp,
71 .termio_size = sizeof(termios),
72 .check = &chk_tty_parms_termios,
73 .tcget = TCGETS,
74 .tcset = TCSETS,
75 },
76 };
77
verify_ioctl(void)78 static void verify_ioctl(void)
79 {
80 tst_res(TINFO, "Testing %s variant", variants[tst_variant].name);
81
82 parenttty = device;
83 childtty = device;
84
85 parentpid = getpid();
86 childpid = SAFE_FORK();
87 if (!childpid) {
88 do_child();
89 exit(EXIT_SUCCESS);
90 }
91
92 TST_CHECKPOINT_WAIT(0);
93
94 parentfd = SAFE_OPEN(parenttty, O_RDWR, 0777);
95 SAFE_IOCTL(parentfd, TCFLSH, TCIOFLUSH);
96
97 run_ptest();
98
99 TST_CHECKPOINT_WAKE(0);
100 SAFE_CLOSE(parentfd);
101 }
102
prepare_termio(void)103 static void prepare_termio(void)
104 {
105 /* Use "old" line discipline */
106 termios_exp.c_line = termio_exp.c_line = 0;
107
108 /* Set control modes */
109 termios_exp.c_cflag = termio_exp.c_cflag = B50 | CS7 | CREAD | PARENB | PARODD | CLOCAL;
110
111 /* Set control chars. */
112 for (int i = 0; i < NCC; i++)
113 termio_exp.c_cc[i] = CSTART;
114 for (int i = 0; i < VEOL2; i++)
115 termios_exp.c_cc[i] = CSTART;
116
117 /* Set local modes. */
118 termios_exp.c_lflag = termio_exp.c_lflag =
119 ((unsigned short)(ISIG | ICANON | XCASE | ECHO | ECHOE | NOFLSH));
120
121 /* Set input modes. */
122 termios_exp.c_iflag = termio_exp.c_iflag =
123 BRKINT | IGNPAR | INPCK | ISTRIP | ICRNL | IUCLC | IXON | IXANY |
124 IXOFF;
125
126 /* Set output modes. */
127 termios_exp.c_oflag = termio_exp.c_oflag = OPOST | OLCUC | ONLCR | ONOCR;
128 }
129
130 /*
131 * run_ptest() - setup the various termio/termios structure values and issue
132 * the TCSETA/TCSETS ioctl call with the TEST macro.
133 */
run_ptest(void)134 static void run_ptest(void)
135 {
136 struct variant *v = &variants[tst_variant];
137
138 /* Init termio/termios structures used to check if all params got set */
139 memset(v->termio, 0, v->termio_size);
140
141 SAFE_IOCTL(parentfd, v->tcset, v->termio_exp);
142
143 /* Get termio and see if all parameters actually got set */
144 SAFE_IOCTL(parentfd, v->tcget, v->termio);
145 v->check();
146 }
147
cmp_attr(unsigned long long exp,unsigned long long act,const char * attr)148 static int cmp_attr(unsigned long long exp, unsigned long long act, const char *attr)
149 {
150 if (act == exp)
151 return 0;
152 tst_res(TFAIL, "%s has incorrect value %llu, expected %llu",
153 attr, act, exp);
154 return 1;
155 }
156
cmp_c_cc(unsigned char * exp_c_cc,unsigned char * act_c_cc,int ncc)157 static int cmp_c_cc(unsigned char *exp_c_cc, unsigned char *act_c_cc, int ncc)
158 {
159 int i, fails = 0;
160 char what[32];
161
162 for (i = 0; i < ncc; ++i) {
163 sprintf(what, "control char %d", i);
164 fails += cmp_attr(exp_c_cc[i], act_c_cc[i], what);
165 }
166 return fails;
167 }
168
169 #define CMP_ATTR(term_exp, term, attr, flag) \
170 ({ \
171 flag += cmp_attr((term_exp).attr, (term).attr, #attr); \
172 flag; \
173 })
174
175 #define CMP_C_CC(term_exp, term, flag) \
176 ({ \
177 flag += cmp_c_cc(term_exp.c_cc, term.c_cc, sizeof(term.c_cc)); \
178 flag; \
179 })
180
chk_tty_parms_termio(void)181 static void chk_tty_parms_termio(void)
182 {
183 int flag = 0;
184
185 flag = CMP_ATTR(termio_exp, termio, c_line, flag);
186 flag = CMP_C_CC(termio_exp, termio, flag);
187 flag = CMP_ATTR(termio_exp, termio, c_lflag, flag);
188 flag = CMP_ATTR(termio_exp, termio, c_iflag, flag);
189 flag = CMP_ATTR(termio_exp, termio, c_oflag, flag);
190
191 if (!flag)
192 tst_res(TPASS, "TCGETA/TCSETA tests");
193 }
194
chk_tty_parms_termios(void)195 static void chk_tty_parms_termios(void)
196 {
197 int flag = 0;
198
199 flag = CMP_ATTR(termios_exp, termios, c_line, flag);
200 flag = CMP_C_CC(termios_exp, termios, flag);
201 flag = CMP_ATTR(termios_exp, termios, c_lflag, flag);
202 flag = CMP_ATTR(termios_exp, termios, c_iflag, flag);
203 flag = CMP_ATTR(termios_exp, termios, c_oflag, flag);
204
205 if (!flag)
206 tst_res(TPASS, "TCGETS/TCSETS tests");
207 }
208
do_child(void)209 static void do_child(void)
210 {
211 int cfd = SAFE_OPEN(childtty, O_RDWR, 0777);
212
213 SAFE_IOCTL(cfd, TCFLSH, TCIOFLUSH);
214
215 /* tell the parent that we're done */
216 TST_CHECKPOINT_WAKE(0);
217
218 TST_CHECKPOINT_WAIT(0);
219 tst_res(TINFO, "child: parent has finished testing");
220 SAFE_CLOSE(cfd);
221 }
222
setup(void)223 static void setup(void)
224 {
225 if (!device)
226 tst_brk(TBROK, "You must specify a tty device with -d option");
227
228 int fd = SAFE_OPEN(device, O_RDWR, 0777);
229
230 SAFE_IOCTL(fd, TCGETS, &termios_bak);
231 SAFE_CLOSE(fd);
232
233 prepare_termio();
234 }
235
cleanup(void)236 static void cleanup(void)
237 {
238 if (parentfd >= 0) {
239 SAFE_IOCTL(parentfd, TCSETS, &termios_bak);
240 SAFE_CLOSE(parentfd);
241 }
242 }
243
244 static struct tst_test test = {
245 .needs_root = 1,
246 .needs_checkpoints = 1,
247 .forks_child = 1,
248 .setup = setup,
249 .cleanup = cleanup,
250 .test_all = verify_ioctl,
251 .test_variants = 2,
252 .options = (struct tst_option[]) {
253 {"d:", &device, "Tty device. For example, /dev/tty[0-9]"},
254 {}
255 }
256 };
257