ASoC: Merge up v6.6-rc7
[linux-block.git] / tools / testing / selftests / alsa / pcm-test.c
1 // SPDX-License-Identifier: GPL-2.0
2 //
3 // kselftest for the ALSA PCM API
4 //
5 // Original author: Jaroslav Kysela <perex@perex.cz>
6 // Copyright (c) 2022 Red Hat Inc.
7
8 // This test will iterate over all cards detected in the system, exercising
9 // every PCM device it can find.  This may conflict with other system
10 // software if there is audio activity so is best run on a system with a
11 // minimal active userspace.
12
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <stdbool.h>
16 #include <errno.h>
17 #include <assert.h>
18 #include <pthread.h>
19
20 #include "../kselftest.h"
21 #include "alsa-local.h"
22
23 typedef struct timespec timestamp_t;
24
25 struct card_data {
26         int card;
27         pthread_t thread;
28         struct card_data *next;
29 };
30
31 struct card_data *card_list = NULL;
32
33 struct pcm_data {
34         snd_pcm_t *handle;
35         int card;
36         int device;
37         int subdevice;
38         snd_pcm_stream_t stream;
39         snd_config_t *pcm_config;
40         struct pcm_data *next;
41 };
42
43 struct pcm_data *pcm_list = NULL;
44
45 int num_missing = 0;
46 struct pcm_data *pcm_missing = NULL;
47
48 snd_config_t *default_pcm_config;
49
50 /* Lock while reporting results since kselftest doesn't */
51 pthread_mutex_t results_lock = PTHREAD_MUTEX_INITIALIZER;
52
53 enum test_class {
54         TEST_CLASS_DEFAULT,
55         TEST_CLASS_SYSTEM,
56 };
57
58 void timestamp_now(timestamp_t *tstamp)
59 {
60         if (clock_gettime(CLOCK_MONOTONIC_RAW, tstamp))
61                 ksft_exit_fail_msg("clock_get_time\n");
62 }
63
64 long long timestamp_diff_ms(timestamp_t *tstamp)
65 {
66         timestamp_t now, diff;
67         timestamp_now(&now);
68         if (tstamp->tv_nsec > now.tv_nsec) {
69                 diff.tv_sec = now.tv_sec - tstamp->tv_sec - 1;
70                 diff.tv_nsec = (now.tv_nsec + 1000000000L) - tstamp->tv_nsec;
71         } else {
72                 diff.tv_sec = now.tv_sec - tstamp->tv_sec;
73                 diff.tv_nsec = now.tv_nsec - tstamp->tv_nsec;
74         }
75         return (diff.tv_sec * 1000) + ((diff.tv_nsec + 500000L) / 1000000L);
76 }
77
78 static long device_from_id(snd_config_t *node)
79 {
80         const char *id;
81         char *end;
82         long v;
83
84         if (snd_config_get_id(node, &id))
85                 ksft_exit_fail_msg("snd_config_get_id\n");
86         errno = 0;
87         v = strtol(id, &end, 10);
88         if (errno || *end)
89                 return -1;
90         return v;
91 }
92
93 static void missing_device(int card, int device, int subdevice, snd_pcm_stream_t stream)
94 {
95         struct pcm_data *pcm_data;
96
97         for (pcm_data = pcm_list; pcm_data != NULL; pcm_data = pcm_data->next) {
98                 if (pcm_data->card != card)
99                         continue;
100                 if (pcm_data->device != device)
101                         continue;
102                 if (pcm_data->subdevice != subdevice)
103                         continue;
104                 if (pcm_data->stream != stream)
105                         continue;
106                 return;
107         }
108         pcm_data = calloc(1, sizeof(*pcm_data));
109         if (!pcm_data)
110                 ksft_exit_fail_msg("Out of memory\n");
111         pcm_data->card = card;
112         pcm_data->device = device;
113         pcm_data->subdevice = subdevice;
114         pcm_data->stream = stream;
115         pcm_data->next = pcm_missing;
116         pcm_missing = pcm_data;
117         num_missing++;
118 }
119
120 static void missing_devices(int card, snd_config_t *card_config)
121 {
122         snd_config_t *pcm_config, *node1, *node2;
123         snd_config_iterator_t i1, i2, next1, next2;
124         int device, subdevice;
125
126         pcm_config = conf_get_subtree(card_config, "pcm", NULL);
127         if (!pcm_config)
128                 return;
129         snd_config_for_each(i1, next1, pcm_config) {
130                 node1 = snd_config_iterator_entry(i1);
131                 device = device_from_id(node1);
132                 if (device < 0)
133                         continue;
134                 if (snd_config_get_type(node1) != SND_CONFIG_TYPE_COMPOUND)
135                         continue;
136                 snd_config_for_each(i2, next2, node1) {
137                         node2 = snd_config_iterator_entry(i2);
138                         subdevice = device_from_id(node2);
139                         if (subdevice < 0)
140                                 continue;
141                         if (conf_get_subtree(node2, "PLAYBACK", NULL))
142                                 missing_device(card, device, subdevice, SND_PCM_STREAM_PLAYBACK);
143                         if (conf_get_subtree(node2, "CAPTURE", NULL))
144                                 missing_device(card, device, subdevice, SND_PCM_STREAM_CAPTURE);
145                 }
146         }
147 }
148
149 static void find_pcms(void)
150 {
151         char name[32], key[64];
152         char *card_name, *card_longname;
153         int card, dev, subdev, count, direction, err;
154         snd_pcm_stream_t stream;
155         struct pcm_data *pcm_data;
156         snd_ctl_t *handle;
157         snd_pcm_info_t *pcm_info;
158         snd_config_t *config, *card_config, *pcm_config;
159         struct card_data *card_data;
160
161         snd_pcm_info_alloca(&pcm_info);
162
163         card = -1;
164         if (snd_card_next(&card) < 0 || card < 0)
165                 return;
166
167         config = get_alsalib_config();
168
169         while (card >= 0) {
170                 sprintf(name, "hw:%d", card);
171
172                 err = snd_ctl_open_lconf(&handle, name, 0, config);
173                 if (err < 0) {
174                         ksft_print_msg("Failed to get hctl for card %d: %s\n",
175                                        card, snd_strerror(err));
176                         goto next_card;
177                 }
178
179                 err = snd_card_get_name(card, &card_name);
180                 if (err != 0)
181                         card_name = "Unknown";
182                 err = snd_card_get_longname(card, &card_longname);
183                 if (err != 0)
184                         card_longname = "Unknown";
185                 ksft_print_msg("Card %d - %s (%s)\n", card,
186                                card_name, card_longname);
187
188                 card_config = conf_by_card(card);
189
190                 card_data = calloc(1, sizeof(*card_data));
191                 if (!card_data)
192                         ksft_exit_fail_msg("Out of memory\n");
193                 card_data->card = card;
194                 card_data->next = card_list;
195                 card_list = card_data;
196
197                 dev = -1;
198                 while (1) {
199                         if (snd_ctl_pcm_next_device(handle, &dev) < 0)
200                                 ksft_exit_fail_msg("snd_ctl_pcm_next_device\n");
201                         if (dev < 0)
202                                 break;
203
204                         for (direction = 0; direction < 2; direction++) {
205                                 stream = direction ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK;
206                                 sprintf(key, "pcm.%d.%s", dev, snd_pcm_stream_name(stream));
207                                 pcm_config = conf_get_subtree(card_config, key, NULL);
208                                 if (conf_get_bool(card_config, key, "skip", false)) {
209                                         ksft_print_msg("skipping pcm %d.%d.%s\n", card, dev, snd_pcm_stream_name(stream));
210                                         continue;
211                                 }
212                                 snd_pcm_info_set_device(pcm_info, dev);
213                                 snd_pcm_info_set_subdevice(pcm_info, 0);
214                                 snd_pcm_info_set_stream(pcm_info, stream);
215                                 err = snd_ctl_pcm_info(handle, pcm_info);
216                                 if (err == -ENOENT)
217                                         continue;
218                                 if (err < 0)
219                                         ksft_exit_fail_msg("snd_ctl_pcm_info: %d:%d:%d\n",
220                                                            dev, 0, stream);
221                                 count = snd_pcm_info_get_subdevices_count(pcm_info);
222                                 for (subdev = 0; subdev < count; subdev++) {
223                                         sprintf(key, "pcm.%d.%d.%s", dev, subdev, snd_pcm_stream_name(stream));
224                                         if (conf_get_bool(card_config, key, "skip", false)) {
225                                                 ksft_print_msg("skipping pcm %d.%d.%d.%s\n", card, dev,
226                                                                subdev, snd_pcm_stream_name(stream));
227                                                 continue;
228                                         }
229                                         pcm_data = calloc(1, sizeof(*pcm_data));
230                                         if (!pcm_data)
231                                                 ksft_exit_fail_msg("Out of memory\n");
232                                         pcm_data->card = card;
233                                         pcm_data->device = dev;
234                                         pcm_data->subdevice = subdev;
235                                         pcm_data->stream = stream;
236                                         pcm_data->pcm_config = conf_get_subtree(card_config, key, NULL);
237                                         pcm_data->next = pcm_list;
238                                         pcm_list = pcm_data;
239                                 }
240                         }
241                 }
242
243                 /* check for missing devices */
244                 missing_devices(card, card_config);
245
246         next_card:
247                 snd_ctl_close(handle);
248                 if (snd_card_next(&card) < 0) {
249                         ksft_print_msg("snd_card_next");
250                         break;
251                 }
252         }
253
254         snd_config_delete(config);
255 }
256
257 static void test_pcm_time(struct pcm_data *data, enum test_class class,
258                           const char *test_name, snd_config_t *pcm_cfg)
259 {
260         char name[64], msg[256];
261         const int duration_s = 2, margin_ms = 100;
262         const int duration_ms = duration_s * 1000;
263         const char *cs;
264         int i, err;
265         snd_pcm_t *handle = NULL;
266         snd_pcm_access_t access = SND_PCM_ACCESS_RW_INTERLEAVED;
267         snd_pcm_format_t format, old_format;
268         const char *alt_formats[8];
269         unsigned char *samples = NULL;
270         snd_pcm_sframes_t frames;
271         long long ms;
272         long rate, channels, period_size, buffer_size;
273         unsigned int rrate;
274         snd_pcm_uframes_t rperiod_size, rbuffer_size, start_threshold;
275         timestamp_t tstamp;
276         bool pass = false;
277         snd_pcm_hw_params_t *hw_params;
278         snd_pcm_sw_params_t *sw_params;
279         const char *test_class_name;
280         bool skip = true;
281         const char *desc;
282
283         switch (class) {
284         case TEST_CLASS_DEFAULT:
285                 test_class_name = "default";
286                 break;
287         case TEST_CLASS_SYSTEM:
288                 test_class_name = "system";
289                 break;
290         default:
291                 ksft_exit_fail_msg("Unknown test class %d\n", class);
292                 break;
293         }
294
295         desc = conf_get_string(pcm_cfg, "description", NULL, NULL);
296         if (desc)
297                 ksft_print_msg("%s.%s.%d.%d.%d.%s - %s\n",
298                                test_class_name, test_name,
299                                data->card, data->device, data->subdevice,
300                                snd_pcm_stream_name(data->stream),
301                                desc);
302
303
304         snd_pcm_hw_params_alloca(&hw_params);
305         snd_pcm_sw_params_alloca(&sw_params);
306
307         cs = conf_get_string(pcm_cfg, "format", NULL, "S16_LE");
308         format = snd_pcm_format_value(cs);
309         if (format == SND_PCM_FORMAT_UNKNOWN)
310                 ksft_exit_fail_msg("Wrong format '%s'\n", cs);
311         conf_get_string_array(pcm_cfg, "alt_formats", NULL,
312                                 alt_formats, ARRAY_SIZE(alt_formats), NULL);
313         rate = conf_get_long(pcm_cfg, "rate", NULL, 48000);
314         channels = conf_get_long(pcm_cfg, "channels", NULL, 2);
315         period_size = conf_get_long(pcm_cfg, "period_size", NULL, 4096);
316         buffer_size = conf_get_long(pcm_cfg, "buffer_size", NULL, 16384);
317
318         samples = malloc((rate * channels * snd_pcm_format_physical_width(format)) / 8);
319         if (!samples)
320                 ksft_exit_fail_msg("Out of memory\n");
321         snd_pcm_format_set_silence(format, samples, rate * channels);
322
323         sprintf(name, "hw:%d,%d,%d", data->card, data->device, data->subdevice);
324         err = snd_pcm_open(&handle, name, data->stream, 0);
325         if (err < 0) {
326                 snprintf(msg, sizeof(msg), "Failed to get pcm handle: %s", snd_strerror(err));
327                 goto __close;
328         }
329
330         err = snd_pcm_hw_params_any(handle, hw_params);
331         if (err < 0) {
332                 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_any: %s", snd_strerror(err));
333                 goto __close;
334         }
335         err = snd_pcm_hw_params_set_rate_resample(handle, hw_params, 0);
336         if (err < 0) {
337                 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate_resample: %s", snd_strerror(err));
338                 goto __close;
339         }
340         err = snd_pcm_hw_params_set_access(handle, hw_params, access);
341         if (err < 0) {
342                 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_access %s: %s",
343                                            snd_pcm_access_name(access), snd_strerror(err));
344                 goto __close;
345         }
346         i = -1;
347 __format:
348         err = snd_pcm_hw_params_set_format(handle, hw_params, format);
349         if (err < 0) {
350                 i++;
351                 if (i < ARRAY_SIZE(alt_formats) && alt_formats[i]) {
352                         old_format = format;
353                         format = snd_pcm_format_value(alt_formats[i]);
354                         if (format != SND_PCM_FORMAT_UNKNOWN) {
355                                 ksft_print_msg("%s.%d.%d.%d.%s.%s format %s -> %s\n",
356                                                  test_name,
357                                                  data->card, data->device, data->subdevice,
358                                                  snd_pcm_stream_name(data->stream),
359                                                  snd_pcm_access_name(access),
360                                                  snd_pcm_format_name(old_format),
361                                                  snd_pcm_format_name(format));
362                                 samples = realloc(samples, (rate * channels *
363                                                             snd_pcm_format_physical_width(format)) / 8);
364                                 if (!samples)
365                                         ksft_exit_fail_msg("Out of memory\n");
366                                 snd_pcm_format_set_silence(format, samples, rate * channels);
367                                 goto __format;
368                         }
369                 }
370                 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_format %s: %s",
371                                            snd_pcm_format_name(format), snd_strerror(err));
372                 goto __close;
373         }
374         err = snd_pcm_hw_params_set_channels(handle, hw_params, channels);
375         if (err < 0) {
376                 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_channels %ld: %s", channels, snd_strerror(err));
377                 goto __close;
378         }
379         rrate = rate;
380         err = snd_pcm_hw_params_set_rate_near(handle, hw_params, &rrate, 0);
381         if (err < 0) {
382                 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate %ld: %s", rate, snd_strerror(err));
383                 goto __close;
384         }
385         if (rrate != rate) {
386                 snprintf(msg, sizeof(msg), "rate mismatch %ld != %d", rate, rrate);
387                 goto __close;
388         }
389         rperiod_size = period_size;
390         err = snd_pcm_hw_params_set_period_size_near(handle, hw_params, &rperiod_size, 0);
391         if (err < 0) {
392                 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_period_size %ld: %s", period_size, snd_strerror(err));
393                 goto __close;
394         }
395         rbuffer_size = buffer_size;
396         err = snd_pcm_hw_params_set_buffer_size_near(handle, hw_params, &rbuffer_size);
397         if (err < 0) {
398                 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_buffer_size %ld: %s", buffer_size, snd_strerror(err));
399                 goto __close;
400         }
401         err = snd_pcm_hw_params(handle, hw_params);
402         if (err < 0) {
403                 snprintf(msg, sizeof(msg), "snd_pcm_hw_params: %s", snd_strerror(err));
404                 goto __close;
405         }
406
407         err = snd_pcm_sw_params_current(handle, sw_params);
408         if (err < 0) {
409                 snprintf(msg, sizeof(msg), "snd_pcm_sw_params_current: %s", snd_strerror(err));
410                 goto __close;
411         }
412         if (data->stream == SND_PCM_STREAM_PLAYBACK) {
413                 start_threshold = (rbuffer_size / rperiod_size) * rperiod_size;
414         } else {
415                 start_threshold = rperiod_size;
416         }
417         err = snd_pcm_sw_params_set_start_threshold(handle, sw_params, start_threshold);
418         if (err < 0) {
419                 snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_start_threshold %ld: %s", (long)start_threshold, snd_strerror(err));
420                 goto __close;
421         }
422         err = snd_pcm_sw_params_set_avail_min(handle, sw_params, rperiod_size);
423         if (err < 0) {
424                 snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_avail_min %ld: %s", (long)rperiod_size, snd_strerror(err));
425                 goto __close;
426         }
427         err = snd_pcm_sw_params(handle, sw_params);
428         if (err < 0) {
429                 snprintf(msg, sizeof(msg), "snd_pcm_sw_params: %s", snd_strerror(err));
430                 goto __close;
431         }
432
433         ksft_print_msg("%s.%s.%d.%d.%d.%s hw_params.%s.%s.%ld.%ld.%ld.%ld sw_params.%ld\n",
434                          test_class_name, test_name,
435                          data->card, data->device, data->subdevice,
436                          snd_pcm_stream_name(data->stream),
437                          snd_pcm_access_name(access),
438                          snd_pcm_format_name(format),
439                          (long)rate, (long)channels,
440                          (long)rperiod_size, (long)rbuffer_size,
441                          (long)start_threshold);
442
443         /* Set all the params, actually run the test */
444         skip = false;
445
446         timestamp_now(&tstamp);
447         for (i = 0; i < duration_s; i++) {
448                 if (data->stream == SND_PCM_STREAM_PLAYBACK) {
449                         frames = snd_pcm_writei(handle, samples, rate);
450                         if (frames < 0) {
451                                 snprintf(msg, sizeof(msg),
452                                          "Write failed: expected %ld, wrote %li", rate, frames);
453                                 goto __close;
454                         }
455                         if (frames < rate) {
456                                 snprintf(msg, sizeof(msg),
457                                          "expected %ld, wrote %li", rate, frames);
458                                 goto __close;
459                         }
460                 } else {
461                         frames = snd_pcm_readi(handle, samples, rate);
462                         if (frames < 0) {
463                                 snprintf(msg, sizeof(msg),
464                                          "expected %ld, wrote %li", rate, frames);
465                                 goto __close;
466                         }
467                         if (frames < rate) {
468                                 snprintf(msg, sizeof(msg),
469                                          "expected %ld, wrote %li", rate, frames);
470                                 goto __close;
471                         }
472                 }
473         }
474
475         snd_pcm_drain(handle);
476         ms = timestamp_diff_ms(&tstamp);
477         if (ms < duration_ms - margin_ms || ms > duration_ms + margin_ms) {
478                 snprintf(msg, sizeof(msg), "time mismatch: expected %dms got %lld", duration_ms, ms);
479                 goto __close;
480         }
481
482         msg[0] = '\0';
483         pass = true;
484 __close:
485         pthread_mutex_lock(&results_lock);
486
487         switch (class) {
488         case TEST_CLASS_SYSTEM:
489                 test_class_name = "system";
490                 /*
491                  * Anything specified as specific to this system
492                  * should always be supported.
493                  */
494                 ksft_test_result(!skip, "%s.%s.%d.%d.%d.%s.params\n",
495                                  test_class_name, test_name,
496                                  data->card, data->device, data->subdevice,
497                                  snd_pcm_stream_name(data->stream));
498                 break;
499         default:
500                 break;
501         }
502
503         if (!skip)
504                 ksft_test_result(pass, "%s.%s.%d.%d.%d.%s\n",
505                                  test_class_name, test_name,
506                                  data->card, data->device, data->subdevice,
507                                  snd_pcm_stream_name(data->stream));
508         else
509                 ksft_test_result_skip("%s.%s.%d.%d.%d.%s\n",
510                                  test_class_name, test_name,
511                                  data->card, data->device, data->subdevice,
512                                  snd_pcm_stream_name(data->stream));
513
514         if (msg[0])
515                 ksft_print_msg("%s\n", msg);
516
517         pthread_mutex_unlock(&results_lock);
518
519         free(samples);
520         if (handle)
521                 snd_pcm_close(handle);
522 }
523
524 void run_time_tests(struct pcm_data *pcm, enum test_class class,
525                     snd_config_t *cfg)
526 {
527         const char *test_name, *test_type;
528         snd_config_t *pcm_cfg;
529         snd_config_iterator_t i, next;
530
531         if (!cfg)
532                 return;
533
534         cfg = conf_get_subtree(cfg, "test", NULL);
535         if (cfg == NULL)
536                 return;
537
538         snd_config_for_each(i, next, cfg) {
539                 pcm_cfg = snd_config_iterator_entry(i);
540                 if (snd_config_get_id(pcm_cfg, &test_name) < 0)
541                         ksft_exit_fail_msg("snd_config_get_id\n");
542                 test_type = conf_get_string(pcm_cfg, "type", NULL, "time");
543                 if (strcmp(test_type, "time") == 0)
544                         test_pcm_time(pcm, class, test_name, pcm_cfg);
545                 else
546                         ksft_exit_fail_msg("unknown test type '%s'\n", test_type);
547         }
548 }
549
550 void *card_thread(void *data)
551 {
552         struct card_data *card = data;
553         struct pcm_data *pcm;
554
555         for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) {
556                 if (pcm->card != card->card)
557                         continue;
558
559                 run_time_tests(pcm, TEST_CLASS_DEFAULT, default_pcm_config);
560                 run_time_tests(pcm, TEST_CLASS_SYSTEM, pcm->pcm_config);
561         }
562
563         return 0;
564 }
565
566 int main(void)
567 {
568         struct card_data *card;
569         struct pcm_data *pcm;
570         snd_config_t *global_config, *cfg;
571         int num_pcm_tests = 0, num_tests, num_std_pcm_tests;
572         int ret;
573         void *thread_ret;
574
575         ksft_print_header();
576
577         global_config = conf_load_from_file("pcm-test.conf");
578         default_pcm_config = conf_get_subtree(global_config, "pcm", NULL);
579         if (default_pcm_config == NULL)
580                 ksft_exit_fail_msg("default pcm test configuration (pcm compound) is missing\n");
581
582         conf_load();
583
584         find_pcms();
585
586         num_std_pcm_tests = conf_get_count(default_pcm_config, "test", NULL);
587
588         for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) {
589                 num_pcm_tests += num_std_pcm_tests;
590                 cfg = pcm->pcm_config;
591                 if (cfg == NULL)
592                         continue;
593                 /* Setting params is reported as a separate test */
594                 num_tests = conf_get_count(cfg, "test", NULL) * 2;
595                 if (num_tests > 0)
596                         num_pcm_tests += num_tests;
597         }
598
599         ksft_set_plan(num_missing + num_pcm_tests);
600
601         for (pcm = pcm_missing; pcm != NULL; pcm = pcm->next) {
602                 ksft_test_result(false, "test.missing.%d.%d.%d.%s\n",
603                                  pcm->card, pcm->device, pcm->subdevice,
604                                  snd_pcm_stream_name(pcm->stream));
605         }
606
607         for (card = card_list; card != NULL; card = card->next) {
608                 ret = pthread_create(&card->thread, NULL, card_thread, card);
609                 if (ret != 0) {
610                         ksft_exit_fail_msg("Failed to create card %d thread: %d (%s)\n",
611                                            card->card, ret,
612                                            strerror(errno));
613                 }
614         }
615
616         for (card = card_list; card != NULL; card = card->next) {
617                 ret = pthread_join(card->thread, &thread_ret);
618                 if (ret != 0) {
619                         ksft_exit_fail_msg("Failed to join card %d thread: %d (%s)\n",
620                                            card->card, ret,
621                                            strerror(errno));
622                 }
623         }
624
625         snd_config_delete(global_config);
626         conf_free();
627
628         ksft_exit_pass();
629
630         return 0;
631 }