// SPDX-License-Identifier: GPL-2.0 /* * tascam_poc.c - synthetic ALSA PCM lifetime reproducer for the TASCAM * US-144MKII manual-review cases C1 and C2. * * This module deliberately reproduces the *lifetime patterns* of the driver, * not the USB device itself: * pcm1 : ALSA PCM .close() drops the runtime while an asynchronous * "completion" still holds a runtime pointer (C1). * pcm2 : a queued work item calls snd_pcm_stop() after .close()/detach * (C2). * * The ALSA card is a synthetic platform device; no TASCAM hardware or USB * traffic is involved. Build and run instructions are in README.txt. */ #include #include #include #include #include #include #include #include #include #include #include #include #define DRV "sndusb_race" #define COOKIE 0x5a5a1234deadbeefULL static char *mode = "pcm1"; module_param(mode, charp, 0444); static bool safe; module_param(safe, bool, 0444); MODULE_PARM_DESC(mode, "pcm1 or pcm2"); MODULE_PARM_DESC(safe, "take the safe cleanup path (negative control)"); /* ------------------------------------------------------------------ */ /* V1/V2 synthetic ALSA PCM */ /* ------------------------------------------------------------------ */ struct race_pcm { struct snd_pcm_substream *substream; struct snd_pcm_runtime *runtime; struct work_struct work; atomic_t active; bool c2; u64 cookie; }; static struct snd_card *race_card; static struct platform_device *race_pdev; static struct snd_pcm *race_pcm_dev; static void race_pcm_work(struct work_struct *work) { struct race_pcm *m = container_of(work, struct race_pcm, work); struct snd_pcm_runtime *rt = m->runtime; struct snd_pcm_substream *ss = m->substream; pr_info(DRV ": work start mode=%s rt=%px ss=%px cookie=%#llx\n", m->c2 ? "c2" : "c1", rt, ss, m->cookie); if (!m->c2 && !rt) return; if (m->c2 && !ss) return; /* Give the closing thread time to run .close() and detach/free. */ msleep(120); if (!m->c2) { /* * C1: the completion cached runtime before close; after * snd_pcm_detach_substream() this is freed memory. */ pr_info(DRV ": c1 use rt=%px dma_area=%px\n", rt, READ_ONCE(rt->dma_area)); barrier(); (void)READ_ONCE(rt->dma_area); } else { /* * C2: the work cached the substream, but detach made * ss->runtime NULL; snd_pcm_stop() dereferences it. */ pr_info(DRV ": c2 call snd_pcm_stop ss=%px runtime=%px\n", ss, READ_ONCE(ss->runtime)); snd_pcm_stop(ss, SNDRV_PCM_STATE_XRUN); } pr_info(DRV ": work end\n"); } static int race_pcm_open(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct race_pcm *m; m = kzalloc(sizeof(*m), GFP_KERNEL); if (!m) return -ENOMEM; m->substream = substream; m->runtime = runtime; m->cookie = COOKIE; m->c2 = !strcmp(mode, "pcm2"); INIT_WORK(&m->work, race_pcm_work); atomic_set(&m->active, 0); runtime->private_data = m; runtime->hw.info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP_VALID; runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; runtime->hw.rates = SNDRV_PCM_RATE_44100; runtime->hw.rate_min = 44100; runtime->hw.rate_max = 44100; runtime->hw.channels_min = 2; runtime->hw.channels_max = 2; runtime->hw.buffer_bytes_max = 64 * 1024; runtime->hw.period_bytes_min = 1024; runtime->hw.period_bytes_max = 4096; runtime->hw.periods_min = 2; runtime->hw.periods_max = 8; pr_info(DRV ": open sub=%px rt=%px mode=%s\n", substream, runtime, mode); return 0; } static int race_pcm_close(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct race_pcm *m = runtime ? runtime->private_data : NULL; pr_info(DRV ": close sub=%px rt=%px m=%px safe=%d\n", substream, runtime, m, safe); if (m && safe) cancel_work_sync(&m->work); if (m) { m->substream = NULL; m->runtime = NULL; } return 0; } static int race_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { return 0; } static int race_pcm_prepare(struct snd_pcm_substream *substream) { return 0; } static int race_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { struct snd_pcm_runtime *runtime = substream->runtime; struct race_pcm *m = runtime ? runtime->private_data : NULL; if (!m) return 0; switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: atomic_set(&m->active, 1); pr_info(DRV ": trigger start, queue work\n"); schedule_work(&m->work); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: atomic_set(&m->active, 0); break; } return 0; } static snd_pcm_uframes_t race_pcm_pointer(struct snd_pcm_substream *substream) { return 0; } static const struct snd_pcm_ops race_pcm_ops = { .open = race_pcm_open, .close = race_pcm_close, .hw_params = race_pcm_hw_params, .prepare = race_pcm_prepare, .trigger = race_pcm_trigger, .pointer = race_pcm_pointer, }; static int sndusb_pcm_init(void) { int ret; race_pdev = platform_device_register_simple("sndusb_race", -1, NULL, 0); if (IS_ERR(race_pdev)) return PTR_ERR(race_pdev); ret = snd_card_new(&race_pdev->dev, 0, "SndUsbRace", THIS_MODULE, 0, &race_card); if (ret < 0) { pr_err(DRV ": snd_card_new failed: %d\n", ret); return ret; } ret = snd_pcm_new(race_card, "SndUsbRace", 0, 0, 1, &race_pcm_dev); if (ret < 0) { pr_err(DRV ": snd_pcm_new failed: %d\n", ret); snd_card_free(race_card); return ret; } snd_pcm_set_ops(race_pcm_dev, SNDRV_PCM_STREAM_CAPTURE, &race_pcm_ops); snd_pcm_lib_preallocate_pages_for_all(race_pcm_dev, SNDRV_DMA_TYPE_CONTINUOUS, NULL, 64 * 1024, 64 * 1024); ret = snd_card_register(race_card); if (ret < 0) { pr_err(DRV ": snd_card_register failed: %d\n", ret); snd_card_free(race_card); return ret; } pr_info(DRV ": card registered mode=%s safe=%d\n", mode, safe); return 0; } static int __init sndusb_init(void) { pr_info(DRV ": init mode=%s safe=%d\n", mode, safe); return sndusb_pcm_init(); } static void __exit sndusb_exit(void) { pr_info(DRV ": exit\n"); if (race_card) snd_card_free(race_card); if (race_pdev) platform_device_unregister(race_pdev); } module_init(sndusb_init); module_exit(sndusb_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("TASCAM US-144MKII C1/C2 synthetic lifetime reproducer");