brintos

brintos / linux-shallow public Read only

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