162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * This is the test which covers PCM middle layer data transferring using 462306a36Sopenharmony_ci * the virtual pcm test driver (snd-pcmtest). 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Copyright 2023 Ivan Orlov <ivan.orlov0322@gmail.com> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci#include <string.h> 962306a36Sopenharmony_ci#include <alsa/asoundlib.h> 1062306a36Sopenharmony_ci#include "../kselftest_harness.h" 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#define CH_NUM 4 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_cistruct pattern_buf { 1562306a36Sopenharmony_ci char buf[1024]; 1662306a36Sopenharmony_ci int len; 1762306a36Sopenharmony_ci}; 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_cistruct pattern_buf patterns[CH_NUM]; 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cistruct pcmtest_test_params { 2262306a36Sopenharmony_ci unsigned long buffer_size; 2362306a36Sopenharmony_ci unsigned long period_size; 2462306a36Sopenharmony_ci unsigned long channels; 2562306a36Sopenharmony_ci unsigned int rate; 2662306a36Sopenharmony_ci snd_pcm_access_t access; 2762306a36Sopenharmony_ci size_t sec_buf_len; 2862306a36Sopenharmony_ci size_t sample_size; 2962306a36Sopenharmony_ci int time; 3062306a36Sopenharmony_ci snd_pcm_format_t format; 3162306a36Sopenharmony_ci}; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistatic int read_patterns(void) 3462306a36Sopenharmony_ci{ 3562306a36Sopenharmony_ci FILE *fp, *fpl; 3662306a36Sopenharmony_ci int i; 3762306a36Sopenharmony_ci char pf[64]; 3862306a36Sopenharmony_ci char plf[64]; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci for (i = 0; i < CH_NUM; i++) { 4162306a36Sopenharmony_ci sprintf(plf, "/sys/kernel/debug/pcmtest/fill_pattern%d_len", i); 4262306a36Sopenharmony_ci fpl = fopen(plf, "r"); 4362306a36Sopenharmony_ci if (!fpl) 4462306a36Sopenharmony_ci return -1; 4562306a36Sopenharmony_ci fscanf(fpl, "%u", &patterns[i].len); 4662306a36Sopenharmony_ci fclose(fpl); 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci sprintf(pf, "/sys/kernel/debug/pcmtest/fill_pattern%d", i); 4962306a36Sopenharmony_ci fp = fopen(pf, "r"); 5062306a36Sopenharmony_ci if (!fp) 5162306a36Sopenharmony_ci return -1; 5262306a36Sopenharmony_ci fread(patterns[i].buf, 1, patterns[i].len, fp); 5362306a36Sopenharmony_ci fclose(fp); 5462306a36Sopenharmony_ci } 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci return 0; 5762306a36Sopenharmony_ci} 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistatic int get_test_results(char *debug_name) 6062306a36Sopenharmony_ci{ 6162306a36Sopenharmony_ci int result; 6262306a36Sopenharmony_ci FILE *f; 6362306a36Sopenharmony_ci char fname[128]; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci sprintf(fname, "/sys/kernel/debug/pcmtest/%s", debug_name); 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci f = fopen(fname, "r"); 6862306a36Sopenharmony_ci if (!f) { 6962306a36Sopenharmony_ci printf("Failed to open file\n"); 7062306a36Sopenharmony_ci return -1; 7162306a36Sopenharmony_ci } 7262306a36Sopenharmony_ci fscanf(f, "%d", &result); 7362306a36Sopenharmony_ci fclose(f); 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci return result; 7662306a36Sopenharmony_ci} 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_cistatic size_t get_sec_buf_len(unsigned int rate, unsigned long channels, snd_pcm_format_t format) 7962306a36Sopenharmony_ci{ 8062306a36Sopenharmony_ci return rate * channels * snd_pcm_format_physical_width(format) / 8; 8162306a36Sopenharmony_ci} 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_cistatic int setup_handle(snd_pcm_t **handle, snd_pcm_sw_params_t *swparams, 8462306a36Sopenharmony_ci snd_pcm_hw_params_t *hwparams, struct pcmtest_test_params *params, 8562306a36Sopenharmony_ci int card, snd_pcm_stream_t stream) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci char pcm_name[32]; 8862306a36Sopenharmony_ci int err; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci sprintf(pcm_name, "hw:%d,0,0", card); 9162306a36Sopenharmony_ci err = snd_pcm_open(handle, pcm_name, stream, 0); 9262306a36Sopenharmony_ci if (err < 0) 9362306a36Sopenharmony_ci return err; 9462306a36Sopenharmony_ci snd_pcm_hw_params_any(*handle, hwparams); 9562306a36Sopenharmony_ci snd_pcm_hw_params_set_rate_resample(*handle, hwparams, 0); 9662306a36Sopenharmony_ci snd_pcm_hw_params_set_access(*handle, hwparams, params->access); 9762306a36Sopenharmony_ci snd_pcm_hw_params_set_format(*handle, hwparams, params->format); 9862306a36Sopenharmony_ci snd_pcm_hw_params_set_channels(*handle, hwparams, params->channels); 9962306a36Sopenharmony_ci snd_pcm_hw_params_set_rate_near(*handle, hwparams, ¶ms->rate, 0); 10062306a36Sopenharmony_ci snd_pcm_hw_params_set_period_size_near(*handle, hwparams, ¶ms->period_size, 0); 10162306a36Sopenharmony_ci snd_pcm_hw_params_set_buffer_size_near(*handle, hwparams, ¶ms->buffer_size); 10262306a36Sopenharmony_ci snd_pcm_hw_params(*handle, hwparams); 10362306a36Sopenharmony_ci snd_pcm_sw_params_current(*handle, swparams); 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci snd_pcm_hw_params_set_rate_resample(*handle, hwparams, 0); 10662306a36Sopenharmony_ci snd_pcm_sw_params_set_avail_min(*handle, swparams, params->period_size); 10762306a36Sopenharmony_ci snd_pcm_hw_params_set_buffer_size_near(*handle, hwparams, ¶ms->buffer_size); 10862306a36Sopenharmony_ci snd_pcm_hw_params_set_period_size_near(*handle, hwparams, ¶ms->period_size, 0); 10962306a36Sopenharmony_ci snd_pcm_sw_params(*handle, swparams); 11062306a36Sopenharmony_ci snd_pcm_hw_params(*handle, hwparams); 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci return 0; 11362306a36Sopenharmony_ci} 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ciFIXTURE(pcmtest) { 11662306a36Sopenharmony_ci int card; 11762306a36Sopenharmony_ci snd_pcm_sw_params_t *swparams; 11862306a36Sopenharmony_ci snd_pcm_hw_params_t *hwparams; 11962306a36Sopenharmony_ci struct pcmtest_test_params params; 12062306a36Sopenharmony_ci}; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ciFIXTURE_TEARDOWN(pcmtest) { 12362306a36Sopenharmony_ci} 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ciFIXTURE_SETUP(pcmtest) { 12662306a36Sopenharmony_ci char *card_name; 12762306a36Sopenharmony_ci int err; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci if (geteuid()) 13062306a36Sopenharmony_ci SKIP(exit(-1), "This test needs root to run!"); 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci err = read_patterns(); 13362306a36Sopenharmony_ci if (err) 13462306a36Sopenharmony_ci SKIP(exit(-1), "Can't read patterns. Probably, module isn't loaded"); 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci card_name = malloc(127); 13762306a36Sopenharmony_ci ASSERT_NE(card_name, NULL); 13862306a36Sopenharmony_ci self->params.buffer_size = 16384; 13962306a36Sopenharmony_ci self->params.period_size = 4096; 14062306a36Sopenharmony_ci self->params.channels = CH_NUM; 14162306a36Sopenharmony_ci self->params.rate = 8000; 14262306a36Sopenharmony_ci self->params.access = SND_PCM_ACCESS_RW_INTERLEAVED; 14362306a36Sopenharmony_ci self->params.format = SND_PCM_FORMAT_S16_LE; 14462306a36Sopenharmony_ci self->card = -1; 14562306a36Sopenharmony_ci self->params.sample_size = snd_pcm_format_physical_width(self->params.format) / 8; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci self->params.sec_buf_len = get_sec_buf_len(self->params.rate, self->params.channels, 14862306a36Sopenharmony_ci self->params.format); 14962306a36Sopenharmony_ci self->params.time = 4; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci while (snd_card_next(&self->card) >= 0) { 15262306a36Sopenharmony_ci if (self->card == -1) 15362306a36Sopenharmony_ci break; 15462306a36Sopenharmony_ci snd_card_get_name(self->card, &card_name); 15562306a36Sopenharmony_ci if (!strcmp(card_name, "PCM-Test")) 15662306a36Sopenharmony_ci break; 15762306a36Sopenharmony_ci } 15862306a36Sopenharmony_ci free(card_name); 15962306a36Sopenharmony_ci ASSERT_NE(self->card, -1); 16062306a36Sopenharmony_ci} 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci/* 16362306a36Sopenharmony_ci * Here we are trying to send the looped monotonically increasing sequence of bytes to the driver. 16462306a36Sopenharmony_ci * If our data isn't corrupted, the driver will set the content of 'pc_test' debugfs file to '1' 16562306a36Sopenharmony_ci */ 16662306a36Sopenharmony_ciTEST_F(pcmtest, playback) { 16762306a36Sopenharmony_ci snd_pcm_t *handle; 16862306a36Sopenharmony_ci unsigned char *it; 16962306a36Sopenharmony_ci size_t write_res; 17062306a36Sopenharmony_ci int test_results; 17162306a36Sopenharmony_ci int i, cur_ch, pos_in_ch; 17262306a36Sopenharmony_ci void *samples; 17362306a36Sopenharmony_ci struct pcmtest_test_params *params = &self->params; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci samples = calloc(self->params.sec_buf_len * self->params.time, 1); 17662306a36Sopenharmony_ci ASSERT_NE(samples, NULL); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci snd_pcm_sw_params_alloca(&self->swparams); 17962306a36Sopenharmony_ci snd_pcm_hw_params_alloca(&self->hwparams); 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, params, 18262306a36Sopenharmony_ci self->card, SND_PCM_STREAM_PLAYBACK), 0); 18362306a36Sopenharmony_ci snd_pcm_format_set_silence(params->format, samples, 18462306a36Sopenharmony_ci params->rate * params->channels * params->time); 18562306a36Sopenharmony_ci it = samples; 18662306a36Sopenharmony_ci for (i = 0; i < self->params.sec_buf_len * params->time; i++) { 18762306a36Sopenharmony_ci cur_ch = (i / params->sample_size) % CH_NUM; 18862306a36Sopenharmony_ci pos_in_ch = i / params->sample_size / CH_NUM * params->sample_size 18962306a36Sopenharmony_ci + (i % params->sample_size); 19062306a36Sopenharmony_ci it[i] = patterns[cur_ch].buf[pos_in_ch % patterns[cur_ch].len]; 19162306a36Sopenharmony_ci } 19262306a36Sopenharmony_ci write_res = snd_pcm_writei(handle, samples, params->rate * params->time); 19362306a36Sopenharmony_ci ASSERT_GE(write_res, 0); 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci snd_pcm_close(handle); 19662306a36Sopenharmony_ci free(samples); 19762306a36Sopenharmony_ci test_results = get_test_results("pc_test"); 19862306a36Sopenharmony_ci ASSERT_EQ(test_results, 1); 19962306a36Sopenharmony_ci} 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci/* 20262306a36Sopenharmony_ci * Here we test that the virtual alsa driver returns looped and monotonically increasing sequence 20362306a36Sopenharmony_ci * of bytes. In the interleaved mode the buffer will contain samples in the following order: 20462306a36Sopenharmony_ci * C0, C1, C2, C3, C0, C1, ... 20562306a36Sopenharmony_ci */ 20662306a36Sopenharmony_ciTEST_F(pcmtest, capture) { 20762306a36Sopenharmony_ci snd_pcm_t *handle; 20862306a36Sopenharmony_ci unsigned char *it; 20962306a36Sopenharmony_ci size_t read_res; 21062306a36Sopenharmony_ci int i, cur_ch, pos_in_ch; 21162306a36Sopenharmony_ci void *samples; 21262306a36Sopenharmony_ci struct pcmtest_test_params *params = &self->params; 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci samples = calloc(self->params.sec_buf_len * self->params.time, 1); 21562306a36Sopenharmony_ci ASSERT_NE(samples, NULL); 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci snd_pcm_sw_params_alloca(&self->swparams); 21862306a36Sopenharmony_ci snd_pcm_hw_params_alloca(&self->hwparams); 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, 22162306a36Sopenharmony_ci params, self->card, SND_PCM_STREAM_CAPTURE), 0); 22262306a36Sopenharmony_ci snd_pcm_format_set_silence(params->format, samples, 22362306a36Sopenharmony_ci params->rate * params->channels * params->time); 22462306a36Sopenharmony_ci read_res = snd_pcm_readi(handle, samples, params->rate * params->time); 22562306a36Sopenharmony_ci ASSERT_GE(read_res, 0); 22662306a36Sopenharmony_ci snd_pcm_close(handle); 22762306a36Sopenharmony_ci it = (unsigned char *)samples; 22862306a36Sopenharmony_ci for (i = 0; i < self->params.sec_buf_len * self->params.time; i++) { 22962306a36Sopenharmony_ci cur_ch = (i / params->sample_size) % CH_NUM; 23062306a36Sopenharmony_ci pos_in_ch = i / params->sample_size / CH_NUM * params->sample_size 23162306a36Sopenharmony_ci + (i % params->sample_size); 23262306a36Sopenharmony_ci ASSERT_EQ(it[i], patterns[cur_ch].buf[pos_in_ch % patterns[cur_ch].len]); 23362306a36Sopenharmony_ci } 23462306a36Sopenharmony_ci free(samples); 23562306a36Sopenharmony_ci} 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci// Test capture in the non-interleaved access mode. The are buffers for each recorded channel 23862306a36Sopenharmony_ciTEST_F(pcmtest, ni_capture) { 23962306a36Sopenharmony_ci snd_pcm_t *handle; 24062306a36Sopenharmony_ci struct pcmtest_test_params params = self->params; 24162306a36Sopenharmony_ci char **chan_samples; 24262306a36Sopenharmony_ci size_t i, j, read_res; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci chan_samples = calloc(CH_NUM, sizeof(*chan_samples)); 24562306a36Sopenharmony_ci ASSERT_NE(chan_samples, NULL); 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci snd_pcm_sw_params_alloca(&self->swparams); 24862306a36Sopenharmony_ci snd_pcm_hw_params_alloca(&self->hwparams); 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci params.access = SND_PCM_ACCESS_RW_NONINTERLEAVED; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, 25362306a36Sopenharmony_ci ¶ms, self->card, SND_PCM_STREAM_CAPTURE), 0); 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci for (i = 0; i < CH_NUM; i++) 25662306a36Sopenharmony_ci chan_samples[i] = calloc(params.sec_buf_len * params.time, 1); 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci for (i = 0; i < 1; i++) { 25962306a36Sopenharmony_ci read_res = snd_pcm_readn(handle, (void **)chan_samples, params.rate * params.time); 26062306a36Sopenharmony_ci ASSERT_GE(read_res, 0); 26162306a36Sopenharmony_ci } 26262306a36Sopenharmony_ci snd_pcm_close(handle); 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci for (i = 0; i < CH_NUM; i++) { 26562306a36Sopenharmony_ci for (j = 0; j < params.rate * params.time; j++) 26662306a36Sopenharmony_ci ASSERT_EQ(chan_samples[i][j], patterns[i].buf[j % patterns[i].len]); 26762306a36Sopenharmony_ci free(chan_samples[i]); 26862306a36Sopenharmony_ci } 26962306a36Sopenharmony_ci free(chan_samples); 27062306a36Sopenharmony_ci} 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ciTEST_F(pcmtest, ni_playback) { 27362306a36Sopenharmony_ci snd_pcm_t *handle; 27462306a36Sopenharmony_ci struct pcmtest_test_params params = self->params; 27562306a36Sopenharmony_ci char **chan_samples; 27662306a36Sopenharmony_ci size_t i, j, read_res; 27762306a36Sopenharmony_ci int test_res; 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci chan_samples = calloc(CH_NUM, sizeof(*chan_samples)); 28062306a36Sopenharmony_ci ASSERT_NE(chan_samples, NULL); 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci snd_pcm_sw_params_alloca(&self->swparams); 28362306a36Sopenharmony_ci snd_pcm_hw_params_alloca(&self->hwparams); 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci params.access = SND_PCM_ACCESS_RW_NONINTERLEAVED; 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, 28862306a36Sopenharmony_ci ¶ms, self->card, SND_PCM_STREAM_PLAYBACK), 0); 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci for (i = 0; i < CH_NUM; i++) { 29162306a36Sopenharmony_ci chan_samples[i] = calloc(params.sec_buf_len * params.time, 1); 29262306a36Sopenharmony_ci for (j = 0; j < params.sec_buf_len * params.time; j++) 29362306a36Sopenharmony_ci chan_samples[i][j] = patterns[i].buf[j % patterns[i].len]; 29462306a36Sopenharmony_ci } 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci for (i = 0; i < 1; i++) { 29762306a36Sopenharmony_ci read_res = snd_pcm_writen(handle, (void **)chan_samples, params.rate * params.time); 29862306a36Sopenharmony_ci ASSERT_GE(read_res, 0); 29962306a36Sopenharmony_ci } 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci snd_pcm_close(handle); 30262306a36Sopenharmony_ci test_res = get_test_results("pc_test"); 30362306a36Sopenharmony_ci ASSERT_EQ(test_res, 1); 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci for (i = 0; i < CH_NUM; i++) 30662306a36Sopenharmony_ci free(chan_samples[i]); 30762306a36Sopenharmony_ci free(chan_samples); 30862306a36Sopenharmony_ci} 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci/* 31162306a36Sopenharmony_ci * Here we are testing the custom ioctl definition inside the virtual driver. If it triggers 31262306a36Sopenharmony_ci * successfully, the driver sets the content of 'ioctl_test' debugfs file to '1'. 31362306a36Sopenharmony_ci */ 31462306a36Sopenharmony_ciTEST_F(pcmtest, reset_ioctl) { 31562306a36Sopenharmony_ci snd_pcm_t *handle; 31662306a36Sopenharmony_ci int test_res; 31762306a36Sopenharmony_ci struct pcmtest_test_params *params = &self->params; 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci snd_pcm_sw_params_alloca(&self->swparams); 32062306a36Sopenharmony_ci snd_pcm_hw_params_alloca(&self->hwparams); 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, params, 32362306a36Sopenharmony_ci self->card, SND_PCM_STREAM_CAPTURE), 0); 32462306a36Sopenharmony_ci snd_pcm_reset(handle); 32562306a36Sopenharmony_ci test_res = get_test_results("ioctl_test"); 32662306a36Sopenharmony_ci ASSERT_EQ(test_res, 1); 32762306a36Sopenharmony_ci snd_pcm_close(handle); 32862306a36Sopenharmony_ci} 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ciTEST_HARNESS_MAIN 331