1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * This is the test which covers PCM middle layer data transferring using |
4 | * the virtual pcm test driver (snd-pcmtest). |
5 | * |
6 | * Copyright 2023 Ivan Orlov <ivan.orlov0322@gmail.com> |
7 | */ |
8 | #include <string.h> |
9 | #include <alsa/asoundlib.h> |
10 | #include "../kselftest_harness.h" |
11 | |
12 | #define CH_NUM 4 |
13 | |
14 | struct pattern_buf { |
15 | char buf[1024]; |
16 | int len; |
17 | }; |
18 | |
19 | struct pattern_buf patterns[CH_NUM]; |
20 | |
21 | struct pcmtest_test_params { |
22 | unsigned long buffer_size; |
23 | unsigned long period_size; |
24 | unsigned long channels; |
25 | unsigned int rate; |
26 | snd_pcm_access_t access; |
27 | size_t sec_buf_len; |
28 | size_t sample_size; |
29 | int time; |
30 | snd_pcm_format_t format; |
31 | }; |
32 | |
33 | static int read_patterns(void) |
34 | { |
35 | FILE *fp, *fpl; |
36 | int i; |
37 | char pf[64]; |
38 | char plf[64]; |
39 | |
40 | for (i = 0; i < CH_NUM; i++) { |
41 | sprintf(plf, "/sys/kernel/debug/pcmtest/fill_pattern%d_len" , i); |
42 | fpl = fopen(plf, "r" ); |
43 | if (!fpl) |
44 | return -1; |
45 | fscanf(fpl, "%u" , &patterns[i].len); |
46 | fclose(fpl); |
47 | |
48 | sprintf(pf, "/sys/kernel/debug/pcmtest/fill_pattern%d" , i); |
49 | fp = fopen(pf, "r" ); |
50 | if (!fp) |
51 | return -1; |
52 | fread(patterns[i].buf, 1, patterns[i].len, fp); |
53 | fclose(fp); |
54 | } |
55 | |
56 | return 0; |
57 | } |
58 | |
59 | static int get_test_results(char *debug_name) |
60 | { |
61 | int result; |
62 | FILE *f; |
63 | char fname[128]; |
64 | |
65 | sprintf(fname, "/sys/kernel/debug/pcmtest/%s" , debug_name); |
66 | |
67 | f = fopen(fname, "r" ); |
68 | if (!f) { |
69 | printf("Failed to open file\n" ); |
70 | return -1; |
71 | } |
72 | fscanf(f, "%d" , &result); |
73 | fclose(f); |
74 | |
75 | return result; |
76 | } |
77 | |
78 | static size_t get_sec_buf_len(unsigned int rate, unsigned long channels, snd_pcm_format_t format) |
79 | { |
80 | return rate * channels * snd_pcm_format_physical_width(format) / 8; |
81 | } |
82 | |
83 | static int setup_handle(snd_pcm_t **handle, snd_pcm_sw_params_t *swparams, |
84 | snd_pcm_hw_params_t *hwparams, struct pcmtest_test_params *params, |
85 | int card, snd_pcm_stream_t stream) |
86 | { |
87 | char pcm_name[32]; |
88 | int err; |
89 | |
90 | sprintf(pcm_name, "hw:%d,0,0" , card); |
91 | err = snd_pcm_open(handle, pcm_name, stream, 0); |
92 | if (err < 0) |
93 | return err; |
94 | snd_pcm_hw_params_any(*handle, hwparams); |
95 | snd_pcm_hw_params_set_rate_resample(*handle, hwparams, 0); |
96 | snd_pcm_hw_params_set_access(*handle, hwparams, params->access); |
97 | snd_pcm_hw_params_set_format(*handle, hwparams, params->format); |
98 | snd_pcm_hw_params_set_channels(*handle, hwparams, params->channels); |
99 | snd_pcm_hw_params_set_rate_near(*handle, hwparams, ¶ms->rate, 0); |
100 | snd_pcm_hw_params_set_period_size_near(*handle, hwparams, ¶ms->period_size, 0); |
101 | snd_pcm_hw_params_set_buffer_size_near(*handle, hwparams, ¶ms->buffer_size); |
102 | snd_pcm_hw_params(*handle, hwparams); |
103 | snd_pcm_sw_params_current(*handle, swparams); |
104 | |
105 | snd_pcm_hw_params_set_rate_resample(*handle, hwparams, 0); |
106 | snd_pcm_sw_params_set_avail_min(*handle, swparams, params->period_size); |
107 | snd_pcm_hw_params_set_buffer_size_near(*handle, hwparams, ¶ms->buffer_size); |
108 | snd_pcm_hw_params_set_period_size_near(*handle, hwparams, ¶ms->period_size, 0); |
109 | snd_pcm_sw_params(*handle, swparams); |
110 | snd_pcm_hw_params(*handle, hwparams); |
111 | |
112 | return 0; |
113 | } |
114 | |
115 | FIXTURE(pcmtest) { |
116 | int card; |
117 | snd_pcm_sw_params_t *swparams; |
118 | snd_pcm_hw_params_t *hwparams; |
119 | struct pcmtest_test_params params; |
120 | }; |
121 | |
122 | FIXTURE_TEARDOWN(pcmtest) { |
123 | } |
124 | |
125 | FIXTURE_SETUP(pcmtest) { |
126 | char *card_name; |
127 | int err; |
128 | |
129 | if (geteuid()) |
130 | SKIP(return, "This test needs root to run!" ); |
131 | |
132 | err = read_patterns(); |
133 | if (err) |
134 | SKIP(return, "Can't read patterns. Probably, module isn't loaded" ); |
135 | |
136 | card_name = malloc(127); |
137 | ASSERT_NE(card_name, NULL); |
138 | self->params.buffer_size = 16384; |
139 | self->params.period_size = 4096; |
140 | self->params.channels = CH_NUM; |
141 | self->params.rate = 8000; |
142 | self->params.access = SND_PCM_ACCESS_RW_INTERLEAVED; |
143 | self->params.format = SND_PCM_FORMAT_S16_LE; |
144 | self->card = -1; |
145 | self->params.sample_size = snd_pcm_format_physical_width(self->params.format) / 8; |
146 | |
147 | self->params.sec_buf_len = get_sec_buf_len(self->params.rate, self->params.channels, |
148 | self->params.format); |
149 | self->params.time = 4; |
150 | |
151 | while (snd_card_next(&self->card) >= 0) { |
152 | if (self->card == -1) |
153 | break; |
154 | snd_card_get_name(self->card, &card_name); |
155 | if (!strcmp(card_name, "PCM-Test" )) |
156 | break; |
157 | } |
158 | free(card_name); |
159 | ASSERT_NE(self->card, -1); |
160 | } |
161 | |
162 | /* |
163 | * Here we are trying to send the looped monotonically increasing sequence of bytes to the driver. |
164 | * If our data isn't corrupted, the driver will set the content of 'pc_test' debugfs file to '1' |
165 | */ |
166 | TEST_F(pcmtest, playback) { |
167 | snd_pcm_t *handle; |
168 | unsigned char *it; |
169 | size_t write_res; |
170 | int test_results; |
171 | int i, cur_ch, pos_in_ch; |
172 | void *samples; |
173 | struct pcmtest_test_params *params = &self->params; |
174 | |
175 | samples = calloc(self->params.sec_buf_len * self->params.time, 1); |
176 | ASSERT_NE(samples, NULL); |
177 | |
178 | snd_pcm_sw_params_alloca(&self->swparams); |
179 | snd_pcm_hw_params_alloca(&self->hwparams); |
180 | |
181 | ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, params, |
182 | self->card, SND_PCM_STREAM_PLAYBACK), 0); |
183 | snd_pcm_format_set_silence(params->format, samples, |
184 | params->rate * params->channels * params->time); |
185 | it = samples; |
186 | for (i = 0; i < self->params.sec_buf_len * params->time; i++) { |
187 | cur_ch = (i / params->sample_size) % CH_NUM; |
188 | pos_in_ch = i / params->sample_size / CH_NUM * params->sample_size |
189 | + (i % params->sample_size); |
190 | it[i] = patterns[cur_ch].buf[pos_in_ch % patterns[cur_ch].len]; |
191 | } |
192 | write_res = snd_pcm_writei(handle, samples, params->rate * params->time); |
193 | ASSERT_GE(write_res, 0); |
194 | |
195 | snd_pcm_close(handle); |
196 | free(samples); |
197 | test_results = get_test_results(debug_name: "pc_test" ); |
198 | ASSERT_EQ(test_results, 1); |
199 | } |
200 | |
201 | /* |
202 | * Here we test that the virtual alsa driver returns looped and monotonically increasing sequence |
203 | * of bytes. In the interleaved mode the buffer will contain samples in the following order: |
204 | * C0, C1, C2, C3, C0, C1, ... |
205 | */ |
206 | TEST_F(pcmtest, capture) { |
207 | snd_pcm_t *handle; |
208 | unsigned char *it; |
209 | size_t read_res; |
210 | int i, cur_ch, pos_in_ch; |
211 | void *samples; |
212 | struct pcmtest_test_params *params = &self->params; |
213 | |
214 | samples = calloc(self->params.sec_buf_len * self->params.time, 1); |
215 | ASSERT_NE(samples, NULL); |
216 | |
217 | snd_pcm_sw_params_alloca(&self->swparams); |
218 | snd_pcm_hw_params_alloca(&self->hwparams); |
219 | |
220 | ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, |
221 | params, self->card, SND_PCM_STREAM_CAPTURE), 0); |
222 | snd_pcm_format_set_silence(params->format, samples, |
223 | params->rate * params->channels * params->time); |
224 | read_res = snd_pcm_readi(handle, samples, params->rate * params->time); |
225 | ASSERT_GE(read_res, 0); |
226 | snd_pcm_close(handle); |
227 | it = (unsigned char *)samples; |
228 | for (i = 0; i < self->params.sec_buf_len * self->params.time; i++) { |
229 | cur_ch = (i / params->sample_size) % CH_NUM; |
230 | pos_in_ch = i / params->sample_size / CH_NUM * params->sample_size |
231 | + (i % params->sample_size); |
232 | ASSERT_EQ(it[i], patterns[cur_ch].buf[pos_in_ch % patterns[cur_ch].len]); |
233 | } |
234 | free(samples); |
235 | } |
236 | |
237 | // Test capture in the non-interleaved access mode. The are buffers for each recorded channel |
238 | TEST_F(pcmtest, ni_capture) { |
239 | snd_pcm_t *handle; |
240 | struct pcmtest_test_params params = self->params; |
241 | char **chan_samples; |
242 | size_t i, j, read_res; |
243 | |
244 | chan_samples = calloc(CH_NUM, sizeof(*chan_samples)); |
245 | ASSERT_NE(chan_samples, NULL); |
246 | |
247 | snd_pcm_sw_params_alloca(&self->swparams); |
248 | snd_pcm_hw_params_alloca(&self->hwparams); |
249 | |
250 | params.access = SND_PCM_ACCESS_RW_NONINTERLEAVED; |
251 | |
252 | ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, |
253 | ¶ms, self->card, SND_PCM_STREAM_CAPTURE), 0); |
254 | |
255 | for (i = 0; i < CH_NUM; i++) |
256 | chan_samples[i] = calloc(params.sec_buf_len * params.time, 1); |
257 | |
258 | for (i = 0; i < 1; i++) { |
259 | read_res = snd_pcm_readn(handle, (void **)chan_samples, params.rate * params.time); |
260 | ASSERT_GE(read_res, 0); |
261 | } |
262 | snd_pcm_close(handle); |
263 | |
264 | for (i = 0; i < CH_NUM; i++) { |
265 | for (j = 0; j < params.rate * params.time; j++) |
266 | ASSERT_EQ(chan_samples[i][j], patterns[i].buf[j % patterns[i].len]); |
267 | free(chan_samples[i]); |
268 | } |
269 | free(chan_samples); |
270 | } |
271 | |
272 | TEST_F(pcmtest, ni_playback) { |
273 | snd_pcm_t *handle; |
274 | struct pcmtest_test_params params = self->params; |
275 | char **chan_samples; |
276 | size_t i, j, read_res; |
277 | int test_res; |
278 | |
279 | chan_samples = calloc(CH_NUM, sizeof(*chan_samples)); |
280 | ASSERT_NE(chan_samples, NULL); |
281 | |
282 | snd_pcm_sw_params_alloca(&self->swparams); |
283 | snd_pcm_hw_params_alloca(&self->hwparams); |
284 | |
285 | params.access = SND_PCM_ACCESS_RW_NONINTERLEAVED; |
286 | |
287 | ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, |
288 | ¶ms, self->card, SND_PCM_STREAM_PLAYBACK), 0); |
289 | |
290 | for (i = 0; i < CH_NUM; i++) { |
291 | chan_samples[i] = calloc(params.sec_buf_len * params.time, 1); |
292 | for (j = 0; j < params.sec_buf_len * params.time; j++) |
293 | chan_samples[i][j] = patterns[i].buf[j % patterns[i].len]; |
294 | } |
295 | |
296 | for (i = 0; i < 1; i++) { |
297 | read_res = snd_pcm_writen(handle, (void **)chan_samples, params.rate * params.time); |
298 | ASSERT_GE(read_res, 0); |
299 | } |
300 | |
301 | snd_pcm_close(handle); |
302 | test_res = get_test_results(debug_name: "pc_test" ); |
303 | ASSERT_EQ(test_res, 1); |
304 | |
305 | for (i = 0; i < CH_NUM; i++) |
306 | free(chan_samples[i]); |
307 | free(chan_samples); |
308 | } |
309 | |
310 | /* |
311 | * Here we are testing the custom ioctl definition inside the virtual driver. If it triggers |
312 | * successfully, the driver sets the content of 'ioctl_test' debugfs file to '1'. |
313 | */ |
314 | TEST_F(pcmtest, reset_ioctl) { |
315 | snd_pcm_t *handle; |
316 | int test_res; |
317 | struct pcmtest_test_params *params = &self->params; |
318 | |
319 | snd_pcm_sw_params_alloca(&self->swparams); |
320 | snd_pcm_hw_params_alloca(&self->hwparams); |
321 | |
322 | ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, params, |
323 | self->card, SND_PCM_STREAM_CAPTURE), 0); |
324 | snd_pcm_reset(handle); |
325 | test_res = get_test_results(debug_name: "ioctl_test" ); |
326 | ASSERT_EQ(test_res, 1); |
327 | snd_pcm_close(handle); |
328 | } |
329 | |
330 | TEST_HARNESS_MAIN |
331 | |