#define _GNU_SOURCE // SPDX-License-Identifier: GPL-2.0 /* * pcmtest.c - guest-side exerciser for sndpci_race.ko. * * Usage: * pcmtest close [iters] - open/start/close one playback substream (V4) * pcmtest link [iters] - link two playback substreams, close B while * the A timer callback walks the group (V5) */ #include #include #include #include #include #include #include #include #include #include #define DEV "/dev/snd/pcmC0D0c" static int set_hw_params(int fd) { struct snd_pcm_hw_params p; int i, ret; memset(&p, 0, sizeof(p)); for (i = 0; i <= SNDRV_PCM_HW_PARAM_LAST_MASK - SNDRV_PCM_HW_PARAM_FIRST_MASK; i++) memset(&p.masks[i], 0xff, sizeof(p.masks[i])); for (i = 0; i <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; i++) { p.intervals[i].min = 0; p.intervals[i].max = UINT_MAX; p.intervals[i].integer = 0; } p.rmask = ~0U; ret = ioctl(fd, SNDRV_PCM_IOCTL_HW_REFINE, &p); printf("HW_REFINE ret=%d errno=%d\n", ret, errno); if (ret < 0) return -1; printf("refined: rate=%u ch=%u fmt=%u ps=%u periods=%u bs=%u\n", p.intervals[SNDRV_PCM_HW_PARAM_RATE - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL].min, p.intervals[SNDRV_PCM_HW_PARAM_CHANNELS - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL].min, __builtin_ctz(p.masks[SNDRV_PCM_HW_PARAM_FORMAT].bits[0]), p.intervals[SNDRV_PCM_HW_PARAM_PERIOD_SIZE - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL].min, p.intervals[SNDRV_PCM_HW_PARAM_PERIODS - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL].min, p.intervals[SNDRV_PCM_HW_PARAM_BUFFER_SIZE - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL].min); ret = ioctl(fd, SNDRV_PCM_IOCTL_HW_PARAMS, &p); if (ret < 0) { fprintf(stderr, "HW_PARAMS: %s\n", strerror(errno)); return -1; } return 0; } static int open_stream(void) { int fd = open(DEV, O_RDWR | O_NONBLOCK); if (fd < 0) fprintf(stderr, "open %s: %s\n", DEV, strerror(errno)); return fd; } static int prepare_stream(int fd) { if (set_hw_params(fd) < 0) return -1; if (ioctl(fd, SNDRV_PCM_IOCTL_PREPARE, 0) < 0) { fprintf(stderr, "PREPARE: %s\n", strerror(errno)); return -1; } return 0; } static int start_stream(int fd) { if (ioctl(fd, SNDRV_PCM_IOCTL_START, 0) < 0) { fprintf(stderr, "START: %s\n", strerror(errno)); return -1; } return 0; } static int one_close(void) { int fd = open_stream(); if (fd < 0) return -1; if (prepare_stream(fd) < 0 || start_stream(fd) < 0) { close(fd); return -1; } usleep(10000); close(fd); return 0; } static int one_link(void) { int a = open_stream(); int b = open_stream(); if (a < 0 || b < 0) return -1; if (prepare_stream(a) < 0 || prepare_stream(b) < 0) goto out; if (ioctl(a, SNDRV_PCM_IOCTL_LINK, b) < 0) { fprintf(stderr, "LINK: %s\n", strerror(errno)); goto out; } if (start_stream(a) < 0) goto out; /* Let the A timer callback start walking the linked group. */ usleep(1500); /* Close B while A's callback may be in the group loop. */ close(b); b = -1; usleep(1500); out: if (b >= 0) close(b); if (a >= 0) close(a); return 0; } int main(int argc, char **argv) { cpu_set_t set; CPU_ZERO(&set); CPU_SET(1, &set); sched_setaffinity(0, sizeof(set), &set); int i, n = 200; if (argc < 2) { fprintf(stderr, "usage: %s close|link [iters]\n", argv[0]); return 2; } if (argc > 2) n = atoi(argv[2]); printf("pcmtest: mode=%s iters=%d\n", argv[1], n); for (i = 0; i < n; i++) { if (!strcmp(argv[1], "link")) { if (one_link() < 0) break; } else { if (one_close() < 0) break; } } printf("pcmtest: done i=%d\n", i); return 0; }