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 	int card, dev, subdev, count, direction, err;
153 	snd_pcm_stream_t stream;
154 	struct pcm_data *pcm_data;
155 	snd_ctl_t *handle;
156 	snd_pcm_info_t *pcm_info;
157 	snd_config_t *config, *card_config, *pcm_config;
158 	struct card_data *card_data;
159 
160 	snd_pcm_info_alloca(&pcm_info);
161 
162 	card = -1;
163 	if (snd_card_next(&card) < 0 || card < 0)
164 		return;
165 
166 	config = get_alsalib_config();
167 
168 	while (card >= 0) {
169 		sprintf(name, "hw:%d", card);
170 
171 		err = snd_ctl_open_lconf(&handle, name, 0, config);
172 		if (err < 0) {
173 			ksft_print_msg("Failed to get hctl for card %d: %s\n",
174 				       card, snd_strerror(err));
175 			goto next_card;
176 		}
177 
178 		card_config = conf_by_card(card);
179 
180 		card_data = calloc(1, sizeof(*card_data));
181 		if (!card_data)
182 			ksft_exit_fail_msg("Out of memory\n");
183 		card_data->card = card;
184 		card_data->next = card_list;
185 		card_list = card_data;
186 
187 		dev = -1;
188 		while (1) {
189 			if (snd_ctl_pcm_next_device(handle, &dev) < 0)
190 				ksft_exit_fail_msg("snd_ctl_pcm_next_device\n");
191 			if (dev < 0)
192 				break;
193 
194 			for (direction = 0; direction < 2; direction++) {
195 				stream = direction ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK;
196 				sprintf(key, "pcm.%d.%s", dev, snd_pcm_stream_name(stream));
197 				pcm_config = conf_get_subtree(card_config, key, NULL);
198 				if (conf_get_bool(card_config, key, "skip", false)) {
199 					ksft_print_msg("skipping pcm %d.%d.%s\n", card, dev, snd_pcm_stream_name(stream));
200 					continue;
201 				}
202 				snd_pcm_info_set_device(pcm_info, dev);
203 				snd_pcm_info_set_subdevice(pcm_info, 0);
204 				snd_pcm_info_set_stream(pcm_info, stream);
205 				err = snd_ctl_pcm_info(handle, pcm_info);
206 				if (err == -ENOENT)
207 					continue;
208 				if (err < 0)
209 					ksft_exit_fail_msg("snd_ctl_pcm_info: %d:%d:%d\n",
210 							   dev, 0, stream);
211 				count = snd_pcm_info_get_subdevices_count(pcm_info);
212 				for (subdev = 0; subdev < count; subdev++) {
213 					sprintf(key, "pcm.%d.%d.%s", dev, subdev, snd_pcm_stream_name(stream));
214 					if (conf_get_bool(card_config, key, "skip", false)) {
215 						ksft_print_msg("skipping pcm %d.%d.%d.%s\n", card, dev,
216 							       subdev, snd_pcm_stream_name(stream));
217 						continue;
218 					}
219 					pcm_data = calloc(1, sizeof(*pcm_data));
220 					if (!pcm_data)
221 						ksft_exit_fail_msg("Out of memory\n");
222 					pcm_data->card = card;
223 					pcm_data->device = dev;
224 					pcm_data->subdevice = subdev;
225 					pcm_data->stream = stream;
226 					pcm_data->pcm_config = conf_get_subtree(card_config, key, NULL);
227 					pcm_data->next = pcm_list;
228 					pcm_list = pcm_data;
229 				}
230 			}
231 		}
232 
233 		/* check for missing devices */
234 		missing_devices(card, card_config);
235 
236 	next_card:
237 		snd_ctl_close(handle);
238 		if (snd_card_next(&card) < 0) {
239 			ksft_print_msg("snd_card_next");
240 			break;
241 		}
242 	}
243 
244 	snd_config_delete(config);
245 }
246 
247 static void test_pcm_time(struct pcm_data *data, enum test_class class,
248 			  const char *test_name, snd_config_t *pcm_cfg)
249 {
250 	char name[64], key[128], msg[256];
251 	const char *cs;
252 	int i, err;
253 	snd_pcm_t *handle = NULL;
254 	snd_pcm_access_t access = SND_PCM_ACCESS_RW_INTERLEAVED;
255 	snd_pcm_format_t format, old_format;
256 	const char *alt_formats[8];
257 	unsigned char *samples = NULL;
258 	snd_pcm_sframes_t frames;
259 	long long ms;
260 	long rate, channels, period_size, buffer_size;
261 	unsigned int rrate;
262 	snd_pcm_uframes_t rperiod_size, rbuffer_size, start_threshold;
263 	timestamp_t tstamp;
264 	bool pass = false;
265 	snd_pcm_hw_params_t *hw_params;
266 	snd_pcm_sw_params_t *sw_params;
267 	const char *test_class_name;
268 	bool skip = true;
269 	const char *desc;
270 
271 	switch (class) {
272 	case TEST_CLASS_DEFAULT:
273 		test_class_name = "default";
274 		break;
275 	case TEST_CLASS_SYSTEM:
276 		test_class_name = "system";
277 		break;
278 	default:
279 		ksft_exit_fail_msg("Unknown test class %d\n", class);
280 		break;
281 	}
282 
283 	desc = conf_get_string(pcm_cfg, "description", NULL, NULL);
284 	if (desc)
285 		ksft_print_msg("%s.%s.%d.%d.%d.%s - %s\n",
286 			       test_class_name, test_name,
287 			       data->card, data->device, data->subdevice,
288 			       snd_pcm_stream_name(data->stream),
289 			       desc);
290 
291 
292 	snd_pcm_hw_params_alloca(&hw_params);
293 	snd_pcm_sw_params_alloca(&sw_params);
294 
295 	cs = conf_get_string(pcm_cfg, "format", NULL, "S16_LE");
296 	format = snd_pcm_format_value(cs);
297 	if (format == SND_PCM_FORMAT_UNKNOWN)
298 		ksft_exit_fail_msg("Wrong format '%s'\n", cs);
299 	conf_get_string_array(pcm_cfg, "alt_formats", NULL,
300 				alt_formats, ARRAY_SIZE(alt_formats), NULL);
301 	rate = conf_get_long(pcm_cfg, "rate", NULL, 48000);
302 	channels = conf_get_long(pcm_cfg, "channels", NULL, 2);
303 	period_size = conf_get_long(pcm_cfg, "period_size", NULL, 4096);
304 	buffer_size = conf_get_long(pcm_cfg, "buffer_size", NULL, 16384);
305 
306 	samples = malloc((rate * channels * snd_pcm_format_physical_width(format)) / 8);
307 	if (!samples)
308 		ksft_exit_fail_msg("Out of memory\n");
309 	snd_pcm_format_set_silence(format, samples, rate * channels);
310 
311 	sprintf(name, "hw:%d,%d,%d", data->card, data->device, data->subdevice);
312 	err = snd_pcm_open(&handle, name, data->stream, 0);
313 	if (err < 0) {
314 		snprintf(msg, sizeof(msg), "Failed to get pcm handle: %s", snd_strerror(err));
315 		goto __close;
316 	}
317 
318 	err = snd_pcm_hw_params_any(handle, hw_params);
319 	if (err < 0) {
320 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_any: %s", snd_strerror(err));
321 		goto __close;
322 	}
323 	err = snd_pcm_hw_params_set_rate_resample(handle, hw_params, 0);
324 	if (err < 0) {
325 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate_resample: %s", snd_strerror(err));
326 		goto __close;
327 	}
328 	err = snd_pcm_hw_params_set_access(handle, hw_params, access);
329 	if (err < 0) {
330 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_access %s: %s",
331 					   snd_pcm_access_name(access), snd_strerror(err));
332 		goto __close;
333 	}
334 	i = -1;
335 __format:
336 	err = snd_pcm_hw_params_set_format(handle, hw_params, format);
337 	if (err < 0) {
338 		i++;
339 		if (i < ARRAY_SIZE(alt_formats) && alt_formats[i]) {
340 			old_format = format;
341 			format = snd_pcm_format_value(alt_formats[i]);
342 			if (format != SND_PCM_FORMAT_UNKNOWN) {
343 				ksft_print_msg("%s.%d.%d.%d.%s.%s format %s -> %s\n",
344 						 test_name,
345 						 data->card, data->device, data->subdevice,
346 						 snd_pcm_stream_name(data->stream),
347 						 snd_pcm_access_name(access),
348 						 snd_pcm_format_name(old_format),
349 						 snd_pcm_format_name(format));
350 				samples = realloc(samples, (rate * channels *
351 							    snd_pcm_format_physical_width(format)) / 8);
352 				if (!samples)
353 					ksft_exit_fail_msg("Out of memory\n");
354 				snd_pcm_format_set_silence(format, samples, rate * channels);
355 				goto __format;
356 			}
357 		}
358 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_format %s: %s",
359 					   snd_pcm_format_name(format), snd_strerror(err));
360 		goto __close;
361 	}
362 	err = snd_pcm_hw_params_set_channels(handle, hw_params, channels);
363 	if (err < 0) {
364 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_channels %ld: %s", channels, snd_strerror(err));
365 		goto __close;
366 	}
367 	rrate = rate;
368 	err = snd_pcm_hw_params_set_rate_near(handle, hw_params, &rrate, 0);
369 	if (err < 0) {
370 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate %ld: %s", rate, snd_strerror(err));
371 		goto __close;
372 	}
373 	if (rrate != rate) {
374 		snprintf(msg, sizeof(msg), "rate mismatch %ld != %ld", rate, rrate);
375 		goto __close;
376 	}
377 	rperiod_size = period_size;
378 	err = snd_pcm_hw_params_set_period_size_near(handle, hw_params, &rperiod_size, 0);
379 	if (err < 0) {
380 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_period_size %ld: %s", period_size, snd_strerror(err));
381 		goto __close;
382 	}
383 	rbuffer_size = buffer_size;
384 	err = snd_pcm_hw_params_set_buffer_size_near(handle, hw_params, &rbuffer_size);
385 	if (err < 0) {
386 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_buffer_size %ld: %s", buffer_size, snd_strerror(err));
387 		goto __close;
388 	}
389 	err = snd_pcm_hw_params(handle, hw_params);
390 	if (err < 0) {
391 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params: %s", snd_strerror(err));
392 		goto __close;
393 	}
394 
395 	err = snd_pcm_sw_params_current(handle, sw_params);
396 	if (err < 0) {
397 		snprintf(msg, sizeof(msg), "snd_pcm_sw_params_current: %s", snd_strerror(err));
398 		goto __close;
399 	}
400 	if (data->stream == SND_PCM_STREAM_PLAYBACK) {
401 		start_threshold = (rbuffer_size / rperiod_size) * rperiod_size;
402 	} else {
403 		start_threshold = rperiod_size;
404 	}
405 	err = snd_pcm_sw_params_set_start_threshold(handle, sw_params, start_threshold);
406 	if (err < 0) {
407 		snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_start_threshold %ld: %s", (long)start_threshold, snd_strerror(err));
408 		goto __close;
409 	}
410 	err = snd_pcm_sw_params_set_avail_min(handle, sw_params, rperiod_size);
411 	if (err < 0) {
412 		snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_avail_min %ld: %s", (long)rperiod_size, snd_strerror(err));
413 		goto __close;
414 	}
415 	err = snd_pcm_sw_params(handle, sw_params);
416 	if (err < 0) {
417 		snprintf(msg, sizeof(msg), "snd_pcm_sw_params: %s", snd_strerror(err));
418 		goto __close;
419 	}
420 
421 	ksft_print_msg("%s.%s.%d.%d.%d.%s hw_params.%s.%s.%ld.%ld.%ld.%ld sw_params.%ld\n",
422 		         test_class_name, test_name,
423 			 data->card, data->device, data->subdevice,
424 			 snd_pcm_stream_name(data->stream),
425 			 snd_pcm_access_name(access),
426 			 snd_pcm_format_name(format),
427 			 (long)rate, (long)channels,
428 			 (long)rperiod_size, (long)rbuffer_size,
429 			 (long)start_threshold);
430 
431 	/* Set all the params, actually run the test */
432 	skip = false;
433 
434 	timestamp_now(&tstamp);
435 	for (i = 0; i < 4; i++) {
436 		if (data->stream == SND_PCM_STREAM_PLAYBACK) {
437 			frames = snd_pcm_writei(handle, samples, rate);
438 			if (frames < 0) {
439 				snprintf(msg, sizeof(msg),
440 					 "Write failed: expected %d, wrote %li", rate, frames);
441 				goto __close;
442 			}
443 			if (frames < rate) {
444 				snprintf(msg, sizeof(msg),
445 					 "expected %d, wrote %li", rate, frames);
446 				goto __close;
447 			}
448 		} else {
449 			frames = snd_pcm_readi(handle, samples, rate);
450 			if (frames < 0) {
451 				snprintf(msg, sizeof(msg),
452 					 "expected %d, wrote %li", rate, frames);
453 				goto __close;
454 			}
455 			if (frames < rate) {
456 				snprintf(msg, sizeof(msg),
457 					 "expected %d, wrote %li", rate, frames);
458 				goto __close;
459 			}
460 		}
461 	}
462 
463 	snd_pcm_drain(handle);
464 	ms = timestamp_diff_ms(&tstamp);
465 	if (ms < 3900 || ms > 4100) {
466 		snprintf(msg, sizeof(msg), "time mismatch: expected 4000ms got %lld", ms);
467 		goto __close;
468 	}
469 
470 	msg[0] = '\0';
471 	pass = true;
472 __close:
473 	pthread_mutex_lock(&results_lock);
474 
475 	switch (class) {
476 	case TEST_CLASS_SYSTEM:
477 		test_class_name = "system";
478 		/*
479 		 * Anything specified as specific to this system
480 		 * should always be supported.
481 		 */
482 		ksft_test_result(!skip, "%s.%s.%d.%d.%d.%s.params\n",
483 				 test_class_name, test_name,
484 				 data->card, data->device, data->subdevice,
485 				 snd_pcm_stream_name(data->stream));
486 		break;
487 	default:
488 		break;
489 	}
490 
491 	if (!skip)
492 		ksft_test_result(pass, "%s.%s.%d.%d.%d.%s%s%s\n",
493 				 test_class_name, test_name,
494 				 data->card, data->device, data->subdevice,
495 				 snd_pcm_stream_name(data->stream),
496 				 msg[0] ? " " : "", msg);
497 	else
498 		ksft_test_result_skip("%s.%s.%d.%d.%d.%s%s%s\n",
499 				 test_class_name, test_name,
500 				 data->card, data->device, data->subdevice,
501 				 snd_pcm_stream_name(data->stream),
502 				 msg[0] ? " " : "", msg);
503 
504 	pthread_mutex_unlock(&results_lock);
505 
506 	free(samples);
507 	if (handle)
508 		snd_pcm_close(handle);
509 }
510 
511 void run_time_tests(struct pcm_data *pcm, enum test_class class,
512 		    snd_config_t *cfg)
513 {
514 	const char *test_name, *test_type;
515 	snd_config_t *pcm_cfg;
516 	snd_config_iterator_t i, next;
517 
518 	if (!cfg)
519 		return;
520 
521 	cfg = conf_get_subtree(cfg, "test", NULL);
522 	if (cfg == NULL)
523 		return;
524 
525 	snd_config_for_each(i, next, cfg) {
526 		pcm_cfg = snd_config_iterator_entry(i);
527 		if (snd_config_get_id(pcm_cfg, &test_name) < 0)
528 			ksft_exit_fail_msg("snd_config_get_id\n");
529 		test_type = conf_get_string(pcm_cfg, "type", NULL, "time");
530 		if (strcmp(test_type, "time") == 0)
531 			test_pcm_time(pcm, class, test_name, pcm_cfg);
532 		else
533 			ksft_exit_fail_msg("unknown test type '%s'\n", test_type);
534 	}
535 }
536 
537 void *card_thread(void *data)
538 {
539 	struct card_data *card = data;
540 	struct pcm_data *pcm;
541 
542 	for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) {
543 		if (pcm->card != card->card)
544 			continue;
545 
546 		run_time_tests(pcm, TEST_CLASS_DEFAULT, default_pcm_config);
547 		run_time_tests(pcm, TEST_CLASS_SYSTEM, pcm->pcm_config);
548 	}
549 
550 	return 0;
551 }
552 
553 int main(void)
554 {
555 	struct card_data *card;
556 	struct pcm_data *pcm;
557 	snd_config_t *global_config, *cfg, *pcm_cfg;
558 	int num_pcm_tests = 0, num_tests, num_std_pcm_tests;
559 	int ret;
560 	void *thread_ret;
561 
562 	ksft_print_header();
563 
564 	global_config = conf_load_from_file("pcm-test.conf");
565 	default_pcm_config = conf_get_subtree(global_config, "pcm", NULL);
566 	if (default_pcm_config == NULL)
567 		ksft_exit_fail_msg("default pcm test configuration (pcm compound) is missing\n");
568 
569 	conf_load();
570 
571 	find_pcms();
572 
573 	num_std_pcm_tests = conf_get_count(default_pcm_config, "test", NULL);
574 
575 	for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) {
576 		num_pcm_tests += num_std_pcm_tests;
577 		cfg = pcm->pcm_config;
578 		if (cfg == NULL)
579 			continue;
580 		/* Setting params is reported as a separate test */
581 		num_tests = conf_get_count(cfg, "test", NULL) * 2;
582 		if (num_tests > 0)
583 			num_pcm_tests += num_tests;
584 	}
585 
586 	ksft_set_plan(num_missing + num_pcm_tests);
587 
588 	for (pcm = pcm_missing; pcm != NULL; pcm = pcm->next) {
589 		ksft_test_result(false, "test.missing.%d.%d.%d.%s\n",
590 				 pcm->card, pcm->device, pcm->subdevice,
591 				 snd_pcm_stream_name(pcm->stream));
592 	}
593 
594 	for (card = card_list; card != NULL; card = card->next) {
595 		ret = pthread_create(&card->thread, NULL, card_thread, card);
596 		if (ret != 0) {
597 			ksft_exit_fail_msg("Failed to create card %d thread: %d (%s)\n",
598 					   card->card, ret,
599 					   strerror(errno));
600 		}
601 	}
602 
603 	for (card = card_list; card != NULL; card = card->next) {
604 		ret = pthread_join(card->thread, &thread_ret);
605 		if (ret != 0) {
606 			ksft_exit_fail_msg("Failed to join card %d thread: %d (%s)\n",
607 					   card->card, ret,
608 					   strerror(errno));
609 		}
610 	}
611 
612 	snd_config_delete(global_config);
613 	conf_free();
614 
615 	ksft_exit_pass();
616 
617 	return 0;
618 }
619