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