All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: Nordschamane <nordschamane1@proton.me>, linux-sound@vger.kernel.org
Subject: Re: [PATCH RFC] ALSA: pci: add analog support for ESI MAYA44e
Date: Wed, 11 Nov 2026 07:32:09 +0000	[thread overview]
Message-ID: <sashiko-outbox-208525@kernel.org> (raw)
In-Reply-To: <20261111-codex-alsa-maya44e-v1-1-df60af443e93@proton.me>

Thank you for your contribution! Sashiko AI review found 6 potential
issues to consider:
- [High] ALSA: maya44e: pseudonymous Signed-off-by trailer and
  MODULE_AUTHOR
- [High] ALSA: maya44e: unrestored D0 power state in maya_resume() error
  path
- [High] ALSA: maya44e: NULL runtime->dma_area dereference in maya_ack()
  after hw_free
- [Medium] ALSA: maya44e: missing joint-duplex rate constraint in
  maya_open()
- [Medium] ALSA: maya44e: skipped PGA updates at zero volume in
  maya_input_levels()
- [Medium] ALSA: maya44e: unlocked last_appl updates in maya_pcm_ioctl()
  and maya_prepare()
--

commit 06d75b60c918848008af6c5fc9d3ea134cf8c6cd
Author: Nordschamane <nordschamane1@proton.me>

ALSA: pci: add analog support for ESI MAYA44e

This commit adds an ALSA PCI driver for the ESI MAYA44e PCIe audio
interface supporting four-channel analog playback and capture, mixer
controls, and power management.

> diff --git a/sound/pci/maya44e.c b/sound/pci/maya44e.c
> new file mode 100644
> index 0000000000000..d30f25742df48
> --- /dev/null
> +++ b/sound/pci/maya44e.c

[ ... ]

> +static const struct snd_pcm_hardware maya_pcm_hardware = {
> +	.info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
> +		SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
> +		SNDRV_PCM_INFO_SYNC_APPLPTR | SNDRV_PCM_INFO_NO_REWINDS,
> +	.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_3LE |
> +		SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
> +	.rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_24000 |
> +		SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000,
> +	.rate_min = 8000, .rate_max = 96000,
> +	.channels_min = 4, .channels_max = 4,
> +	.buffer_bytes_max = MAYA_PCM_BYTES_MAX,
> +	.period_bytes_min = MAYA_MIN_PERIOD * 8,
> +	.period_bytes_max = MAYA_MAX_PERIOD * 16,
> +	.periods_min = 2, .periods_max = 2,
> +};
> +
> +/* Full duplex shares one clock, but each direction may use its own period.
> + * Reserve at hw_params, not just while running, to protect prepared peers.
> + */
> +static int maya_hw_params(struct snd_pcm_substream *s, struct snd_pcm_hw_params *p)
> +{
> +	struct maya *m = snd_pcm_substream_chip(s);
> +	unsigned int direction = s->stream;
> +	int err = 0;
> +
> +	mutex_lock(&m->codec_lock);
> +	if (m->reserved_rate[1 - direction] &&
> +	    m->reserved_rate[1 - direction] != params_rate(p))
> +		err = -EBUSY;
> +	else
> +		m->reserved_rate[direction] = params_rate(p);
> +	mutex_unlock(&m->codec_lock);
> +	return err;
> +}

[Severity: Medium]
Should maya_pcm_hardware include SNDRV_PCM_INFO_JOINT_DUPLEX and constrain
SNDRV_PCM_HW_PARAM_RATE when the opposite stream has already reserved a
rate?

During snd_pcm_hw_params(), snd_pcm_hw_params_choose() picks the minimum
rate in the refined interval before calling maya_hw_params().  When one
stream has reserved a rate above the minimum (such as 48000 Hz) and a
second stream passes a rate range containing 48000 Hz, ALSA selects 8000
Hz or 32000 Hz and maya_hw_params() rejects it with -EBUSY.  In addition,
because playback allows 8000..24000 Hz while capture starts at 32000 Hz,
reserving a sub-32 kHz playback rate prevents capture from configuring
hw_params at all.

[ ... ]

> +static int maya_pcm_ioctl(struct snd_pcm_substream *s, unsigned int cmd, void *arg)
> +{
> +	struct maya *m = snd_pcm_substream_chip(s);
> +	int err = snd_pcm_lib_ioctl(s, cmd, arg);
> +
> +	if (!err && cmd == SNDRV_PCM_IOCTL1_RESET)
> +		m->last_appl = s->runtime->status->hw_ptr;
> +	return err;
> +}
> +
> +static int maya_prepare(struct snd_pcm_substream *s)
> +{

[ ... ]

> +	memset(m->ring[0], 0, m->buffer_frames * 8);
> +	memset(m->ring[1], 0, m->buffer_frames * 8);
> +	memset(s->runtime->dma_area, 0,
> +	       s->runtime->buffer_size * maya_sample_bytes(s->runtime->format) * 4);
> +	m->last_appl = 0;
> +	dma_wmb();
> +	return 0;
> +}

[Severity: Medium]
Can updating m->last_appl without holding the PCM stream lock desynchronize
it from runtime->control->appl_ptr?

In snd_pcm_reset(), snd_pcm_do_reset() -> snd_pcm_lib_ioctl_reset() drops
the PCM stream lock before maya_pcm_ioctl() reads runtime->status->hw_ptr,
and snd_pcm_post_reset() re-acquires the lock afterward to copy hw_ptr into
runtime->control->appl_ptr:

CPU0                                  CPU1
snd_pcm_reset()
  snd_pcm_do_reset()
    maya_pcm_ioctl()
      snd_pcm_lib_ioctl_reset()
      m->last_appl = ...->hw_ptr;
                                      maya_irq()
                                        snd_pcm_period_elapsed()
                                          /* advances hw_ptr */
  snd_pcm_post_reset()
    appl_ptr = ...->hw_ptr;           <-- appl_ptr != m->last_appl

When a period interrupt lands in that window on a running playback stream,
m->last_appl lags behind appl_ptr by a period, causing a subsequent write
of more than one period in maya_ack() to compute count > r->buffer_size and
fail with -EINVAL.  Similarly, snd_pcm_sync_ptr() calls maya_ack() under
only pcm_stream_lock_irq() without holding buffer_mutex, so unlocked writes
to m->last_appl in maya_prepare() and maya_pcm_ioctl() race with concurrent
SNDRV_PCM_IOCTL_SYNC_PTR calls.

[ ... ]

> +static int maya_ack(struct snd_pcm_substream *s)
> +{
> +	struct maya *m = snd_pcm_substream_chip(s);
> +	struct snd_pcm_runtime *r = s->runtime;
> +	snd_pcm_uframes_t appl = READ_ONCE(r->control->appl_ptr);
> +	snd_pcm_sframes_t count = appl - m->last_appl;
> +	snd_pcm_uframes_t frame;
> +	unsigned int sample_bytes = maya_sample_bytes(r->format);
> +	__le32 *out12 = m->ring[0], *out34 = m->ring[1];
> +
> +	if (READ_ONCE(m->fault) || READ_ONCE(m->removing) || READ_ONCE(m->suspended))
> +		return -EIO;
> +	if (!sample_bytes)
> +		return -EINVAL;
> +	if (count < 0)
> +		count += r->boundary;
> +	if (count < 0 || count > (snd_pcm_sframes_t)r->buffer_size) {
> +		dev_err_ratelimited(&m->pci->dev,
> +				    "invalid appl_ptr delta=%ld old=%lu new=%lu boundary=%lu\n",
> +				    count, m->last_appl, appl, r->boundary);
> +		return -EINVAL;
> +	}
> +	for (frame = 0; frame < (snd_pcm_uframes_t)count; ++frame) {
> +		unsigned int slot = (m->last_appl + frame) % r->buffer_size;
> +		const u8 *source = r->dma_area + slot * sample_bytes * 4;
> +
> +		out12[slot * 2] = cpu_to_le32(maya_unpack_sample(source, r->format));

[Severity: High]
Can maya_ack() dereference a NULL or concurrently freed r->dma_area if
SNDRV_PCM_IOCTL_SYNC_PTR is called after SNDRV_PCM_IOCTL_HW_FREE?

When SNDRV_PCM_IOCTL_HW_FREE runs after SNDRV_PCM_IOCTL_HW_PARAMS,
do_hw_free() -> snd_pcm_lib_free_pages() frees the managed vmalloc buffer
and sets r->dma_area to NULL under r->buffer_mutex, while leaving
r->format, r->buffer_size and r->boundary populated.  Because
snd_pcm_sync_ptr() only takes pcm_stream_lock_irq() and calls
pcm_lib_apply_appl_ptr() -> maya_ack() without checking r->state or
r->dma_area:

sound/core/pcm_native.c:snd_pcm_sync_ptr() {
    ...
	scoped_guard(pcm_stream_lock_irq, substream) {
		if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL)) {
			err = pcm_lib_apply_appl_ptr(substream, scontrol.appl_ptr);
    ...
}

calling SNDRV_PCM_IOCTL_SYNC_PTR with flags = 0 and a non-zero appl_ptr
passes the bounds checks in maya_ack() and dereferences NULL r->dma_area in
maya_unpack_sample() (or races with snd_pcm_lib_free_pages() freeing the
buffer).

[ ... ]

> +static int maya_input_levels(struct maya *m, unsigned int pair)
> +{
> +	unsigned int ch;
> +	int err;
> +
> +	for (ch = 0; ch < 2; ++ch) {
> +		unsigned int volume = m->input_volume[pair][ch];
> +		unsigned int raw = 62 + volume + m->input_gain[pair];
> +
> +		/* -infinity is a digital gate in maya_capture_period. Keep the
> +		 * last PGA setting there: code 0 causes a bias/settling transient
> +		 * on this board when returning to normal gain.
> +		 */
> +		if (!volume)
> +			continue;
> +		err = maya_codec_write(m, pair, 14 + ch, 0x100 | raw);
> +		if (err)
> +			return err;
> +	}
> +	return 0;
> +}

[Severity: Medium]
Does skipping the WM8776 R14/R15 write when volume is 0 leave the analog
PGA unmuted on the hardware direct-monitoring path and after resume?

Input 1-2/3-4 Capture Volume advertises 0 as mute in input_volume_db, and
maya_capture_period() gates captured samples in software.  However, WM8776
R14/R15 also feeds the analog monitor bypass enabled by Input 1-2/3-4
Monitor Playback Switch, so setting volume to 0 leaves the monitored input
audible at its previous gain and ignores Input Capture Gain updates made
while volume is 0.  Furthermore, on S3 resume maya_codec_init() resets R14
and R15 to 0x1cf (0 dB), and maya_restore_after_sleep() ->
maya_input_levels() then skips any channel whose cached volume is 0.

[ ... ]

> +static int maya_suspend(struct device *dev)
> +{
> +	struct snd_card *card = dev_get_drvdata(dev);
> +	struct maya *m = card->private_data;
> +	unsigned long flags;
> +
> +	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
> +	/* ALSA stream locks must be acquired before our register/codec locks. */
> +	snd_pcm_suspend_all(m->pcm);
> +	mutex_lock(&m->codec_lock);
> +	spin_lock_irqsave(&m->lock, flags);
> +	m->suspend_rate = m->rate;
> +	if (!m->suspended) {
> +		mw8(m, 0x481, mr8(m, 0x481) & ~0x40);
> +		maya_quiesce(m);
> +		m->suspended = true;
> +	}
> +	spin_unlock_irqrestore(&m->lock, flags);
> +	synchronize_irq(m->pci->irq);
> +	mutex_unlock(&m->codec_lock);
> +	/* The gate prevents new work; complete any pending peer notification. */
> +	flush_work(&m->fault_work);
> +	return 0;
> +}
> +
> +static int maya_resume(struct device *dev)
> +{
> +	struct snd_card *card = dev_get_drvdata(dev);
> +	struct maya *m = card->private_data;
> +	unsigned long flags;
> +	int err;
> +
> +	mutex_lock(&m->codec_lock);
> +	if (!m->suspended) {
> +		mutex_unlock(&m->codec_lock);
> +		return 0;
> +	}
> +	if (m->fault || m->removing) {
> +		err = -EIO;
> +		goto fail;
> +	}
> +	pci_intx(m->pci, 0);
> +	pci_clear_master(m->pci);
> +	err = maya_restore_after_sleep(m);
> +	if (err)
> +		goto fail;
> +	spin_lock_irqsave(&m->lock, flags);
> +	m->irq_window = jiffies;
> +	m->irq_window_handled = 0;
> +
> +	m->suspended = false;
> +	pci_set_master(m->pci);
> +	pci_intx(m->pci, 1);
> +	spin_unlock_irqrestore(&m->lock, flags);
> +	mutex_unlock(&m->codec_lock);
> +	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
> +	return 0;
> +fail:
> +	spin_lock_irqsave(&m->lock, flags);
> +
> +	maya_fatal(m);
> +	spin_unlock_irqrestore(&m->lock, flags);
> +	mutex_unlock(&m->codec_lock);
> +	dev_err(dev, "resume failed: %d; DMA and interrupts disabled\n", err);
> +	return err;
> +}

[Severity: High]
Does the fail path in maya_resume() leave the card stuck in
SNDRV_CTL_POWER_D3hot with m->suspended still set?

If m->fault is set before suspend or maya_restore_after_sleep() fails,
maya_resume() returns without calling snd_power_change_state(card,
SNDRV_CTL_POWER_D0) or clearing m->suspended.  Any process that later
closes or accesses a PCM substream or mixer control sleeps forever in
TASK_UNINTERRUPTIBLE inside snd_power_ref_and_wait() / snd_power_wait().
Keeping m->suspended true also causes maya_fatal() -> maya_quiesce() and
maya_free() to skip MMIO register shutdown and fault_work scheduling even
though the PCI device is in D0, and a subsequent maya_suspend() will
overwrite m->suspend_rate with 0 if maya_restore_after_sleep() failed after
clearing m->rate.

[ ... ]

> +MODULE_AUTHOR("MAYA44e Linux reverse-engineering project");
> +MODULE_DESCRIPTION("ESI MAYA44e analog audio driver");
> +MODULE_LICENSE("GPL");

[Severity: High]
Under Documentation/process/submitting-patches.rst, Signed-off-by and
author attribution must use a known real full name rather than a single-word
handle ("Nordschamane <nordschamane1@proton.me>") or a generic project
string in MODULE_AUTHOR.  Also, the transient notes in the commit message
about the tree base and RFC feedback requests belong below the "---"
separator line so they are not recorded in the permanent git history.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261111-codex-alsa-maya44e-v1-1-df60af443e93@proton.me?part=1

  reply	other threads:[~2026-11-11  7:32 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-11  6:58 [PATCH RFC] ALSA: pci: add analog support for ESI MAYA44e Nordschamane via B4 Relay
2026-11-11  6:58 ` Nordschamane
2026-11-11  7:32 ` sashiko-bot [this message]
2026-11-11  9:25   ` Nordschamane

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-208525@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=linux-sound@vger.kernel.org \
    --cc=nordschamane1@proton.me \
    --cc=sashiko-reviews@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.