1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Virtual ALSA driver for PCM testing/fuzzing |
4 | * |
5 | * Copyright 2023 Ivan Orlov <ivan.orlov0322@gmail.com> |
6 | * |
7 | * This is a simple virtual ALSA driver, which can be used for audio applications/PCM middle layer |
8 | * testing or fuzzing. |
9 | * It can: |
10 | * - Simulate 'playback' and 'capture' actions |
11 | * - Generate random or pattern-based capture data |
12 | * - Check playback buffer for containing looped template, and notify about the results |
13 | * through the debugfs entry |
14 | * - Inject delays into the playback and capturing processes. See 'inject_delay' parameter. |
15 | * - Inject errors during the PCM callbacks. |
16 | * - Register custom RESET ioctl and notify when it is called through the debugfs entry |
17 | * - Work in interleaved and non-interleaved modes |
18 | * - Support up to 8 substreams |
19 | * - Support up to 4 channels |
20 | * - Support framerates from 8 kHz to 48 kHz |
21 | * |
22 | * When driver works in the capture mode with multiple channels, it duplicates the looped |
23 | * pattern to each separate channel. For example, if we have 2 channels, format = U8, interleaved |
24 | * access mode and pattern 'abacaba', the DMA buffer will look like aabbccaabbaaaa..., so buffer for |
25 | * each channel will contain abacabaabacaba... Same for the non-interleaved mode. |
26 | * |
27 | * However, it may break the capturing on the higher framerates with small period size, so it is |
28 | * better to choose larger period sizes. |
29 | * |
30 | * You can find the corresponding selftest in the 'alsa' selftests folder. |
31 | */ |
32 | |
33 | #include <linux/module.h> |
34 | #include <linux/init.h> |
35 | #include <sound/pcm.h> |
36 | #include <sound/core.h> |
37 | #include <linux/dma-mapping.h> |
38 | #include <linux/platform_device.h> |
39 | #include <linux/timer.h> |
40 | #include <linux/random.h> |
41 | #include <linux/debugfs.h> |
42 | #include <linux/delay.h> |
43 | |
44 | #define TIMER_PER_SEC 5 |
45 | #define TIMER_INTERVAL (HZ / TIMER_PER_SEC) |
46 | #define DELAY_JIFFIES HZ |
47 | #define PLAYBACK_SUBSTREAM_CNT 8 |
48 | #define CAPTURE_SUBSTREAM_CNT 8 |
49 | #define MAX_CHANNELS_NUM 4 |
50 | |
51 | #define DEFAULT_PATTERN "abacaba" |
52 | #define DEFAULT_PATTERN_LEN 7 |
53 | |
54 | #define FILL_MODE_RAND 0 |
55 | #define FILL_MODE_PAT 1 |
56 | |
57 | #define MAX_PATTERN_LEN 4096 |
58 | |
59 | static int index = -1; |
60 | static char *id = "pcmtest" ; |
61 | static bool enable = true; |
62 | static int inject_delay; |
63 | static bool inject_hwpars_err; |
64 | static bool inject_prepare_err; |
65 | static bool inject_trigger_err; |
66 | static bool inject_open_err; |
67 | |
68 | static short fill_mode = FILL_MODE_PAT; |
69 | |
70 | static u8 playback_capture_test; |
71 | static u8 ioctl_reset_test; |
72 | static struct dentry *driver_debug_dir; |
73 | |
74 | module_param(index, int, 0444); |
75 | MODULE_PARM_DESC(index, "Index value for pcmtest soundcard" ); |
76 | module_param(id, charp, 0444); |
77 | MODULE_PARM_DESC(id, "ID string for pcmtest soundcard" ); |
78 | module_param(enable, bool, 0444); |
79 | MODULE_PARM_DESC(enable, "Enable pcmtest soundcard." ); |
80 | module_param(fill_mode, short, 0600); |
81 | MODULE_PARM_DESC(fill_mode, "Buffer fill mode: rand(0) or pattern(1)" ); |
82 | module_param(inject_delay, int, 0600); |
83 | MODULE_PARM_DESC(inject_delay, "Inject delays during playback/capture (in jiffies)" ); |
84 | module_param(inject_hwpars_err, bool, 0600); |
85 | MODULE_PARM_DESC(inject_hwpars_err, "Inject EBUSY error in the 'hw_params' callback" ); |
86 | module_param(inject_prepare_err, bool, 0600); |
87 | MODULE_PARM_DESC(inject_prepare_err, "Inject EINVAL error in the 'prepare' callback" ); |
88 | module_param(inject_trigger_err, bool, 0600); |
89 | MODULE_PARM_DESC(inject_trigger_err, "Inject EINVAL error in the 'trigger' callback" ); |
90 | module_param(inject_open_err, bool, 0600); |
91 | MODULE_PARM_DESC(inject_open_err, "Inject EBUSY error in the 'open' callback" ); |
92 | |
93 | struct pcmtst { |
94 | struct snd_pcm *pcm; |
95 | struct snd_card *card; |
96 | struct platform_device *pdev; |
97 | }; |
98 | |
99 | struct pcmtst_buf_iter { |
100 | size_t buf_pos; // position in the DMA buffer |
101 | size_t period_pos; // period-relative position |
102 | size_t b_rw; // Bytes to write on every timer tick |
103 | size_t s_rw_ch; // Samples to write to one channel on every tick |
104 | unsigned int sample_bytes; // sample_bits / 8 |
105 | bool is_buf_corrupted; // playback test result indicator |
106 | size_t period_bytes; // bytes in a one period |
107 | bool interleaved; // Interleaved/Non-interleaved mode |
108 | size_t total_bytes; // Total bytes read/written |
109 | size_t chan_block; // Bytes in one channel buffer when non-interleaved |
110 | struct snd_pcm_substream *substream; |
111 | bool suspend; // We need to pause timer without shutting it down |
112 | struct timer_list timer_instance; |
113 | }; |
114 | |
115 | static struct snd_pcm_hardware snd_pcmtst_hw = { |
116 | .info = (SNDRV_PCM_INFO_INTERLEAVED | |
117 | SNDRV_PCM_INFO_BLOCK_TRANSFER | |
118 | SNDRV_PCM_INFO_NONINTERLEAVED | |
119 | SNDRV_PCM_INFO_MMAP_VALID | |
120 | SNDRV_PCM_INFO_PAUSE), |
121 | .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, |
122 | .rates = SNDRV_PCM_RATE_8000_48000, |
123 | .rate_min = 8000, |
124 | .rate_max = 48000, |
125 | .channels_min = 1, |
126 | .channels_max = MAX_CHANNELS_NUM, |
127 | .buffer_bytes_max = 128 * 1024, |
128 | .period_bytes_min = 4096, |
129 | .period_bytes_max = 32768, |
130 | .periods_min = 1, |
131 | .periods_max = 1024, |
132 | }; |
133 | |
134 | struct pattern_buf { |
135 | char *buf; |
136 | u32 len; |
137 | }; |
138 | |
139 | static int buf_allocated; |
140 | static struct pattern_buf patt_bufs[MAX_CHANNELS_NUM]; |
141 | |
142 | static inline void inc_buf_pos(struct pcmtst_buf_iter *v_iter, size_t by, size_t bytes) |
143 | { |
144 | v_iter->total_bytes += by; |
145 | v_iter->buf_pos += by; |
146 | if (v_iter->buf_pos >= bytes) |
147 | v_iter->buf_pos %= bytes; |
148 | } |
149 | |
150 | /* |
151 | * Position in the DMA buffer when we are in the non-interleaved mode. We increment buf_pos |
152 | * every time we write a byte to any channel, so the position in the current channel buffer is |
153 | * (position in the DMA buffer) / count_of_channels + size_of_channel_buf * current_channel |
154 | */ |
155 | static inline size_t buf_pos_n(struct pcmtst_buf_iter *v_iter, unsigned int channels, |
156 | unsigned int chan_num) |
157 | { |
158 | return v_iter->buf_pos / channels + v_iter->chan_block * chan_num; |
159 | } |
160 | |
161 | /* |
162 | * Get the count of bytes written for the current channel in the interleaved mode. |
163 | * This is (count of samples written for the current channel) * bytes_in_sample + |
164 | * (relative position in the current sample) |
165 | */ |
166 | static inline size_t ch_pos_i(size_t b_total, unsigned int channels, unsigned int b_sample) |
167 | { |
168 | return b_total / channels / b_sample * b_sample + (b_total % b_sample); |
169 | } |
170 | |
171 | static void check_buf_block_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) |
172 | { |
173 | size_t i; |
174 | short ch_num; |
175 | u8 current_byte; |
176 | |
177 | for (i = 0; i < v_iter->b_rw; i++) { |
178 | current_byte = runtime->dma_area[v_iter->buf_pos]; |
179 | if (!current_byte) |
180 | break; |
181 | ch_num = (v_iter->total_bytes / v_iter->sample_bytes) % runtime->channels; |
182 | if (current_byte != patt_bufs[ch_num].buf[ch_pos_i(b_total: v_iter->total_bytes, |
183 | channels: runtime->channels, |
184 | b_sample: v_iter->sample_bytes) |
185 | % patt_bufs[ch_num].len]) { |
186 | v_iter->is_buf_corrupted = true; |
187 | break; |
188 | } |
189 | inc_buf_pos(v_iter, by: 1, bytes: runtime->dma_bytes); |
190 | } |
191 | // If we broke during the loop, add remaining bytes to the buffer position. |
192 | inc_buf_pos(v_iter, by: v_iter->b_rw - i, bytes: runtime->dma_bytes); |
193 | } |
194 | |
195 | static void check_buf_block_ni(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) |
196 | { |
197 | unsigned int channels = runtime->channels; |
198 | size_t i; |
199 | short ch_num; |
200 | u8 current_byte; |
201 | |
202 | for (i = 0; i < v_iter->b_rw; i++) { |
203 | ch_num = i % channels; |
204 | current_byte = runtime->dma_area[buf_pos_n(v_iter, channels, chan_num: ch_num)]; |
205 | if (!current_byte) |
206 | break; |
207 | if (current_byte != patt_bufs[ch_num].buf[(v_iter->total_bytes / channels) |
208 | % patt_bufs[ch_num].len]) { |
209 | v_iter->is_buf_corrupted = true; |
210 | break; |
211 | } |
212 | inc_buf_pos(v_iter, by: 1, bytes: runtime->dma_bytes); |
213 | } |
214 | inc_buf_pos(v_iter, by: v_iter->b_rw - i, bytes: runtime->dma_bytes); |
215 | } |
216 | |
217 | /* |
218 | * Check one block of the buffer. Here we iterate the buffer until we find '0'. This condition is |
219 | * necessary because we need to detect when the reading/writing ends, so we assume that the pattern |
220 | * doesn't contain zeros. |
221 | */ |
222 | static void check_buf_block(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) |
223 | { |
224 | if (v_iter->interleaved) |
225 | check_buf_block_i(v_iter, runtime); |
226 | else |
227 | check_buf_block_ni(v_iter, runtime); |
228 | } |
229 | |
230 | /* |
231 | * Fill buffer in the non-interleaved mode. The order of samples is C0, ..., C0, C1, ..., C1, C2... |
232 | * The channel buffers lay in the DMA buffer continuously (see default copy |
233 | * handlers in the pcm_lib.c file). |
234 | * |
235 | * Here we increment the DMA buffer position every time we write a byte to any channel 'buffer'. |
236 | * We need this to simulate the correct hardware pointer moving. |
237 | */ |
238 | static void fill_block_pattern_n(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) |
239 | { |
240 | size_t i; |
241 | unsigned int channels = runtime->channels; |
242 | short ch_num; |
243 | |
244 | for (i = 0; i < v_iter->b_rw; i++) { |
245 | ch_num = i % channels; |
246 | runtime->dma_area[buf_pos_n(v_iter, channels, chan_num: ch_num)] = |
247 | patt_bufs[ch_num].buf[(v_iter->total_bytes / channels) |
248 | % patt_bufs[ch_num].len]; |
249 | inc_buf_pos(v_iter, by: 1, bytes: runtime->dma_bytes); |
250 | } |
251 | } |
252 | |
253 | // Fill buffer in the interleaved mode. The order of samples is C0, C1, C2, C0, C1, C2, ... |
254 | static void fill_block_pattern_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) |
255 | { |
256 | size_t sample; |
257 | size_t pos_in_ch, pos_pattern; |
258 | short ch, pos_sample; |
259 | |
260 | pos_in_ch = ch_pos_i(b_total: v_iter->total_bytes, channels: runtime->channels, b_sample: v_iter->sample_bytes); |
261 | |
262 | for (sample = 0; sample < v_iter->s_rw_ch; sample++) { |
263 | for (ch = 0; ch < runtime->channels; ch++) { |
264 | for (pos_sample = 0; pos_sample < v_iter->sample_bytes; pos_sample++) { |
265 | pos_pattern = (pos_in_ch + sample * v_iter->sample_bytes |
266 | + pos_sample) % patt_bufs[ch].len; |
267 | runtime->dma_area[v_iter->buf_pos] = patt_bufs[ch].buf[pos_pattern]; |
268 | inc_buf_pos(v_iter, by: 1, bytes: runtime->dma_bytes); |
269 | } |
270 | } |
271 | } |
272 | } |
273 | |
274 | static void fill_block_pattern(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) |
275 | { |
276 | if (v_iter->interleaved) |
277 | fill_block_pattern_i(v_iter, runtime); |
278 | else |
279 | fill_block_pattern_n(v_iter, runtime); |
280 | } |
281 | |
282 | static void fill_block_rand_n(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) |
283 | { |
284 | unsigned int channels = runtime->channels; |
285 | // Remaining space in all channel buffers |
286 | size_t bytes_remain = runtime->dma_bytes - v_iter->buf_pos; |
287 | unsigned int i; |
288 | |
289 | for (i = 0; i < channels; i++) { |
290 | if (v_iter->b_rw <= bytes_remain) { |
291 | //b_rw - count of bytes must be written for all channels at each timer tick |
292 | get_random_bytes(buf: runtime->dma_area + buf_pos_n(v_iter, channels, chan_num: i), |
293 | len: v_iter->b_rw / channels); |
294 | } else { |
295 | // Write to the end of buffer and start from the beginning of it |
296 | get_random_bytes(buf: runtime->dma_area + buf_pos_n(v_iter, channels, chan_num: i), |
297 | len: bytes_remain / channels); |
298 | get_random_bytes(buf: runtime->dma_area + v_iter->chan_block * i, |
299 | len: (v_iter->b_rw - bytes_remain) / channels); |
300 | } |
301 | } |
302 | inc_buf_pos(v_iter, by: v_iter->b_rw, bytes: runtime->dma_bytes); |
303 | } |
304 | |
305 | static void fill_block_rand_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) |
306 | { |
307 | size_t in_cur_block = runtime->dma_bytes - v_iter->buf_pos; |
308 | |
309 | if (v_iter->b_rw <= in_cur_block) { |
310 | get_random_bytes(buf: &runtime->dma_area[v_iter->buf_pos], len: v_iter->b_rw); |
311 | } else { |
312 | get_random_bytes(buf: &runtime->dma_area[v_iter->buf_pos], len: in_cur_block); |
313 | get_random_bytes(buf: runtime->dma_area, len: v_iter->b_rw - in_cur_block); |
314 | } |
315 | inc_buf_pos(v_iter, by: v_iter->b_rw, bytes: runtime->dma_bytes); |
316 | } |
317 | |
318 | static void fill_block_random(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) |
319 | { |
320 | if (v_iter->interleaved) |
321 | fill_block_rand_i(v_iter, runtime); |
322 | else |
323 | fill_block_rand_n(v_iter, runtime); |
324 | } |
325 | |
326 | static void fill_block(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) |
327 | { |
328 | switch (fill_mode) { |
329 | case FILL_MODE_RAND: |
330 | fill_block_random(v_iter, runtime); |
331 | break; |
332 | case FILL_MODE_PAT: |
333 | fill_block_pattern(v_iter, runtime); |
334 | break; |
335 | } |
336 | } |
337 | |
338 | /* |
339 | * Here we iterate through the buffer by (buffer_size / iterates_per_second) bytes. |
340 | * The driver uses timer to simulate the hardware pointer moving, and notify the PCM middle layer |
341 | * about period elapsed. |
342 | */ |
343 | static void timer_timeout(struct timer_list *data) |
344 | { |
345 | struct pcmtst_buf_iter *v_iter; |
346 | struct snd_pcm_substream *substream; |
347 | |
348 | v_iter = from_timer(v_iter, data, timer_instance); |
349 | substream = v_iter->substream; |
350 | |
351 | if (v_iter->suspend) |
352 | return; |
353 | |
354 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !v_iter->is_buf_corrupted) |
355 | check_buf_block(v_iter, runtime: substream->runtime); |
356 | else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) |
357 | fill_block(v_iter, runtime: substream->runtime); |
358 | else |
359 | inc_buf_pos(v_iter, by: v_iter->b_rw, bytes: substream->runtime->dma_bytes); |
360 | |
361 | v_iter->period_pos += v_iter->b_rw; |
362 | if (v_iter->period_pos >= v_iter->period_bytes) { |
363 | v_iter->period_pos %= v_iter->period_bytes; |
364 | snd_pcm_period_elapsed(substream); |
365 | } |
366 | |
367 | if (!v_iter->suspend) |
368 | mod_timer(timer: &v_iter->timer_instance, expires: jiffies + TIMER_INTERVAL + inject_delay); |
369 | } |
370 | |
371 | static int snd_pcmtst_pcm_open(struct snd_pcm_substream *substream) |
372 | { |
373 | struct snd_pcm_runtime *runtime = substream->runtime; |
374 | struct pcmtst_buf_iter *v_iter; |
375 | |
376 | if (inject_open_err) |
377 | return -EBUSY; |
378 | |
379 | v_iter = kzalloc(size: sizeof(*v_iter), GFP_KERNEL); |
380 | if (!v_iter) |
381 | return -ENOMEM; |
382 | |
383 | v_iter->substream = substream; |
384 | runtime->hw = snd_pcmtst_hw; |
385 | runtime->private_data = v_iter; |
386 | |
387 | playback_capture_test = 0; |
388 | ioctl_reset_test = 0; |
389 | |
390 | timer_setup(&v_iter->timer_instance, timer_timeout, 0); |
391 | |
392 | return 0; |
393 | } |
394 | |
395 | static int snd_pcmtst_pcm_close(struct snd_pcm_substream *substream) |
396 | { |
397 | struct pcmtst_buf_iter *v_iter = substream->runtime->private_data; |
398 | |
399 | timer_shutdown_sync(timer: &v_iter->timer_instance); |
400 | playback_capture_test = !v_iter->is_buf_corrupted; |
401 | kfree(objp: v_iter); |
402 | return 0; |
403 | } |
404 | |
405 | static inline void reset_buf_iterator(struct pcmtst_buf_iter *v_iter) |
406 | { |
407 | v_iter->buf_pos = 0; |
408 | v_iter->is_buf_corrupted = false; |
409 | v_iter->period_pos = 0; |
410 | v_iter->total_bytes = 0; |
411 | } |
412 | |
413 | static inline void start_pcmtest_timer(struct pcmtst_buf_iter *v_iter) |
414 | { |
415 | v_iter->suspend = false; |
416 | mod_timer(timer: &v_iter->timer_instance, expires: jiffies + TIMER_INTERVAL); |
417 | } |
418 | |
419 | static int snd_pcmtst_pcm_trigger(struct snd_pcm_substream *substream, int cmd) |
420 | { |
421 | struct pcmtst_buf_iter *v_iter = substream->runtime->private_data; |
422 | |
423 | if (inject_trigger_err) |
424 | return -EINVAL; |
425 | switch (cmd) { |
426 | case SNDRV_PCM_TRIGGER_START: |
427 | reset_buf_iterator(v_iter); |
428 | start_pcmtest_timer(v_iter); |
429 | break; |
430 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
431 | start_pcmtest_timer(v_iter); |
432 | break; |
433 | case SNDRV_PCM_TRIGGER_STOP: |
434 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
435 | // We can't call timer_shutdown_sync here, as it is forbidden to sleep here |
436 | v_iter->suspend = true; |
437 | timer_delete(timer: &v_iter->timer_instance); |
438 | break; |
439 | } |
440 | |
441 | return 0; |
442 | } |
443 | |
444 | static snd_pcm_uframes_t snd_pcmtst_pcm_pointer(struct snd_pcm_substream *substream) |
445 | { |
446 | struct pcmtst_buf_iter *v_iter = substream->runtime->private_data; |
447 | |
448 | return bytes_to_frames(runtime: substream->runtime, size: v_iter->buf_pos); |
449 | } |
450 | |
451 | static int snd_pcmtst_free(struct pcmtst *pcmtst) |
452 | { |
453 | if (!pcmtst) |
454 | return 0; |
455 | kfree(objp: pcmtst); |
456 | return 0; |
457 | } |
458 | |
459 | // These callbacks are required, but empty - all freeing occurs in pdev_remove |
460 | static int snd_pcmtst_dev_free(struct snd_device *device) |
461 | { |
462 | return 0; |
463 | } |
464 | |
465 | static void pcmtst_pdev_release(struct device *dev) |
466 | { |
467 | } |
468 | |
469 | static int snd_pcmtst_pcm_prepare(struct snd_pcm_substream *substream) |
470 | { |
471 | struct snd_pcm_runtime *runtime = substream->runtime; |
472 | struct pcmtst_buf_iter *v_iter = runtime->private_data; |
473 | |
474 | if (inject_prepare_err) |
475 | return -EINVAL; |
476 | |
477 | v_iter->sample_bytes = samples_to_bytes(runtime, size: 1); |
478 | v_iter->period_bytes = snd_pcm_lib_period_bytes(substream); |
479 | v_iter->interleaved = true; |
480 | if (runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED || |
481 | runtime->access == SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED) { |
482 | v_iter->chan_block = snd_pcm_lib_buffer_bytes(substream) / runtime->channels; |
483 | v_iter->interleaved = false; |
484 | } |
485 | // We want to record RATE * ch_cnt samples per sec, it is rate * sample_bytes * ch_cnt bytes |
486 | v_iter->s_rw_ch = runtime->rate / TIMER_PER_SEC; |
487 | v_iter->b_rw = v_iter->s_rw_ch * v_iter->sample_bytes * runtime->channels; |
488 | |
489 | return 0; |
490 | } |
491 | |
492 | static int snd_pcmtst_pcm_hw_params(struct snd_pcm_substream *substream, |
493 | struct snd_pcm_hw_params *params) |
494 | { |
495 | if (inject_hwpars_err) |
496 | return -EBUSY; |
497 | return 0; |
498 | } |
499 | |
500 | static int snd_pcmtst_pcm_hw_free(struct snd_pcm_substream *substream) |
501 | { |
502 | return 0; |
503 | } |
504 | |
505 | static int snd_pcmtst_ioctl(struct snd_pcm_substream *substream, unsigned int cmd, void *arg) |
506 | { |
507 | switch (cmd) { |
508 | case SNDRV_PCM_IOCTL1_RESET: |
509 | ioctl_reset_test = 1; |
510 | break; |
511 | } |
512 | return snd_pcm_lib_ioctl(substream, cmd, arg); |
513 | } |
514 | |
515 | static int snd_pcmtst_sync_stop(struct snd_pcm_substream *substream) |
516 | { |
517 | struct pcmtst_buf_iter *v_iter = substream->runtime->private_data; |
518 | |
519 | timer_delete_sync(timer: &v_iter->timer_instance); |
520 | |
521 | return 0; |
522 | } |
523 | |
524 | static const struct snd_pcm_ops snd_pcmtst_playback_ops = { |
525 | .open = snd_pcmtst_pcm_open, |
526 | .close = snd_pcmtst_pcm_close, |
527 | .trigger = snd_pcmtst_pcm_trigger, |
528 | .hw_params = snd_pcmtst_pcm_hw_params, |
529 | .ioctl = snd_pcmtst_ioctl, |
530 | .sync_stop = snd_pcmtst_sync_stop, |
531 | .hw_free = snd_pcmtst_pcm_hw_free, |
532 | .prepare = snd_pcmtst_pcm_prepare, |
533 | .pointer = snd_pcmtst_pcm_pointer, |
534 | }; |
535 | |
536 | static const struct snd_pcm_ops snd_pcmtst_capture_ops = { |
537 | .open = snd_pcmtst_pcm_open, |
538 | .close = snd_pcmtst_pcm_close, |
539 | .trigger = snd_pcmtst_pcm_trigger, |
540 | .hw_params = snd_pcmtst_pcm_hw_params, |
541 | .hw_free = snd_pcmtst_pcm_hw_free, |
542 | .ioctl = snd_pcmtst_ioctl, |
543 | .sync_stop = snd_pcmtst_sync_stop, |
544 | .prepare = snd_pcmtst_pcm_prepare, |
545 | .pointer = snd_pcmtst_pcm_pointer, |
546 | }; |
547 | |
548 | static int snd_pcmtst_new_pcm(struct pcmtst *pcmtst) |
549 | { |
550 | struct snd_pcm *pcm; |
551 | int err; |
552 | |
553 | err = snd_pcm_new(card: pcmtst->card, id: "PCMTest" , device: 0, PLAYBACK_SUBSTREAM_CNT, |
554 | CAPTURE_SUBSTREAM_CNT, rpcm: &pcm); |
555 | if (err < 0) |
556 | return err; |
557 | pcm->private_data = pcmtst; |
558 | strcpy(p: pcm->name, q: "PCMTest" ); |
559 | pcmtst->pcm = pcm; |
560 | snd_pcm_set_ops(pcm, direction: SNDRV_PCM_STREAM_PLAYBACK, ops: &snd_pcmtst_playback_ops); |
561 | snd_pcm_set_ops(pcm, direction: SNDRV_PCM_STREAM_CAPTURE, ops: &snd_pcmtst_capture_ops); |
562 | |
563 | err = snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, data: &pcmtst->pdev->dev, |
564 | size: 0, max: 128 * 1024); |
565 | return err; |
566 | } |
567 | |
568 | static int snd_pcmtst_create(struct snd_card *card, struct platform_device *pdev, |
569 | struct pcmtst **r_pcmtst) |
570 | { |
571 | struct pcmtst *pcmtst; |
572 | int err; |
573 | static const struct snd_device_ops ops = { |
574 | .dev_free = snd_pcmtst_dev_free, |
575 | }; |
576 | |
577 | pcmtst = kzalloc(size: sizeof(*pcmtst), GFP_KERNEL); |
578 | if (!pcmtst) |
579 | return -ENOMEM; |
580 | pcmtst->card = card; |
581 | pcmtst->pdev = pdev; |
582 | |
583 | err = snd_device_new(card, type: SNDRV_DEV_LOWLEVEL, device_data: pcmtst, ops: &ops); |
584 | if (err < 0) |
585 | goto _err_free_chip; |
586 | |
587 | err = snd_pcmtst_new_pcm(pcmtst); |
588 | if (err < 0) |
589 | goto _err_free_chip; |
590 | |
591 | *r_pcmtst = pcmtst; |
592 | return 0; |
593 | |
594 | _err_free_chip: |
595 | snd_pcmtst_free(pcmtst); |
596 | return err; |
597 | } |
598 | |
599 | static int pcmtst_probe(struct platform_device *pdev) |
600 | { |
601 | struct snd_card *card; |
602 | struct pcmtst *pcmtst; |
603 | int err; |
604 | |
605 | err = dma_set_mask_and_coherent(dev: &pdev->dev, DMA_BIT_MASK(32)); |
606 | if (err) |
607 | return err; |
608 | |
609 | err = snd_devm_card_new(parent: &pdev->dev, idx: index, xid: id, THIS_MODULE, extra_size: 0, card_ret: &card); |
610 | if (err < 0) |
611 | return err; |
612 | err = snd_pcmtst_create(card, pdev, r_pcmtst: &pcmtst); |
613 | if (err < 0) |
614 | return err; |
615 | |
616 | strcpy(p: card->driver, q: "PCM-TEST Driver" ); |
617 | strcpy(p: card->shortname, q: "PCM-Test" ); |
618 | strcpy(p: card->longname, q: "PCM-Test virtual driver" ); |
619 | |
620 | err = snd_card_register(card); |
621 | if (err < 0) |
622 | return err; |
623 | |
624 | platform_set_drvdata(pdev, data: pcmtst); |
625 | |
626 | return 0; |
627 | } |
628 | |
629 | static void pdev_remove(struct platform_device *pdev) |
630 | { |
631 | struct pcmtst *pcmtst = platform_get_drvdata(pdev); |
632 | |
633 | snd_pcmtst_free(pcmtst); |
634 | } |
635 | |
636 | static struct platform_device pcmtst_pdev = { |
637 | .name = "pcmtest" , |
638 | .dev.release = pcmtst_pdev_release, |
639 | }; |
640 | |
641 | static struct platform_driver pcmtst_pdrv = { |
642 | .probe = pcmtst_probe, |
643 | .remove_new = pdev_remove, |
644 | .driver = { |
645 | .name = "pcmtest" , |
646 | }, |
647 | }; |
648 | |
649 | static ssize_t pattern_write(struct file *file, const char __user *u_buff, size_t len, loff_t *off) |
650 | { |
651 | struct pattern_buf *patt_buf = file->f_inode->i_private; |
652 | ssize_t to_write = len; |
653 | |
654 | if (*off + to_write > MAX_PATTERN_LEN) |
655 | to_write = MAX_PATTERN_LEN - *off; |
656 | |
657 | // Crop silently everything over the buffer |
658 | if (to_write <= 0) |
659 | return len; |
660 | |
661 | if (copy_from_user(to: patt_buf->buf + *off, from: u_buff, n: to_write)) |
662 | return -EFAULT; |
663 | |
664 | patt_buf->len = *off + to_write; |
665 | *off += to_write; |
666 | |
667 | return to_write; |
668 | } |
669 | |
670 | static ssize_t pattern_read(struct file *file, char __user *u_buff, size_t len, loff_t *off) |
671 | { |
672 | struct pattern_buf *patt_buf = file->f_inode->i_private; |
673 | ssize_t to_read = len; |
674 | |
675 | if (*off + to_read >= MAX_PATTERN_LEN) |
676 | to_read = MAX_PATTERN_LEN - *off; |
677 | if (to_read <= 0) |
678 | return 0; |
679 | |
680 | if (copy_to_user(to: u_buff, from: patt_buf->buf + *off, n: to_read)) |
681 | to_read = 0; |
682 | else |
683 | *off += to_read; |
684 | |
685 | return to_read; |
686 | } |
687 | |
688 | static const struct file_operations fill_pattern_fops = { |
689 | .read = pattern_read, |
690 | .write = pattern_write, |
691 | }; |
692 | |
693 | static int setup_patt_bufs(void) |
694 | { |
695 | size_t i; |
696 | |
697 | for (i = 0; i < ARRAY_SIZE(patt_bufs); i++) { |
698 | patt_bufs[i].buf = kzalloc(MAX_PATTERN_LEN, GFP_KERNEL); |
699 | if (!patt_bufs[i].buf) |
700 | break; |
701 | strcpy(p: patt_bufs[i].buf, DEFAULT_PATTERN); |
702 | patt_bufs[i].len = DEFAULT_PATTERN_LEN; |
703 | } |
704 | |
705 | return i; |
706 | } |
707 | |
708 | static const char * const pattern_files[] = { "fill_pattern0" , "fill_pattern1" , |
709 | "fill_pattern2" , "fill_pattern3" }; |
710 | static int init_debug_files(int buf_count) |
711 | { |
712 | size_t i; |
713 | char len_file_name[32]; |
714 | |
715 | driver_debug_dir = debugfs_create_dir(name: "pcmtest" , NULL); |
716 | if (IS_ERR(ptr: driver_debug_dir)) |
717 | return PTR_ERR(ptr: driver_debug_dir); |
718 | debugfs_create_u8(name: "pc_test" , mode: 0444, parent: driver_debug_dir, value: &playback_capture_test); |
719 | debugfs_create_u8(name: "ioctl_test" , mode: 0444, parent: driver_debug_dir, value: &ioctl_reset_test); |
720 | |
721 | for (i = 0; i < buf_count; i++) { |
722 | debugfs_create_file(name: pattern_files[i], mode: 0600, parent: driver_debug_dir, |
723 | data: &patt_bufs[i], fops: &fill_pattern_fops); |
724 | snprintf(buf: len_file_name, size: sizeof(len_file_name), fmt: "%s_len" , pattern_files[i]); |
725 | debugfs_create_u32(name: len_file_name, mode: 0444, parent: driver_debug_dir, value: &patt_bufs[i].len); |
726 | } |
727 | |
728 | return 0; |
729 | } |
730 | |
731 | static void free_pattern_buffers(void) |
732 | { |
733 | int i; |
734 | |
735 | for (i = 0; i < buf_allocated; i++) |
736 | kfree(objp: patt_bufs[i].buf); |
737 | } |
738 | |
739 | static void clear_debug_files(void) |
740 | { |
741 | debugfs_remove_recursive(dentry: driver_debug_dir); |
742 | } |
743 | |
744 | static int __init mod_init(void) |
745 | { |
746 | int err = 0; |
747 | |
748 | buf_allocated = setup_patt_bufs(); |
749 | if (!buf_allocated) |
750 | return -ENOMEM; |
751 | |
752 | snd_pcmtst_hw.channels_max = buf_allocated; |
753 | |
754 | err = init_debug_files(buf_count: buf_allocated); |
755 | if (err) |
756 | return err; |
757 | err = platform_device_register(&pcmtst_pdev); |
758 | if (err) |
759 | return err; |
760 | err = platform_driver_register(&pcmtst_pdrv); |
761 | if (err) |
762 | platform_device_unregister(&pcmtst_pdev); |
763 | return err; |
764 | } |
765 | |
766 | static void __exit mod_exit(void) |
767 | { |
768 | clear_debug_files(); |
769 | free_pattern_buffers(); |
770 | |
771 | platform_driver_unregister(&pcmtst_pdrv); |
772 | platform_device_unregister(&pcmtst_pdev); |
773 | } |
774 | |
775 | MODULE_LICENSE("GPL" ); |
776 | MODULE_AUTHOR("Ivan Orlov" ); |
777 | module_init(mod_init); |
778 | module_exit(mod_exit); |
779 | |