linux/sound/drivers/pcmtest.c

// SPDX-License-Identifier: GPL-2.0
/*
 * Virtual ALSA driver for PCM testing/fuzzing
 *
 * Copyright 2023 Ivan Orlov <[email protected]>
 *
 * This is a simple virtual ALSA driver, which can be used for audio applications/PCM middle layer
 * testing or fuzzing.
 * It can:
 *	- Simulate 'playback' and 'capture' actions
 *	- Generate random or pattern-based capture data
 *	- Check playback buffer for containing looped template, and notify about the results
 *	through the debugfs entry
 *	- Inject delays into the playback and capturing processes. See 'inject_delay' parameter.
 *	- Inject errors during the PCM callbacks.
 *	- Register custom RESET ioctl and notify when it is called through the debugfs entry
 *	- Work in interleaved and non-interleaved modes
 *	- Support up to 8 substreams
 *	- Support up to 4 channels
 *	- Support framerates from 8 kHz to 48 kHz
 *
 * When driver works in the capture mode with multiple channels, it duplicates the looped
 * pattern to each separate channel. For example, if we have 2 channels, format = U8, interleaved
 * access mode and pattern 'abacaba', the DMA buffer will look like aabbccaabbaaaa..., so buffer for
 * each channel will contain abacabaabacaba... Same for the non-interleaved mode.
 *
 * However, it may break the capturing on the higher framerates with small period size, so it is
 * better to choose larger period sizes.
 *
 * You can find the corresponding selftest in the 'alsa' selftests folder.
 */

#include <linux/module.h>
#include <linux/init.h>
#include <sound/pcm.h>
#include <sound/core.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/timer.h>
#include <linux/random.h>
#include <linux/debugfs.h>
#include <linux/delay.h>

#define TIMER_PER_SEC
#define TIMER_INTERVAL
#define DELAY_JIFFIES
#define PLAYBACK_SUBSTREAM_CNT
#define CAPTURE_SUBSTREAM_CNT
#define MAX_CHANNELS_NUM

#define DEFAULT_PATTERN
#define DEFAULT_PATTERN_LEN

#define FILL_MODE_RAND
#define FILL_MODE_PAT

#define MAX_PATTERN_LEN

static int index =;
static char *id =;
static bool enable =;
static int inject_delay;
static bool inject_hwpars_err;
static bool inject_prepare_err;
static bool inject_trigger_err;
static bool inject_open_err;

static short fill_mode =;

static u8 playback_capture_test;
static u8 ioctl_reset_test;
static struct dentry *driver_debug_dir;

module_param(index, int, 0444);
MODULE_PARM_DESC();
module_param(id, charp, 0444);
MODULE_PARM_DESC();
module_param(enable, bool, 0444);
MODULE_PARM_DESC();
module_param(fill_mode, short, 0600);
MODULE_PARM_DESC();
module_param(inject_delay, int, 0600);
MODULE_PARM_DESC();
module_param(inject_hwpars_err, bool, 0600);
MODULE_PARM_DESC();
module_param(inject_prepare_err, bool, 0600);
MODULE_PARM_DESC();
module_param(inject_trigger_err, bool, 0600);
MODULE_PARM_DESC();
module_param(inject_open_err, bool, 0600);
MODULE_PARM_DESC();

struct pcmtst {};

struct pcmtst_buf_iter {};

static struct snd_pcm_hardware snd_pcmtst_hw =;

struct pattern_buf {};

static int buf_allocated;
static struct pattern_buf patt_bufs[MAX_CHANNELS_NUM];

static inline void inc_buf_pos(struct pcmtst_buf_iter *v_iter, size_t by, size_t bytes)
{}

/*
 * Position in the DMA buffer when we are in the non-interleaved mode. We increment buf_pos
 * every time we write a byte to any channel, so the position in the current channel buffer is
 * (position in the DMA buffer) / count_of_channels + size_of_channel_buf * current_channel
 */
static inline size_t buf_pos_n(struct pcmtst_buf_iter *v_iter, unsigned int channels,
			       unsigned int chan_num)
{}

/*
 * Get the count of bytes written for the current channel in the interleaved mode.
 * This is (count of samples written for the current channel) * bytes_in_sample +
 * (relative position in the current sample)
 */
static inline size_t ch_pos_i(size_t b_total, unsigned int channels, unsigned int b_sample)
{}

static void check_buf_block_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime)
{}

static void check_buf_block_ni(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime)
{}

/*
 * Check one block of the buffer. Here we iterate the buffer until we find '0'. This condition is
 * necessary because we need to detect when the reading/writing ends, so we assume that the pattern
 * doesn't contain zeros.
 */
static void check_buf_block(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime)
{}

/*
 * Fill buffer in the non-interleaved mode. The order of samples is C0, ..., C0, C1, ..., C1, C2...
 * The channel buffers lay in the DMA buffer continuously (see default copy
 * handlers in the pcm_lib.c file).
 *
 * Here we increment the DMA buffer position every time we write a byte to any channel 'buffer'.
 * We need this to simulate the correct hardware pointer moving.
 */
static void fill_block_pattern_n(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime)
{}

// Fill buffer in the interleaved mode. The order of samples is C0, C1, C2, C0, C1, C2, ...
static void fill_block_pattern_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime)
{}

static void fill_block_pattern(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime)
{}

static void fill_block_rand_n(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime)
{}

static void fill_block_rand_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime)
{}

static void fill_block_random(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime)
{}

static void fill_block(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime)
{}

/*
 * Here we iterate through the buffer by (buffer_size / iterates_per_second) bytes.
 * The driver uses timer to simulate the hardware pointer moving, and notify the PCM middle layer
 * about period elapsed.
 */
static void timer_timeout(struct timer_list *data)
{}

static int snd_pcmtst_pcm_open(struct snd_pcm_substream *substream)
{}

static int snd_pcmtst_pcm_close(struct snd_pcm_substream *substream)
{}

static inline void reset_buf_iterator(struct pcmtst_buf_iter *v_iter)
{}

static inline void start_pcmtest_timer(struct pcmtst_buf_iter *v_iter)
{}

static int snd_pcmtst_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{}

static snd_pcm_uframes_t snd_pcmtst_pcm_pointer(struct snd_pcm_substream *substream)
{}

static int snd_pcmtst_free(struct pcmtst *pcmtst)
{}

// These callbacks are required, but empty - all freeing occurs in pdev_remove
static int snd_pcmtst_dev_free(struct snd_device *device)
{}

static void pcmtst_pdev_release(struct device *dev)
{}

static int snd_pcmtst_pcm_prepare(struct snd_pcm_substream *substream)
{}

static int snd_pcmtst_pcm_hw_params(struct snd_pcm_substream *substream,
				    struct snd_pcm_hw_params *params)
{}

static int snd_pcmtst_pcm_hw_free(struct snd_pcm_substream *substream)
{}

static int snd_pcmtst_ioctl(struct snd_pcm_substream *substream, unsigned int cmd, void *arg)
{}

static int snd_pcmtst_sync_stop(struct snd_pcm_substream *substream)
{}

static const struct snd_pcm_ops snd_pcmtst_playback_ops =;

static const struct snd_pcm_ops snd_pcmtst_capture_ops =;

static int snd_pcmtst_new_pcm(struct pcmtst *pcmtst)
{}

static int snd_pcmtst_create(struct snd_card *card, struct platform_device *pdev,
			     struct pcmtst **r_pcmtst)
{}

static int pcmtst_probe(struct platform_device *pdev)
{}

static void pdev_remove(struct platform_device *pdev)
{}

static struct platform_device pcmtst_pdev =;

static struct platform_driver pcmtst_pdrv =;

static ssize_t pattern_write(struct file *file, const char __user *u_buff, size_t len, loff_t *off)
{}

static ssize_t pattern_read(struct file *file, char __user *u_buff, size_t len, loff_t *off)
{}

static const struct file_operations fill_pattern_fops =;

static int setup_patt_bufs(void)
{}

static const char * const pattern_files[] =;
static int init_debug_files(int buf_count)
{}

static void free_pattern_buffers(void)
{}

static void clear_debug_files(void)
{}

static int __init mod_init(void)
{}

static void __exit mod_exit(void)
{}

MODULE_DESCRIPTION();
MODULE_LICENSE();
MODULE_AUTHOR();
module_init();
module_exit(mod_exit);