xref: /kernel/linux/linux-6.6/sound/drivers/pcmtest.c (revision 62306a36)
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