162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Virtual ALSA driver for PCM testing/fuzzing 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright 2023 Ivan Orlov <ivan.orlov0322@gmail.com> 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * This is a simple virtual ALSA driver, which can be used for audio applications/PCM middle layer 862306a36Sopenharmony_ci * testing or fuzzing. 962306a36Sopenharmony_ci * It can: 1062306a36Sopenharmony_ci * - Simulate 'playback' and 'capture' actions 1162306a36Sopenharmony_ci * - Generate random or pattern-based capture data 1262306a36Sopenharmony_ci * - Check playback buffer for containing looped template, and notify about the results 1362306a36Sopenharmony_ci * through the debugfs entry 1462306a36Sopenharmony_ci * - Inject delays into the playback and capturing processes. See 'inject_delay' parameter. 1562306a36Sopenharmony_ci * - Inject errors during the PCM callbacks. 1662306a36Sopenharmony_ci * - Register custom RESET ioctl and notify when it is called through the debugfs entry 1762306a36Sopenharmony_ci * - Work in interleaved and non-interleaved modes 1862306a36Sopenharmony_ci * - Support up to 8 substreams 1962306a36Sopenharmony_ci * - Support up to 4 channels 2062306a36Sopenharmony_ci * - Support framerates from 8 kHz to 48 kHz 2162306a36Sopenharmony_ci * 2262306a36Sopenharmony_ci * When driver works in the capture mode with multiple channels, it duplicates the looped 2362306a36Sopenharmony_ci * pattern to each separate channel. For example, if we have 2 channels, format = U8, interleaved 2462306a36Sopenharmony_ci * access mode and pattern 'abacaba', the DMA buffer will look like aabbccaabbaaaa..., so buffer for 2562306a36Sopenharmony_ci * each channel will contain abacabaabacaba... Same for the non-interleaved mode. 2662306a36Sopenharmony_ci * 2762306a36Sopenharmony_ci * However, it may break the capturing on the higher framerates with small period size, so it is 2862306a36Sopenharmony_ci * better to choose larger period sizes. 2962306a36Sopenharmony_ci * 3062306a36Sopenharmony_ci * You can find the corresponding selftest in the 'alsa' selftests folder. 3162306a36Sopenharmony_ci */ 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci#include <linux/module.h> 3462306a36Sopenharmony_ci#include <linux/init.h> 3562306a36Sopenharmony_ci#include <sound/pcm.h> 3662306a36Sopenharmony_ci#include <sound/core.h> 3762306a36Sopenharmony_ci#include <linux/dma-mapping.h> 3862306a36Sopenharmony_ci#include <linux/platform_device.h> 3962306a36Sopenharmony_ci#include <linux/timer.h> 4062306a36Sopenharmony_ci#include <linux/random.h> 4162306a36Sopenharmony_ci#include <linux/debugfs.h> 4262306a36Sopenharmony_ci#include <linux/delay.h> 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci#define TIMER_PER_SEC 5 4562306a36Sopenharmony_ci#define TIMER_INTERVAL (HZ / TIMER_PER_SEC) 4662306a36Sopenharmony_ci#define DELAY_JIFFIES HZ 4762306a36Sopenharmony_ci#define PLAYBACK_SUBSTREAM_CNT 8 4862306a36Sopenharmony_ci#define CAPTURE_SUBSTREAM_CNT 8 4962306a36Sopenharmony_ci#define MAX_CHANNELS_NUM 4 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci#define DEFAULT_PATTERN "abacaba" 5262306a36Sopenharmony_ci#define DEFAULT_PATTERN_LEN 7 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci#define FILL_MODE_RAND 0 5562306a36Sopenharmony_ci#define FILL_MODE_PAT 1 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci#define MAX_PATTERN_LEN 4096 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistatic int index = -1; 6062306a36Sopenharmony_cistatic char *id = "pcmtest"; 6162306a36Sopenharmony_cistatic bool enable = true; 6262306a36Sopenharmony_cistatic int inject_delay; 6362306a36Sopenharmony_cistatic bool inject_hwpars_err; 6462306a36Sopenharmony_cistatic bool inject_prepare_err; 6562306a36Sopenharmony_cistatic bool inject_trigger_err; 6662306a36Sopenharmony_cistatic bool inject_open_err; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_cistatic short fill_mode = FILL_MODE_PAT; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cistatic u8 playback_capture_test; 7162306a36Sopenharmony_cistatic u8 ioctl_reset_test; 7262306a36Sopenharmony_cistatic struct dentry *driver_debug_dir; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cimodule_param(index, int, 0444); 7562306a36Sopenharmony_ciMODULE_PARM_DESC(index, "Index value for pcmtest soundcard"); 7662306a36Sopenharmony_cimodule_param(id, charp, 0444); 7762306a36Sopenharmony_ciMODULE_PARM_DESC(id, "ID string for pcmtest soundcard"); 7862306a36Sopenharmony_cimodule_param(enable, bool, 0444); 7962306a36Sopenharmony_ciMODULE_PARM_DESC(enable, "Enable pcmtest soundcard."); 8062306a36Sopenharmony_cimodule_param(fill_mode, short, 0600); 8162306a36Sopenharmony_ciMODULE_PARM_DESC(fill_mode, "Buffer fill mode: rand(0) or pattern(1)"); 8262306a36Sopenharmony_cimodule_param(inject_delay, int, 0600); 8362306a36Sopenharmony_ciMODULE_PARM_DESC(inject_delay, "Inject delays during playback/capture (in jiffies)"); 8462306a36Sopenharmony_cimodule_param(inject_hwpars_err, bool, 0600); 8562306a36Sopenharmony_ciMODULE_PARM_DESC(inject_hwpars_err, "Inject EBUSY error in the 'hw_params' callback"); 8662306a36Sopenharmony_cimodule_param(inject_prepare_err, bool, 0600); 8762306a36Sopenharmony_ciMODULE_PARM_DESC(inject_prepare_err, "Inject EINVAL error in the 'prepare' callback"); 8862306a36Sopenharmony_cimodule_param(inject_trigger_err, bool, 0600); 8962306a36Sopenharmony_ciMODULE_PARM_DESC(inject_trigger_err, "Inject EINVAL error in the 'trigger' callback"); 9062306a36Sopenharmony_cimodule_param(inject_open_err, bool, 0600); 9162306a36Sopenharmony_ciMODULE_PARM_DESC(inject_open_err, "Inject EBUSY error in the 'open' callback"); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_cistruct pcmtst { 9462306a36Sopenharmony_ci struct snd_pcm *pcm; 9562306a36Sopenharmony_ci struct snd_card *card; 9662306a36Sopenharmony_ci struct platform_device *pdev; 9762306a36Sopenharmony_ci}; 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_cistruct pcmtst_buf_iter { 10062306a36Sopenharmony_ci size_t buf_pos; // position in the DMA buffer 10162306a36Sopenharmony_ci size_t period_pos; // period-relative position 10262306a36Sopenharmony_ci size_t b_rw; // Bytes to write on every timer tick 10362306a36Sopenharmony_ci size_t s_rw_ch; // Samples to write to one channel on every tick 10462306a36Sopenharmony_ci unsigned int sample_bytes; // sample_bits / 8 10562306a36Sopenharmony_ci bool is_buf_corrupted; // playback test result indicator 10662306a36Sopenharmony_ci size_t period_bytes; // bytes in a one period 10762306a36Sopenharmony_ci bool interleaved; // Interleaved/Non-interleaved mode 10862306a36Sopenharmony_ci size_t total_bytes; // Total bytes read/written 10962306a36Sopenharmony_ci size_t chan_block; // Bytes in one channel buffer when non-interleaved 11062306a36Sopenharmony_ci struct snd_pcm_substream *substream; 11162306a36Sopenharmony_ci bool suspend; // We need to pause timer without shutting it down 11262306a36Sopenharmony_ci struct timer_list timer_instance; 11362306a36Sopenharmony_ci}; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_cistatic struct snd_pcm_hardware snd_pcmtst_hw = { 11662306a36Sopenharmony_ci .info = (SNDRV_PCM_INFO_INTERLEAVED | 11762306a36Sopenharmony_ci SNDRV_PCM_INFO_BLOCK_TRANSFER | 11862306a36Sopenharmony_ci SNDRV_PCM_INFO_NONINTERLEAVED | 11962306a36Sopenharmony_ci SNDRV_PCM_INFO_MMAP_VALID | 12062306a36Sopenharmony_ci SNDRV_PCM_INFO_PAUSE), 12162306a36Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, 12262306a36Sopenharmony_ci .rates = SNDRV_PCM_RATE_8000_48000, 12362306a36Sopenharmony_ci .rate_min = 8000, 12462306a36Sopenharmony_ci .rate_max = 48000, 12562306a36Sopenharmony_ci .channels_min = 1, 12662306a36Sopenharmony_ci .channels_max = MAX_CHANNELS_NUM, 12762306a36Sopenharmony_ci .buffer_bytes_max = 128 * 1024, 12862306a36Sopenharmony_ci .period_bytes_min = 4096, 12962306a36Sopenharmony_ci .period_bytes_max = 32768, 13062306a36Sopenharmony_ci .periods_min = 1, 13162306a36Sopenharmony_ci .periods_max = 1024, 13262306a36Sopenharmony_ci}; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_cistruct pattern_buf { 13562306a36Sopenharmony_ci char *buf; 13662306a36Sopenharmony_ci u32 len; 13762306a36Sopenharmony_ci}; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_cistatic int buf_allocated; 14062306a36Sopenharmony_cistatic struct pattern_buf patt_bufs[MAX_CHANNELS_NUM]; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_cistatic inline void inc_buf_pos(struct pcmtst_buf_iter *v_iter, size_t by, size_t bytes) 14362306a36Sopenharmony_ci{ 14462306a36Sopenharmony_ci v_iter->total_bytes += by; 14562306a36Sopenharmony_ci v_iter->buf_pos += by; 14662306a36Sopenharmony_ci if (v_iter->buf_pos >= bytes) 14762306a36Sopenharmony_ci v_iter->buf_pos %= bytes; 14862306a36Sopenharmony_ci} 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci/* 15162306a36Sopenharmony_ci * Position in the DMA buffer when we are in the non-interleaved mode. We increment buf_pos 15262306a36Sopenharmony_ci * every time we write a byte to any channel, so the position in the current channel buffer is 15362306a36Sopenharmony_ci * (position in the DMA buffer) / count_of_channels + size_of_channel_buf * current_channel 15462306a36Sopenharmony_ci */ 15562306a36Sopenharmony_cistatic inline size_t buf_pos_n(struct pcmtst_buf_iter *v_iter, unsigned int channels, 15662306a36Sopenharmony_ci unsigned int chan_num) 15762306a36Sopenharmony_ci{ 15862306a36Sopenharmony_ci return v_iter->buf_pos / channels + v_iter->chan_block * chan_num; 15962306a36Sopenharmony_ci} 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci/* 16262306a36Sopenharmony_ci * Get the count of bytes written for the current channel in the interleaved mode. 16362306a36Sopenharmony_ci * This is (count of samples written for the current channel) * bytes_in_sample + 16462306a36Sopenharmony_ci * (relative position in the current sample) 16562306a36Sopenharmony_ci */ 16662306a36Sopenharmony_cistatic inline size_t ch_pos_i(size_t b_total, unsigned int channels, unsigned int b_sample) 16762306a36Sopenharmony_ci{ 16862306a36Sopenharmony_ci return b_total / channels / b_sample * b_sample + (b_total % b_sample); 16962306a36Sopenharmony_ci} 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_cistatic void check_buf_block_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) 17262306a36Sopenharmony_ci{ 17362306a36Sopenharmony_ci size_t i; 17462306a36Sopenharmony_ci short ch_num; 17562306a36Sopenharmony_ci u8 current_byte; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci for (i = 0; i < v_iter->b_rw; i++) { 17862306a36Sopenharmony_ci current_byte = runtime->dma_area[v_iter->buf_pos]; 17962306a36Sopenharmony_ci if (!current_byte) 18062306a36Sopenharmony_ci break; 18162306a36Sopenharmony_ci ch_num = (v_iter->total_bytes / v_iter->sample_bytes) % runtime->channels; 18262306a36Sopenharmony_ci if (current_byte != patt_bufs[ch_num].buf[ch_pos_i(v_iter->total_bytes, 18362306a36Sopenharmony_ci runtime->channels, 18462306a36Sopenharmony_ci v_iter->sample_bytes) 18562306a36Sopenharmony_ci % patt_bufs[ch_num].len]) { 18662306a36Sopenharmony_ci v_iter->is_buf_corrupted = true; 18762306a36Sopenharmony_ci break; 18862306a36Sopenharmony_ci } 18962306a36Sopenharmony_ci inc_buf_pos(v_iter, 1, runtime->dma_bytes); 19062306a36Sopenharmony_ci } 19162306a36Sopenharmony_ci // If we broke during the loop, add remaining bytes to the buffer position. 19262306a36Sopenharmony_ci inc_buf_pos(v_iter, v_iter->b_rw - i, runtime->dma_bytes); 19362306a36Sopenharmony_ci} 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_cistatic void check_buf_block_ni(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) 19662306a36Sopenharmony_ci{ 19762306a36Sopenharmony_ci unsigned int channels = runtime->channels; 19862306a36Sopenharmony_ci size_t i; 19962306a36Sopenharmony_ci short ch_num; 20062306a36Sopenharmony_ci u8 current_byte; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci for (i = 0; i < v_iter->b_rw; i++) { 20362306a36Sopenharmony_ci ch_num = i % channels; 20462306a36Sopenharmony_ci current_byte = runtime->dma_area[buf_pos_n(v_iter, channels, ch_num)]; 20562306a36Sopenharmony_ci if (!current_byte) 20662306a36Sopenharmony_ci break; 20762306a36Sopenharmony_ci if (current_byte != patt_bufs[ch_num].buf[(v_iter->total_bytes / channels) 20862306a36Sopenharmony_ci % patt_bufs[ch_num].len]) { 20962306a36Sopenharmony_ci v_iter->is_buf_corrupted = true; 21062306a36Sopenharmony_ci break; 21162306a36Sopenharmony_ci } 21262306a36Sopenharmony_ci inc_buf_pos(v_iter, 1, runtime->dma_bytes); 21362306a36Sopenharmony_ci } 21462306a36Sopenharmony_ci inc_buf_pos(v_iter, v_iter->b_rw - i, runtime->dma_bytes); 21562306a36Sopenharmony_ci} 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci/* 21862306a36Sopenharmony_ci * Check one block of the buffer. Here we iterate the buffer until we find '0'. This condition is 21962306a36Sopenharmony_ci * necessary because we need to detect when the reading/writing ends, so we assume that the pattern 22062306a36Sopenharmony_ci * doesn't contain zeros. 22162306a36Sopenharmony_ci */ 22262306a36Sopenharmony_cistatic void check_buf_block(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) 22362306a36Sopenharmony_ci{ 22462306a36Sopenharmony_ci if (v_iter->interleaved) 22562306a36Sopenharmony_ci check_buf_block_i(v_iter, runtime); 22662306a36Sopenharmony_ci else 22762306a36Sopenharmony_ci check_buf_block_ni(v_iter, runtime); 22862306a36Sopenharmony_ci} 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci/* 23162306a36Sopenharmony_ci * Fill buffer in the non-interleaved mode. The order of samples is C0, ..., C0, C1, ..., C1, C2... 23262306a36Sopenharmony_ci * The channel buffers lay in the DMA buffer continuously (see default copy 23362306a36Sopenharmony_ci * handlers in the pcm_lib.c file). 23462306a36Sopenharmony_ci * 23562306a36Sopenharmony_ci * Here we increment the DMA buffer position every time we write a byte to any channel 'buffer'. 23662306a36Sopenharmony_ci * We need this to simulate the correct hardware pointer moving. 23762306a36Sopenharmony_ci */ 23862306a36Sopenharmony_cistatic void fill_block_pattern_n(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) 23962306a36Sopenharmony_ci{ 24062306a36Sopenharmony_ci size_t i; 24162306a36Sopenharmony_ci unsigned int channels = runtime->channels; 24262306a36Sopenharmony_ci short ch_num; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci for (i = 0; i < v_iter->b_rw; i++) { 24562306a36Sopenharmony_ci ch_num = i % channels; 24662306a36Sopenharmony_ci runtime->dma_area[buf_pos_n(v_iter, channels, ch_num)] = 24762306a36Sopenharmony_ci patt_bufs[ch_num].buf[(v_iter->total_bytes / channels) 24862306a36Sopenharmony_ci % patt_bufs[ch_num].len]; 24962306a36Sopenharmony_ci inc_buf_pos(v_iter, 1, runtime->dma_bytes); 25062306a36Sopenharmony_ci } 25162306a36Sopenharmony_ci} 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci// Fill buffer in the interleaved mode. The order of samples is C0, C1, C2, C0, C1, C2, ... 25462306a36Sopenharmony_cistatic void fill_block_pattern_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) 25562306a36Sopenharmony_ci{ 25662306a36Sopenharmony_ci size_t sample; 25762306a36Sopenharmony_ci size_t pos_in_ch, pos_pattern; 25862306a36Sopenharmony_ci short ch, pos_sample; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci pos_in_ch = ch_pos_i(v_iter->total_bytes, runtime->channels, v_iter->sample_bytes); 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci for (sample = 0; sample < v_iter->s_rw_ch; sample++) { 26362306a36Sopenharmony_ci for (ch = 0; ch < runtime->channels; ch++) { 26462306a36Sopenharmony_ci for (pos_sample = 0; pos_sample < v_iter->sample_bytes; pos_sample++) { 26562306a36Sopenharmony_ci pos_pattern = (pos_in_ch + sample * v_iter->sample_bytes 26662306a36Sopenharmony_ci + pos_sample) % patt_bufs[ch].len; 26762306a36Sopenharmony_ci runtime->dma_area[v_iter->buf_pos] = patt_bufs[ch].buf[pos_pattern]; 26862306a36Sopenharmony_ci inc_buf_pos(v_iter, 1, runtime->dma_bytes); 26962306a36Sopenharmony_ci } 27062306a36Sopenharmony_ci } 27162306a36Sopenharmony_ci } 27262306a36Sopenharmony_ci} 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_cistatic void fill_block_pattern(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) 27562306a36Sopenharmony_ci{ 27662306a36Sopenharmony_ci if (v_iter->interleaved) 27762306a36Sopenharmony_ci fill_block_pattern_i(v_iter, runtime); 27862306a36Sopenharmony_ci else 27962306a36Sopenharmony_ci fill_block_pattern_n(v_iter, runtime); 28062306a36Sopenharmony_ci} 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_cistatic void fill_block_rand_n(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) 28362306a36Sopenharmony_ci{ 28462306a36Sopenharmony_ci unsigned int channels = runtime->channels; 28562306a36Sopenharmony_ci // Remaining space in all channel buffers 28662306a36Sopenharmony_ci size_t bytes_remain = runtime->dma_bytes - v_iter->buf_pos; 28762306a36Sopenharmony_ci unsigned int i; 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci for (i = 0; i < channels; i++) { 29062306a36Sopenharmony_ci if (v_iter->b_rw <= bytes_remain) { 29162306a36Sopenharmony_ci //b_rw - count of bytes must be written for all channels at each timer tick 29262306a36Sopenharmony_ci get_random_bytes(runtime->dma_area + buf_pos_n(v_iter, channels, i), 29362306a36Sopenharmony_ci v_iter->b_rw / channels); 29462306a36Sopenharmony_ci } else { 29562306a36Sopenharmony_ci // Write to the end of buffer and start from the beginning of it 29662306a36Sopenharmony_ci get_random_bytes(runtime->dma_area + buf_pos_n(v_iter, channels, i), 29762306a36Sopenharmony_ci bytes_remain / channels); 29862306a36Sopenharmony_ci get_random_bytes(runtime->dma_area + v_iter->chan_block * i, 29962306a36Sopenharmony_ci (v_iter->b_rw - bytes_remain) / channels); 30062306a36Sopenharmony_ci } 30162306a36Sopenharmony_ci } 30262306a36Sopenharmony_ci inc_buf_pos(v_iter, v_iter->b_rw, runtime->dma_bytes); 30362306a36Sopenharmony_ci} 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_cistatic void fill_block_rand_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) 30662306a36Sopenharmony_ci{ 30762306a36Sopenharmony_ci size_t in_cur_block = runtime->dma_bytes - v_iter->buf_pos; 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci if (v_iter->b_rw <= in_cur_block) { 31062306a36Sopenharmony_ci get_random_bytes(&runtime->dma_area[v_iter->buf_pos], v_iter->b_rw); 31162306a36Sopenharmony_ci } else { 31262306a36Sopenharmony_ci get_random_bytes(&runtime->dma_area[v_iter->buf_pos], in_cur_block); 31362306a36Sopenharmony_ci get_random_bytes(runtime->dma_area, v_iter->b_rw - in_cur_block); 31462306a36Sopenharmony_ci } 31562306a36Sopenharmony_ci inc_buf_pos(v_iter, v_iter->b_rw, runtime->dma_bytes); 31662306a36Sopenharmony_ci} 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_cistatic void fill_block_random(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) 31962306a36Sopenharmony_ci{ 32062306a36Sopenharmony_ci if (v_iter->interleaved) 32162306a36Sopenharmony_ci fill_block_rand_i(v_iter, runtime); 32262306a36Sopenharmony_ci else 32362306a36Sopenharmony_ci fill_block_rand_n(v_iter, runtime); 32462306a36Sopenharmony_ci} 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_cistatic void fill_block(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) 32762306a36Sopenharmony_ci{ 32862306a36Sopenharmony_ci switch (fill_mode) { 32962306a36Sopenharmony_ci case FILL_MODE_RAND: 33062306a36Sopenharmony_ci fill_block_random(v_iter, runtime); 33162306a36Sopenharmony_ci break; 33262306a36Sopenharmony_ci case FILL_MODE_PAT: 33362306a36Sopenharmony_ci fill_block_pattern(v_iter, runtime); 33462306a36Sopenharmony_ci break; 33562306a36Sopenharmony_ci } 33662306a36Sopenharmony_ci} 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci/* 33962306a36Sopenharmony_ci * Here we iterate through the buffer by (buffer_size / iterates_per_second) bytes. 34062306a36Sopenharmony_ci * The driver uses timer to simulate the hardware pointer moving, and notify the PCM middle layer 34162306a36Sopenharmony_ci * about period elapsed. 34262306a36Sopenharmony_ci */ 34362306a36Sopenharmony_cistatic void timer_timeout(struct timer_list *data) 34462306a36Sopenharmony_ci{ 34562306a36Sopenharmony_ci struct pcmtst_buf_iter *v_iter; 34662306a36Sopenharmony_ci struct snd_pcm_substream *substream; 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci v_iter = from_timer(v_iter, data, timer_instance); 34962306a36Sopenharmony_ci substream = v_iter->substream; 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci if (v_iter->suspend) 35262306a36Sopenharmony_ci return; 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !v_iter->is_buf_corrupted) 35562306a36Sopenharmony_ci check_buf_block(v_iter, substream->runtime); 35662306a36Sopenharmony_ci else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) 35762306a36Sopenharmony_ci fill_block(v_iter, substream->runtime); 35862306a36Sopenharmony_ci else 35962306a36Sopenharmony_ci inc_buf_pos(v_iter, v_iter->b_rw, substream->runtime->dma_bytes); 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ci v_iter->period_pos += v_iter->b_rw; 36262306a36Sopenharmony_ci if (v_iter->period_pos >= v_iter->period_bytes) { 36362306a36Sopenharmony_ci v_iter->period_pos %= v_iter->period_bytes; 36462306a36Sopenharmony_ci snd_pcm_period_elapsed(substream); 36562306a36Sopenharmony_ci } 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci if (!v_iter->suspend) 36862306a36Sopenharmony_ci mod_timer(&v_iter->timer_instance, jiffies + TIMER_INTERVAL + inject_delay); 36962306a36Sopenharmony_ci} 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_cistatic int snd_pcmtst_pcm_open(struct snd_pcm_substream *substream) 37262306a36Sopenharmony_ci{ 37362306a36Sopenharmony_ci struct snd_pcm_runtime *runtime = substream->runtime; 37462306a36Sopenharmony_ci struct pcmtst_buf_iter *v_iter; 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci if (inject_open_err) 37762306a36Sopenharmony_ci return -EBUSY; 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci v_iter = kzalloc(sizeof(*v_iter), GFP_KERNEL); 38062306a36Sopenharmony_ci if (!v_iter) 38162306a36Sopenharmony_ci return -ENOMEM; 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci v_iter->substream = substream; 38462306a36Sopenharmony_ci runtime->hw = snd_pcmtst_hw; 38562306a36Sopenharmony_ci runtime->private_data = v_iter; 38662306a36Sopenharmony_ci 38762306a36Sopenharmony_ci playback_capture_test = 0; 38862306a36Sopenharmony_ci ioctl_reset_test = 0; 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci timer_setup(&v_iter->timer_instance, timer_timeout, 0); 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci return 0; 39362306a36Sopenharmony_ci} 39462306a36Sopenharmony_ci 39562306a36Sopenharmony_cistatic int snd_pcmtst_pcm_close(struct snd_pcm_substream *substream) 39662306a36Sopenharmony_ci{ 39762306a36Sopenharmony_ci struct pcmtst_buf_iter *v_iter = substream->runtime->private_data; 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_ci timer_shutdown_sync(&v_iter->timer_instance); 40062306a36Sopenharmony_ci playback_capture_test = !v_iter->is_buf_corrupted; 40162306a36Sopenharmony_ci kfree(v_iter); 40262306a36Sopenharmony_ci return 0; 40362306a36Sopenharmony_ci} 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_cistatic inline void reset_buf_iterator(struct pcmtst_buf_iter *v_iter) 40662306a36Sopenharmony_ci{ 40762306a36Sopenharmony_ci v_iter->buf_pos = 0; 40862306a36Sopenharmony_ci v_iter->is_buf_corrupted = false; 40962306a36Sopenharmony_ci v_iter->period_pos = 0; 41062306a36Sopenharmony_ci v_iter->total_bytes = 0; 41162306a36Sopenharmony_ci} 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_cistatic inline void start_pcmtest_timer(struct pcmtst_buf_iter *v_iter) 41462306a36Sopenharmony_ci{ 41562306a36Sopenharmony_ci v_iter->suspend = false; 41662306a36Sopenharmony_ci mod_timer(&v_iter->timer_instance, jiffies + TIMER_INTERVAL); 41762306a36Sopenharmony_ci} 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_cistatic int snd_pcmtst_pcm_trigger(struct snd_pcm_substream *substream, int cmd) 42062306a36Sopenharmony_ci{ 42162306a36Sopenharmony_ci struct pcmtst_buf_iter *v_iter = substream->runtime->private_data; 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci if (inject_trigger_err) 42462306a36Sopenharmony_ci return -EINVAL; 42562306a36Sopenharmony_ci switch (cmd) { 42662306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_START: 42762306a36Sopenharmony_ci reset_buf_iterator(v_iter); 42862306a36Sopenharmony_ci start_pcmtest_timer(v_iter); 42962306a36Sopenharmony_ci break; 43062306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 43162306a36Sopenharmony_ci start_pcmtest_timer(v_iter); 43262306a36Sopenharmony_ci break; 43362306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_STOP: 43462306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 43562306a36Sopenharmony_ci // We can't call timer_shutdown_sync here, as it is forbidden to sleep here 43662306a36Sopenharmony_ci v_iter->suspend = true; 43762306a36Sopenharmony_ci timer_delete(&v_iter->timer_instance); 43862306a36Sopenharmony_ci break; 43962306a36Sopenharmony_ci } 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci return 0; 44262306a36Sopenharmony_ci} 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_cistatic snd_pcm_uframes_t snd_pcmtst_pcm_pointer(struct snd_pcm_substream *substream) 44562306a36Sopenharmony_ci{ 44662306a36Sopenharmony_ci struct pcmtst_buf_iter *v_iter = substream->runtime->private_data; 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ci return bytes_to_frames(substream->runtime, v_iter->buf_pos); 44962306a36Sopenharmony_ci} 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_cistatic int snd_pcmtst_free(struct pcmtst *pcmtst) 45262306a36Sopenharmony_ci{ 45362306a36Sopenharmony_ci if (!pcmtst) 45462306a36Sopenharmony_ci return 0; 45562306a36Sopenharmony_ci kfree(pcmtst); 45662306a36Sopenharmony_ci return 0; 45762306a36Sopenharmony_ci} 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ci// These callbacks are required, but empty - all freeing occurs in pdev_remove 46062306a36Sopenharmony_cistatic int snd_pcmtst_dev_free(struct snd_device *device) 46162306a36Sopenharmony_ci{ 46262306a36Sopenharmony_ci return 0; 46362306a36Sopenharmony_ci} 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_cistatic void pcmtst_pdev_release(struct device *dev) 46662306a36Sopenharmony_ci{ 46762306a36Sopenharmony_ci} 46862306a36Sopenharmony_ci 46962306a36Sopenharmony_cistatic int snd_pcmtst_pcm_prepare(struct snd_pcm_substream *substream) 47062306a36Sopenharmony_ci{ 47162306a36Sopenharmony_ci struct snd_pcm_runtime *runtime = substream->runtime; 47262306a36Sopenharmony_ci struct pcmtst_buf_iter *v_iter = runtime->private_data; 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci if (inject_prepare_err) 47562306a36Sopenharmony_ci return -EINVAL; 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ci v_iter->sample_bytes = samples_to_bytes(runtime, 1); 47862306a36Sopenharmony_ci v_iter->period_bytes = snd_pcm_lib_period_bytes(substream); 47962306a36Sopenharmony_ci v_iter->interleaved = true; 48062306a36Sopenharmony_ci if (runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED || 48162306a36Sopenharmony_ci runtime->access == SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED) { 48262306a36Sopenharmony_ci v_iter->chan_block = snd_pcm_lib_buffer_bytes(substream) / runtime->channels; 48362306a36Sopenharmony_ci v_iter->interleaved = false; 48462306a36Sopenharmony_ci } 48562306a36Sopenharmony_ci // We want to record RATE * ch_cnt samples per sec, it is rate * sample_bytes * ch_cnt bytes 48662306a36Sopenharmony_ci v_iter->s_rw_ch = runtime->rate / TIMER_PER_SEC; 48762306a36Sopenharmony_ci v_iter->b_rw = v_iter->s_rw_ch * v_iter->sample_bytes * runtime->channels; 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_ci return 0; 49062306a36Sopenharmony_ci} 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_cistatic int snd_pcmtst_pcm_hw_params(struct snd_pcm_substream *substream, 49362306a36Sopenharmony_ci struct snd_pcm_hw_params *params) 49462306a36Sopenharmony_ci{ 49562306a36Sopenharmony_ci if (inject_hwpars_err) 49662306a36Sopenharmony_ci return -EBUSY; 49762306a36Sopenharmony_ci return 0; 49862306a36Sopenharmony_ci} 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_cistatic int snd_pcmtst_pcm_hw_free(struct snd_pcm_substream *substream) 50162306a36Sopenharmony_ci{ 50262306a36Sopenharmony_ci return 0; 50362306a36Sopenharmony_ci} 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_cistatic int snd_pcmtst_ioctl(struct snd_pcm_substream *substream, unsigned int cmd, void *arg) 50662306a36Sopenharmony_ci{ 50762306a36Sopenharmony_ci switch (cmd) { 50862306a36Sopenharmony_ci case SNDRV_PCM_IOCTL1_RESET: 50962306a36Sopenharmony_ci ioctl_reset_test = 1; 51062306a36Sopenharmony_ci break; 51162306a36Sopenharmony_ci } 51262306a36Sopenharmony_ci return snd_pcm_lib_ioctl(substream, cmd, arg); 51362306a36Sopenharmony_ci} 51462306a36Sopenharmony_ci 51562306a36Sopenharmony_cistatic int snd_pcmtst_sync_stop(struct snd_pcm_substream *substream) 51662306a36Sopenharmony_ci{ 51762306a36Sopenharmony_ci struct pcmtst_buf_iter *v_iter = substream->runtime->private_data; 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_ci timer_delete_sync(&v_iter->timer_instance); 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ci return 0; 52262306a36Sopenharmony_ci} 52362306a36Sopenharmony_ci 52462306a36Sopenharmony_cistatic const struct snd_pcm_ops snd_pcmtst_playback_ops = { 52562306a36Sopenharmony_ci .open = snd_pcmtst_pcm_open, 52662306a36Sopenharmony_ci .close = snd_pcmtst_pcm_close, 52762306a36Sopenharmony_ci .trigger = snd_pcmtst_pcm_trigger, 52862306a36Sopenharmony_ci .hw_params = snd_pcmtst_pcm_hw_params, 52962306a36Sopenharmony_ci .ioctl = snd_pcmtst_ioctl, 53062306a36Sopenharmony_ci .sync_stop = snd_pcmtst_sync_stop, 53162306a36Sopenharmony_ci .hw_free = snd_pcmtst_pcm_hw_free, 53262306a36Sopenharmony_ci .prepare = snd_pcmtst_pcm_prepare, 53362306a36Sopenharmony_ci .pointer = snd_pcmtst_pcm_pointer, 53462306a36Sopenharmony_ci}; 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_cistatic const struct snd_pcm_ops snd_pcmtst_capture_ops = { 53762306a36Sopenharmony_ci .open = snd_pcmtst_pcm_open, 53862306a36Sopenharmony_ci .close = snd_pcmtst_pcm_close, 53962306a36Sopenharmony_ci .trigger = snd_pcmtst_pcm_trigger, 54062306a36Sopenharmony_ci .hw_params = snd_pcmtst_pcm_hw_params, 54162306a36Sopenharmony_ci .hw_free = snd_pcmtst_pcm_hw_free, 54262306a36Sopenharmony_ci .ioctl = snd_pcmtst_ioctl, 54362306a36Sopenharmony_ci .sync_stop = snd_pcmtst_sync_stop, 54462306a36Sopenharmony_ci .prepare = snd_pcmtst_pcm_prepare, 54562306a36Sopenharmony_ci .pointer = snd_pcmtst_pcm_pointer, 54662306a36Sopenharmony_ci}; 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_cistatic int snd_pcmtst_new_pcm(struct pcmtst *pcmtst) 54962306a36Sopenharmony_ci{ 55062306a36Sopenharmony_ci struct snd_pcm *pcm; 55162306a36Sopenharmony_ci int err; 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_ci err = snd_pcm_new(pcmtst->card, "PCMTest", 0, PLAYBACK_SUBSTREAM_CNT, 55462306a36Sopenharmony_ci CAPTURE_SUBSTREAM_CNT, &pcm); 55562306a36Sopenharmony_ci if (err < 0) 55662306a36Sopenharmony_ci return err; 55762306a36Sopenharmony_ci pcm->private_data = pcmtst; 55862306a36Sopenharmony_ci strcpy(pcm->name, "PCMTest"); 55962306a36Sopenharmony_ci pcmtst->pcm = pcm; 56062306a36Sopenharmony_ci snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_pcmtst_playback_ops); 56162306a36Sopenharmony_ci snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_pcmtst_capture_ops); 56262306a36Sopenharmony_ci 56362306a36Sopenharmony_ci err = snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, &pcmtst->pdev->dev, 56462306a36Sopenharmony_ci 0, 128 * 1024); 56562306a36Sopenharmony_ci return err; 56662306a36Sopenharmony_ci} 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_cistatic int snd_pcmtst_create(struct snd_card *card, struct platform_device *pdev, 56962306a36Sopenharmony_ci struct pcmtst **r_pcmtst) 57062306a36Sopenharmony_ci{ 57162306a36Sopenharmony_ci struct pcmtst *pcmtst; 57262306a36Sopenharmony_ci int err; 57362306a36Sopenharmony_ci static const struct snd_device_ops ops = { 57462306a36Sopenharmony_ci .dev_free = snd_pcmtst_dev_free, 57562306a36Sopenharmony_ci }; 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ci pcmtst = kzalloc(sizeof(*pcmtst), GFP_KERNEL); 57862306a36Sopenharmony_ci if (!pcmtst) 57962306a36Sopenharmony_ci return -ENOMEM; 58062306a36Sopenharmony_ci pcmtst->card = card; 58162306a36Sopenharmony_ci pcmtst->pdev = pdev; 58262306a36Sopenharmony_ci 58362306a36Sopenharmony_ci err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, pcmtst, &ops); 58462306a36Sopenharmony_ci if (err < 0) 58562306a36Sopenharmony_ci goto _err_free_chip; 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci err = snd_pcmtst_new_pcm(pcmtst); 58862306a36Sopenharmony_ci if (err < 0) 58962306a36Sopenharmony_ci goto _err_free_chip; 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_ci *r_pcmtst = pcmtst; 59262306a36Sopenharmony_ci return 0; 59362306a36Sopenharmony_ci 59462306a36Sopenharmony_ci_err_free_chip: 59562306a36Sopenharmony_ci snd_pcmtst_free(pcmtst); 59662306a36Sopenharmony_ci return err; 59762306a36Sopenharmony_ci} 59862306a36Sopenharmony_ci 59962306a36Sopenharmony_cistatic int pcmtst_probe(struct platform_device *pdev) 60062306a36Sopenharmony_ci{ 60162306a36Sopenharmony_ci struct snd_card *card; 60262306a36Sopenharmony_ci struct pcmtst *pcmtst; 60362306a36Sopenharmony_ci int err; 60462306a36Sopenharmony_ci 60562306a36Sopenharmony_ci err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); 60662306a36Sopenharmony_ci if (err) 60762306a36Sopenharmony_ci return err; 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_ci err = snd_devm_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); 61062306a36Sopenharmony_ci if (err < 0) 61162306a36Sopenharmony_ci return err; 61262306a36Sopenharmony_ci err = snd_pcmtst_create(card, pdev, &pcmtst); 61362306a36Sopenharmony_ci if (err < 0) 61462306a36Sopenharmony_ci return err; 61562306a36Sopenharmony_ci 61662306a36Sopenharmony_ci strcpy(card->driver, "PCM-TEST Driver"); 61762306a36Sopenharmony_ci strcpy(card->shortname, "PCM-Test"); 61862306a36Sopenharmony_ci strcpy(card->longname, "PCM-Test virtual driver"); 61962306a36Sopenharmony_ci 62062306a36Sopenharmony_ci err = snd_card_register(card); 62162306a36Sopenharmony_ci if (err < 0) 62262306a36Sopenharmony_ci return err; 62362306a36Sopenharmony_ci 62462306a36Sopenharmony_ci platform_set_drvdata(pdev, pcmtst); 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_ci return 0; 62762306a36Sopenharmony_ci} 62862306a36Sopenharmony_ci 62962306a36Sopenharmony_cistatic void pdev_remove(struct platform_device *pdev) 63062306a36Sopenharmony_ci{ 63162306a36Sopenharmony_ci struct pcmtst *pcmtst = platform_get_drvdata(pdev); 63262306a36Sopenharmony_ci 63362306a36Sopenharmony_ci snd_pcmtst_free(pcmtst); 63462306a36Sopenharmony_ci} 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_cistatic struct platform_device pcmtst_pdev = { 63762306a36Sopenharmony_ci .name = "pcmtest", 63862306a36Sopenharmony_ci .dev.release = pcmtst_pdev_release, 63962306a36Sopenharmony_ci}; 64062306a36Sopenharmony_ci 64162306a36Sopenharmony_cistatic struct platform_driver pcmtst_pdrv = { 64262306a36Sopenharmony_ci .probe = pcmtst_probe, 64362306a36Sopenharmony_ci .remove_new = pdev_remove, 64462306a36Sopenharmony_ci .driver = { 64562306a36Sopenharmony_ci .name = "pcmtest", 64662306a36Sopenharmony_ci }, 64762306a36Sopenharmony_ci}; 64862306a36Sopenharmony_ci 64962306a36Sopenharmony_cistatic ssize_t pattern_write(struct file *file, const char __user *u_buff, size_t len, loff_t *off) 65062306a36Sopenharmony_ci{ 65162306a36Sopenharmony_ci struct pattern_buf *patt_buf = file->f_inode->i_private; 65262306a36Sopenharmony_ci ssize_t to_write = len; 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci if (*off + to_write > MAX_PATTERN_LEN) 65562306a36Sopenharmony_ci to_write = MAX_PATTERN_LEN - *off; 65662306a36Sopenharmony_ci 65762306a36Sopenharmony_ci // Crop silently everything over the buffer 65862306a36Sopenharmony_ci if (to_write <= 0) 65962306a36Sopenharmony_ci return len; 66062306a36Sopenharmony_ci 66162306a36Sopenharmony_ci if (copy_from_user(patt_buf->buf + *off, u_buff, to_write)) 66262306a36Sopenharmony_ci return -EFAULT; 66362306a36Sopenharmony_ci 66462306a36Sopenharmony_ci patt_buf->len = *off + to_write; 66562306a36Sopenharmony_ci *off += to_write; 66662306a36Sopenharmony_ci 66762306a36Sopenharmony_ci return to_write; 66862306a36Sopenharmony_ci} 66962306a36Sopenharmony_ci 67062306a36Sopenharmony_cistatic ssize_t pattern_read(struct file *file, char __user *u_buff, size_t len, loff_t *off) 67162306a36Sopenharmony_ci{ 67262306a36Sopenharmony_ci struct pattern_buf *patt_buf = file->f_inode->i_private; 67362306a36Sopenharmony_ci ssize_t to_read = len; 67462306a36Sopenharmony_ci 67562306a36Sopenharmony_ci if (*off + to_read >= MAX_PATTERN_LEN) 67662306a36Sopenharmony_ci to_read = MAX_PATTERN_LEN - *off; 67762306a36Sopenharmony_ci if (to_read <= 0) 67862306a36Sopenharmony_ci return 0; 67962306a36Sopenharmony_ci 68062306a36Sopenharmony_ci if (copy_to_user(u_buff, patt_buf->buf + *off, to_read)) 68162306a36Sopenharmony_ci to_read = 0; 68262306a36Sopenharmony_ci else 68362306a36Sopenharmony_ci *off += to_read; 68462306a36Sopenharmony_ci 68562306a36Sopenharmony_ci return to_read; 68662306a36Sopenharmony_ci} 68762306a36Sopenharmony_ci 68862306a36Sopenharmony_cistatic const struct file_operations fill_pattern_fops = { 68962306a36Sopenharmony_ci .read = pattern_read, 69062306a36Sopenharmony_ci .write = pattern_write, 69162306a36Sopenharmony_ci}; 69262306a36Sopenharmony_ci 69362306a36Sopenharmony_cistatic int setup_patt_bufs(void) 69462306a36Sopenharmony_ci{ 69562306a36Sopenharmony_ci size_t i; 69662306a36Sopenharmony_ci 69762306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(patt_bufs); i++) { 69862306a36Sopenharmony_ci patt_bufs[i].buf = kzalloc(MAX_PATTERN_LEN, GFP_KERNEL); 69962306a36Sopenharmony_ci if (!patt_bufs[i].buf) 70062306a36Sopenharmony_ci break; 70162306a36Sopenharmony_ci strcpy(patt_bufs[i].buf, DEFAULT_PATTERN); 70262306a36Sopenharmony_ci patt_bufs[i].len = DEFAULT_PATTERN_LEN; 70362306a36Sopenharmony_ci } 70462306a36Sopenharmony_ci 70562306a36Sopenharmony_ci return i; 70662306a36Sopenharmony_ci} 70762306a36Sopenharmony_ci 70862306a36Sopenharmony_cistatic const char * const pattern_files[] = { "fill_pattern0", "fill_pattern1", 70962306a36Sopenharmony_ci "fill_pattern2", "fill_pattern3"}; 71062306a36Sopenharmony_cistatic int init_debug_files(int buf_count) 71162306a36Sopenharmony_ci{ 71262306a36Sopenharmony_ci size_t i; 71362306a36Sopenharmony_ci char len_file_name[32]; 71462306a36Sopenharmony_ci 71562306a36Sopenharmony_ci driver_debug_dir = debugfs_create_dir("pcmtest", NULL); 71662306a36Sopenharmony_ci if (IS_ERR(driver_debug_dir)) 71762306a36Sopenharmony_ci return PTR_ERR(driver_debug_dir); 71862306a36Sopenharmony_ci debugfs_create_u8("pc_test", 0444, driver_debug_dir, &playback_capture_test); 71962306a36Sopenharmony_ci debugfs_create_u8("ioctl_test", 0444, driver_debug_dir, &ioctl_reset_test); 72062306a36Sopenharmony_ci 72162306a36Sopenharmony_ci for (i = 0; i < buf_count; i++) { 72262306a36Sopenharmony_ci debugfs_create_file(pattern_files[i], 0600, driver_debug_dir, 72362306a36Sopenharmony_ci &patt_bufs[i], &fill_pattern_fops); 72462306a36Sopenharmony_ci snprintf(len_file_name, sizeof(len_file_name), "%s_len", pattern_files[i]); 72562306a36Sopenharmony_ci debugfs_create_u32(len_file_name, 0444, driver_debug_dir, &patt_bufs[i].len); 72662306a36Sopenharmony_ci } 72762306a36Sopenharmony_ci 72862306a36Sopenharmony_ci return 0; 72962306a36Sopenharmony_ci} 73062306a36Sopenharmony_ci 73162306a36Sopenharmony_cistatic void free_pattern_buffers(void) 73262306a36Sopenharmony_ci{ 73362306a36Sopenharmony_ci int i; 73462306a36Sopenharmony_ci 73562306a36Sopenharmony_ci for (i = 0; i < buf_allocated; i++) 73662306a36Sopenharmony_ci kfree(patt_bufs[i].buf); 73762306a36Sopenharmony_ci} 73862306a36Sopenharmony_ci 73962306a36Sopenharmony_cistatic void clear_debug_files(void) 74062306a36Sopenharmony_ci{ 74162306a36Sopenharmony_ci debugfs_remove_recursive(driver_debug_dir); 74262306a36Sopenharmony_ci} 74362306a36Sopenharmony_ci 74462306a36Sopenharmony_cistatic int __init mod_init(void) 74562306a36Sopenharmony_ci{ 74662306a36Sopenharmony_ci int err = 0; 74762306a36Sopenharmony_ci 74862306a36Sopenharmony_ci buf_allocated = setup_patt_bufs(); 74962306a36Sopenharmony_ci if (!buf_allocated) 75062306a36Sopenharmony_ci return -ENOMEM; 75162306a36Sopenharmony_ci 75262306a36Sopenharmony_ci snd_pcmtst_hw.channels_max = buf_allocated; 75362306a36Sopenharmony_ci 75462306a36Sopenharmony_ci err = init_debug_files(buf_allocated); 75562306a36Sopenharmony_ci if (err) 75662306a36Sopenharmony_ci return err; 75762306a36Sopenharmony_ci err = platform_device_register(&pcmtst_pdev); 75862306a36Sopenharmony_ci if (err) 75962306a36Sopenharmony_ci return err; 76062306a36Sopenharmony_ci err = platform_driver_register(&pcmtst_pdrv); 76162306a36Sopenharmony_ci if (err) 76262306a36Sopenharmony_ci platform_device_unregister(&pcmtst_pdev); 76362306a36Sopenharmony_ci return err; 76462306a36Sopenharmony_ci} 76562306a36Sopenharmony_ci 76662306a36Sopenharmony_cistatic void __exit mod_exit(void) 76762306a36Sopenharmony_ci{ 76862306a36Sopenharmony_ci clear_debug_files(); 76962306a36Sopenharmony_ci free_pattern_buffers(); 77062306a36Sopenharmony_ci 77162306a36Sopenharmony_ci platform_driver_unregister(&pcmtst_pdrv); 77262306a36Sopenharmony_ci platform_device_unregister(&pcmtst_pdev); 77362306a36Sopenharmony_ci} 77462306a36Sopenharmony_ci 77562306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 77662306a36Sopenharmony_ciMODULE_AUTHOR("Ivan Orlov"); 77762306a36Sopenharmony_cimodule_init(mod_init); 77862306a36Sopenharmony_cimodule_exit(mod_exit); 779