Line data Source code
1 : // SPDX-License-Identifier: GPL-2.0
2 : /*
3 : * KUnit test helpers for TTY drivers
4 : *
5 : * This file is included directly into tty_io.c when CONFIG_TTY_KUNIT_TESTS=y.
6 : * This allows the helper functions to access internal TTY functions like
7 : * tty_open() and tty_release() while providing exported symbols for use
8 : * by test modules.
9 : *
10 : * All functions are exported via EXPORT_SYMBOL_IF_KUNIT() so they are
11 : * only available when KUNIT is enabled, preventing pollution of the
12 : * production symbol table.
13 : *
14 : * Copyright (c) 2025 Abhinav Saxena <[email protected]>
15 : */
16 :
17 : #include <kunit/test.h>
18 : #include <kunit/visibility.h>
19 : #include <linux/fs.h>
20 : #include <linux/kdev_t.h>
21 : #include <linux/uio.h>
22 : #include <linux/tty.h>
23 : #include <linux/tty_driver.h>
24 : #include <linux/tty_flip.h>
25 : #include <linux/tty_ldisc.h>
26 : #include <linux/termios.h>
27 :
28 : #include "tests/tty_test_helpers.h"
29 :
30 :
31 : static struct cdev tty_cdev;
32 :
33 : /**
34 : * _tty_test_cleanup_release - KUnit cleanup action for TTY release
35 : * @data: Pointer to tty_test_fixture
36 : *
37 : * Internal cleanup function registered with kunit_add_action() to ensure
38 : * TTY is properly released even if test fails or exits early.
39 : * This prevents resource leaks and system instability.
40 : */
41 6 : static void _tty_test_cleanup_release(void *data)
42 : {
43 6 : struct tty_test_fixture *fx = data;
44 6 : int ret;
45 :
46 6 : if (!fx || !fx->opened || !fx->file || !fx->inode)
47 6 : return;
48 :
49 0 : ret = tty_release(fx->inode, fx->file);
50 0 : if (ret)
51 0 : pr_warn("TTY test cleanup failed: %d\n", ret);
52 0 : fx->opened = false;
53 6 : }
54 :
55 : /**
56 : * tty_test_create_fixture - Create a test fixture for TTY driver testing
57 : */
58 6 : struct tty_test_fixture *tty_test_create_fixture(struct kunit *test,
59 : struct tty_driver *driver,
60 : unsigned int index)
61 : {
62 6 : struct tty_test_fixture *fx;
63 :
64 6 : KUNIT_ASSERT_NOT_NULL(test, driver);
65 :
66 6 : fx = kunit_kzalloc(test, sizeof(*fx), GFP_KERNEL);
67 6 : KUNIT_ASSERT_NOT_NULL(test, fx);
68 :
69 6 : fx->test = test;
70 6 : fx->driver = driver;
71 6 : fx->dev = MKDEV(driver->major, driver->minor_start + index);
72 :
73 : /* Create synthetic VFS structures for real TTY operations */
74 6 : fx->file = kunit_kzalloc(test, sizeof(*fx->file), GFP_KERNEL);
75 6 : fx->inode = kunit_kzalloc(test, sizeof(*fx->inode), GFP_KERNEL);
76 6 : KUNIT_ASSERT_NOT_NULL(test, fx->file);
77 6 : KUNIT_ASSERT_NOT_NULL(test, fx->inode);
78 :
79 : /* Initialize as character device with appropriate permissions */
80 6 : init_special_inode(fx->inode, S_IFCHR | 0600, fx->dev);
81 6 : fx->inode->i_rdev = fx->dev;
82 6 : fx->inode->i_cdev = &tty_cdev;
83 6 : KUNIT_ASSERT_NOT_NULL(test, fx->inode->i_cdev);
84 :
85 6 : fx->file->f_flags = O_RDWR;
86 6 : fx->file->f_mode = FMODE_READ | FMODE_WRITE;
87 6 : fx->file->f_inode = fx->inode;
88 :
89 : /* Register cleanup before any operations that might fail */
90 6 : kunit_add_action(test, _tty_test_cleanup_release, fx);
91 :
92 6 : fx->opened = false;
93 12 : return fx;
94 6 : }
95 : EXPORT_SYMBOL_IF_KUNIT(tty_test_create_fixture);
96 :
97 : /**
98 : * tty_test_open - Open TTY through standard kernel path
99 : */
100 6 : int tty_test_open(struct tty_test_fixture *fx)
101 : {
102 6 : int ret;
103 :
104 6 : KUNIT_ASSERT_NOT_NULL(fx->test, fx);
105 6 : KUNIT_ASSERT_NOT_NULL(fx->test, fx->file);
106 6 : KUNIT_ASSERT_NOT_NULL(fx->test, fx->inode);
107 :
108 6 : ret = tty_open(fx->inode, fx->file);
109 6 : if (ret)
110 0 : return ret;
111 :
112 6 : fx->tty = file_tty(fx->file);
113 6 : KUNIT_ASSERT_NOT_NULL(fx->test, fx->tty);
114 :
115 : /* Verify the TTY is properly set up */
116 6 : KUNIT_EXPECT_TRUE(fx->test, !list_empty(&fx->tty->tty_files));
117 : /* Ldisc must now be fully installed */
118 6 : KUNIT_ASSERT_NOT_NULL(fx->test, fx->tty->ldisc);
119 6 : KUNIT_EXPECT_TRUE(fx->test, fx->tty->ldisc->ops);
120 6 : KUNIT_ASSERT_NOT_NULL(fx->test, fx->tty->disc_data);
121 6 : KUNIT_EXPECT_NOT_NULL(fx->test, fx->tty->port);
122 :
123 6 : fx->port = fx->tty->port;
124 6 : ret = fx->tty->ldisc->ops->open(fx->tty);
125 6 : if (ret) {
126 0 : tty_release(fx->inode, fx->file);
127 0 : return ret;
128 : }
129 :
130 : /* Enable non-blocking mode for predictable test behavior */
131 6 : fx->file->f_flags |= O_NONBLOCK;
132 6 : fx->opened = true;
133 6 : return 0;
134 6 : }
135 : EXPORT_SYMBOL_IF_KUNIT(tty_test_open);
136 :
137 : /**
138 : * tty_test_release - Close TTY through standard kernel path
139 : */
140 6 : int tty_test_release(struct tty_test_fixture *fx)
141 : {
142 6 : int ret;
143 :
144 6 : if (!fx || !fx->opened)
145 0 : return 0;
146 :
147 : /*
148 : * This calls the internal tty_release() function directly.
149 : * This works because this code is compiled as part of tty_io.c.
150 : */
151 6 : ret = tty_release(fx->inode, fx->file);
152 6 : if (!ret) {
153 6 : fx->opened = false;
154 6 : fx->tty = NULL;
155 6 : fx->port = NULL;
156 6 : }
157 6 : return ret;
158 6 : }
159 : EXPORT_SYMBOL_IF_KUNIT(tty_test_release);
160 :
161 : /**
162 : * tty_test_write - Write data to TTY
163 : */
164 5 : ssize_t tty_test_write(struct tty_test_fixture *fx, const void *buf,
165 : size_t count)
166 : {
167 5 : struct kiocb iocb;
168 5 : struct iov_iter from;
169 5 : struct kvec kvec = { .iov_base = (void *)buf, .iov_len = count };
170 :
171 5 : KUNIT_ASSERT_NOT_NULL(fx->test, fx);
172 5 : KUNIT_ASSERT_NOT_NULL(fx->test, fx->file);
173 5 : KUNIT_ASSERT_TRUE(fx->test, fx->opened);
174 :
175 5 : init_sync_kiocb(&iocb, fx->file);
176 5 : iov_iter_kvec(&from, WRITE, &kvec, 1, count);
177 :
178 : /* tty_write() is exported, so this works */
179 10 : return tty_write(&iocb, &from);
180 5 : }
181 : EXPORT_SYMBOL_IF_KUNIT(tty_test_write);
182 :
183 : /**
184 : * tty_test_write_all - Write all data or fail
185 : */
186 1 : int tty_test_write_all(struct tty_test_fixture *fx, const void *buf, size_t len)
187 : {
188 1 : size_t off = 0;
189 1 : int retries = 10;
190 :
191 1 : KUNIT_ASSERT_NOT_NULL(fx->test, fx);
192 1 : KUNIT_ASSERT_TRUE(fx->test, fx->opened);
193 :
194 2 : while (off < len && retries--) {
195 2 : ssize_t n =
196 1 : tty_test_write(fx, (const char *)buf + off, len - off);
197 1 : if (n < 0)
198 0 : return n;
199 1 : if (n == 0) {
200 : /* No progress - prevent infinite loop */
201 0 : if (--retries <= 0) {
202 0 : KUNIT_FAIL(fx->test,
203 : "Write stalled after %zu bytes",
204 : off);
205 0 : return -EIO;
206 : }
207 0 : continue;
208 : }
209 1 : off += n;
210 1 : }
211 :
212 1 : if (off < len) {
213 0 : KUNIT_FAIL(fx->test, "Incomplete write: %zu/%zu bytes", off,
214 : len);
215 0 : return -EIO;
216 : }
217 :
218 1 : return 0;
219 1 : }
220 : EXPORT_SYMBOL_IF_KUNIT(tty_test_write_all);
221 :
222 : /**
223 : * tty_test_read - Read data from TTY (non-blocking)
224 : */
225 0 : ssize_t tty_test_read(struct tty_test_fixture *fx, void *buf, size_t count)
226 : {
227 0 : struct kiocb iocb;
228 0 : struct iov_iter to;
229 0 : struct kvec kvec = { .iov_base = buf, .iov_len = count };
230 :
231 0 : KUNIT_ASSERT_NOT_NULL(fx->test, fx);
232 0 : KUNIT_ASSERT_NOT_NULL(fx->test, fx->file);
233 0 : KUNIT_ASSERT_TRUE(fx->test, fx->opened);
234 :
235 0 : init_sync_kiocb(&iocb, fx->file);
236 0 : iov_iter_kvec(&to, READ, &kvec, 1, count);
237 :
238 0 : return tty_read(&iocb, &to);
239 0 : }
240 : EXPORT_SYMBOL_IF_KUNIT(tty_test_read);
241 :
242 : /**
243 : * tty_test_read_all - Attempt to read all requested data
244 : */
245 0 : ssize_t tty_test_read_all(struct tty_test_fixture *fx, void *buf, size_t want)
246 : {
247 0 : size_t off = 0;
248 0 : int tries = 8;
249 :
250 0 : KUNIT_ASSERT_NOT_NULL(fx->test, fx);
251 0 : KUNIT_ASSERT_TRUE(fx->test, fx->opened);
252 :
253 0 : while (off < want && tries--) {
254 0 : ssize_t n = tty_test_read(fx, (char *)buf + off, want - off);
255 :
256 0 : if (n == -EAGAIN)
257 0 : continue;
258 0 : if (n < 0)
259 0 : return n;
260 0 : if (n == 0)
261 0 : continue;
262 0 : off += n;
263 0 : }
264 0 : return off;
265 0 : }
266 : EXPORT_SYMBOL_IF_KUNIT(tty_test_read_all);
267 :
268 : /**
269 : * tty_test_simulate_rx - Inject received data for testing
270 : */
271 1 : int tty_test_simulate_rx(struct tty_test_fixture *fx, const unsigned char *data,
272 : size_t len)
273 : {
274 1 : int ret;
275 :
276 1 : KUNIT_ASSERT_NOT_NULL(fx->test, fx);
277 1 : KUNIT_ASSERT_NOT_NULL(fx->test, fx->port);
278 1 : KUNIT_ASSERT_TRUE(fx->test, fx->opened);
279 :
280 1 : ret = tty_insert_flip_string(fx->port, data, len);
281 1 : if (ret > 0)
282 1 : tty_flip_buffer_push(fx->port);
283 :
284 2 : return ret;
285 1 : }
286 : EXPORT_SYMBOL_IF_KUNIT(tty_test_simulate_rx);
287 :
288 : /**
289 : * tty_fx_supports_rx - Check if fixture supports RX testing
290 : */
291 1 : bool tty_fx_supports_rx(const struct tty_test_fixture *fx)
292 : {
293 1 : struct tty_ldisc *ld;
294 1 : const struct tty_ldisc_ops *ops;
295 :
296 1 : if (!fx || !fx->tty || !fx->opened)
297 0 : return false;
298 :
299 1 : ld = tty_ldisc_ref(fx->tty);
300 1 : if (!ld)
301 0 : return false;
302 :
303 1 : ops = READ_ONCE(ld->ops);
304 1 : if (ops && (ops->receive_buf || ops->receive_buf2)) {
305 1 : tty_ldisc_deref(ld);
306 1 : return true;
307 : }
308 :
309 0 : tty_ldisc_deref(ld);
310 0 : return false;
311 1 : }
312 : EXPORT_SYMBOL_IF_KUNIT(tty_fx_supports_rx);
313 :
314 : /**
315 : * tty_test_assert_valid_ops - Validate driver has required operations
316 : */
317 2 : void tty_test_assert_valid_ops(struct kunit *test,
318 : const struct tty_driver *driver)
319 : {
320 2 : KUNIT_ASSERT_NOT_NULL(test, driver);
321 2 : KUNIT_ASSERT_NOT_NULL(test, driver->ops);
322 2 : KUNIT_ASSERT_NOT_NULL(test, driver->ops->open);
323 2 : KUNIT_ASSERT_NOT_NULL(test, driver->ops->close);
324 2 : KUNIT_ASSERT_NOT_NULL(test, driver->ops->write);
325 2 : KUNIT_ASSERT_NOT_NULL(test, driver->ops->write_room);
326 2 : KUNIT_EXPECT_TRUE(test, driver->flags & TTY_DRIVER_INSTALLED);
327 2 : }
328 : EXPORT_SYMBOL_IF_KUNIT(tty_test_assert_valid_ops);
329 :
330 : /**
331 : * tty_test_get_chars_in_buffer - Get number of chars in output buffer
332 : */
333 1 : unsigned int tty_test_get_chars_in_buffer(struct tty_test_fixture *fx)
334 : {
335 1 : KUNIT_ASSERT_NOT_NULL(fx->test, fx);
336 1 : KUNIT_ASSERT_TRUE(fx->test, fx->opened);
337 1 : KUNIT_ASSERT_NOT_NULL(fx->test, fx->tty);
338 :
339 1 : if (fx->tty->ops->chars_in_buffer)
340 0 : return fx->tty->ops->chars_in_buffer(fx->tty);
341 :
342 1 : return 0;
343 1 : }
344 : EXPORT_SYMBOL_IF_KUNIT(tty_test_get_chars_in_buffer);
345 :
346 : /**
347 : * tty_test_get_write_room - Get available write room
348 : */
349 2 : unsigned int tty_test_get_write_room(struct tty_test_fixture *fx)
350 : {
351 2 : KUNIT_ASSERT_NOT_NULL(fx->test, fx);
352 2 : KUNIT_ASSERT_TRUE(fx->test, fx->opened);
353 2 : KUNIT_ASSERT_NOT_NULL(fx->test, fx->tty);
354 :
355 2 : if (fx->tty->ops->write_room)
356 2 : return fx->tty->ops->write_room(fx->tty);
357 :
358 0 : return 0;
359 2 : }
360 : EXPORT_SYMBOL_IF_KUNIT(tty_test_get_write_room);
361 :
362 : /**
363 : * tty_test_set_termios - Set terminal attributes for testing
364 : */
365 5 : int tty_test_set_termios(struct tty_test_fixture *fx,
366 : const struct ktermios *termios)
367 : {
368 5 : struct ktermios old_termios;
369 :
370 5 : KUNIT_ASSERT_NOT_NULL(fx->test, fx);
371 5 : KUNIT_ASSERT_TRUE(fx->test, fx->opened);
372 5 : KUNIT_ASSERT_NOT_NULL(fx->test, fx->tty);
373 5 : KUNIT_ASSERT_NOT_NULL(fx->test, termios);
374 :
375 : /* Save old termios for potential restoration */
376 5 : old_termios = fx->tty->termios;
377 :
378 : /* Update termios */
379 5 : fx->tty->termios = *termios;
380 :
381 : /* Call driver's set_termios if it exists */
382 5 : if (fx->tty->ops->set_termios)
383 0 : fx->tty->ops->set_termios(fx->tty, &old_termios);
384 :
385 5 : return 0;
386 5 : }
387 : EXPORT_SYMBOL_IF_KUNIT(tty_test_set_termios);
|