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