linux-brain/drivers/staging/vc04_services/bcm2835-audio/bcm2835-pcm.c

374 lines
10 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0
/* Copyright 2011 Broadcom Corporation. All rights reserved. */
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <sound/asoundef.h>
#include "bcm2835.h"
/* hardware definition */
static const struct snd_pcm_hardware snd_bcm2835_playback_hw = {
.info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_SYNC_APPLPTR),
.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
.rate_min = 8000,
.rate_max = 48000,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = 128 * 1024,
.period_bytes_min = 1 * 1024,
.period_bytes_max = 128 * 1024,
.periods_min = 1,
.periods_max = 128,
};
static const struct snd_pcm_hardware snd_bcm2835_playback_spdif_hw = {
.info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_SYNC_APPLPTR),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_48000,
.rate_min = 44100,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 128 * 1024,
.period_bytes_min = 1 * 1024,
.period_bytes_max = 128 * 1024,
.periods_min = 1,
.periods_max = 128,
};
static void snd_bcm2835_playback_free(struct snd_pcm_runtime *runtime)
{
kfree(runtime->private_data);
}
void bcm2835_playback_fifo(struct bcm2835_alsa_stream *alsa_stream,
unsigned int bytes)
{
struct snd_pcm_substream *substream = alsa_stream->substream;
unsigned int pos;
if (!alsa_stream->period_size)
return;
if (bytes >= alsa_stream->buffer_size) {
snd_pcm_stream_lock(substream);
snd_pcm_stop(substream,
alsa_stream->draining ?
SNDRV_PCM_STATE_SETUP :
SNDRV_PCM_STATE_XRUN);
snd_pcm_stream_unlock(substream);
return;
}
pos = atomic_read(&alsa_stream->pos);
pos += bytes;
pos %= alsa_stream->buffer_size;
atomic_set(&alsa_stream->pos, pos);
alsa_stream->period_offset += bytes;
alsa_stream->interpolate_start = ktime_get();
if (alsa_stream->period_offset >= alsa_stream->period_size) {
alsa_stream->period_offset %= alsa_stream->period_size;
snd_pcm_period_elapsed(substream);
}
}
/* open callback */
static int snd_bcm2835_playback_open_generic(
struct snd_pcm_substream *substream, int spdif)
{
struct bcm2835_chip *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct bcm2835_alsa_stream *alsa_stream;
int idx;
int err;
mutex_lock(&chip->audio_mutex);
idx = substream->number;
if (spdif && chip->opened) {
err = -EBUSY;
goto out;
} else if (!spdif && (chip->opened & (1 << idx))) {
err = -EBUSY;
goto out;
}
if (idx >= MAX_SUBSTREAMS) {
dev_err(chip->dev,
"substream(%d) device doesn't exist max(%d) substreams allowed\n",
idx, MAX_SUBSTREAMS);
err = -ENODEV;
goto out;
}
alsa_stream = kzalloc(sizeof(*alsa_stream), GFP_KERNEL);
if (!alsa_stream) {
err = -ENOMEM;
goto out;
}
/* Initialise alsa_stream */
alsa_stream->chip = chip;
alsa_stream->substream = substream;
alsa_stream->idx = idx;
err = bcm2835_audio_open(alsa_stream);
if (err) {
kfree(alsa_stream);
goto out;
}
runtime->private_data = alsa_stream;
runtime->private_free = snd_bcm2835_playback_free;
if (spdif) {
runtime->hw = snd_bcm2835_playback_spdif_hw;
} else {
/* clear spdif status, as we are not in spdif mode */
chip->spdif_status = 0;
runtime->hw = snd_bcm2835_playback_hw;
}
/* minimum 16 bytes alignment (for vchiq bulk transfers) */
snd_pcm_hw_constraint_step(runtime,
0,
SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
16);
/* position update is in 10ms order */
snd_pcm_hw_constraint_minmax(runtime,
SNDRV_PCM_HW_PARAM_PERIOD_TIME,
10 * 1000, UINT_MAX);
chip->alsa_stream[idx] = alsa_stream;
chip->opened |= (1 << idx);
out:
mutex_unlock(&chip->audio_mutex);
return err;
}
static int snd_bcm2835_playback_open(struct snd_pcm_substream *substream)
{
return snd_bcm2835_playback_open_generic(substream, 0);
}
static int snd_bcm2835_playback_spdif_open(struct snd_pcm_substream *substream)
{
return snd_bcm2835_playback_open_generic(substream, 1);
}
static int snd_bcm2835_playback_close(struct snd_pcm_substream *substream)
{
struct bcm2835_alsa_stream *alsa_stream;
struct snd_pcm_runtime *runtime;
struct bcm2835_chip *chip;
chip = snd_pcm_substream_chip(substream);
mutex_lock(&chip->audio_mutex);
runtime = substream->runtime;
alsa_stream = runtime->private_data;
alsa_stream->period_size = 0;
alsa_stream->buffer_size = 0;
bcm2835_audio_close(alsa_stream);
alsa_stream->chip->alsa_stream[alsa_stream->idx] = NULL;
/*
* Do not free up alsa_stream here, it will be freed up by
* runtime->private_free callback we registered in *_open above
*/
chip->opened &= ~(1 << substream->number);
mutex_unlock(&chip->audio_mutex);
return 0;
}
static int snd_bcm2835_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
}
static int snd_bcm2835_pcm_hw_free(struct snd_pcm_substream *substream)
{
return snd_pcm_lib_free_pages(substream);
}
static int snd_bcm2835_pcm_prepare(struct snd_pcm_substream *substream)
{
struct bcm2835_chip *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct bcm2835_alsa_stream *alsa_stream = runtime->private_data;
int channels;
int err;
/* notify the vchiq that it should enter spdif passthrough mode by
* setting channels=0 (see
* https://github.com/raspberrypi/linux/issues/528)
*/
if (chip->spdif_status & IEC958_AES0_NONAUDIO)
channels = 0;
else
channels = runtime->channels;
err = bcm2835_audio_set_params(alsa_stream, channels,
runtime->rate,
snd_pcm_format_width(runtime->format));
if (err < 0)
return err;
memset(&alsa_stream->pcm_indirect, 0, sizeof(alsa_stream->pcm_indirect));
alsa_stream->pcm_indirect.hw_buffer_size =
alsa_stream->pcm_indirect.sw_buffer_size =
snd_pcm_lib_buffer_bytes(substream);
alsa_stream->buffer_size = snd_pcm_lib_buffer_bytes(substream);
alsa_stream->period_size = snd_pcm_lib_period_bytes(substream);
atomic_set(&alsa_stream->pos, 0);
alsa_stream->period_offset = 0;
alsa_stream->draining = false;
alsa_stream->interpolate_start = ktime_get();
return 0;
}
static void snd_bcm2835_pcm_transfer(struct snd_pcm_substream *substream,
struct snd_pcm_indirect *rec, size_t bytes)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct bcm2835_alsa_stream *alsa_stream = runtime->private_data;
void *src = (void *) (substream->runtime->dma_area + rec->sw_data);
bcm2835_audio_write(alsa_stream, bytes, src);
}
static int snd_bcm2835_pcm_ack(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct bcm2835_alsa_stream *alsa_stream = runtime->private_data;
struct snd_pcm_indirect *pcm_indirect = &alsa_stream->pcm_indirect;
return snd_pcm_indirect_playback_transfer(substream, pcm_indirect,
snd_bcm2835_pcm_transfer);
}
/* trigger callback */
static int snd_bcm2835_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct bcm2835_alsa_stream *alsa_stream = runtime->private_data;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
return bcm2835_audio_start(alsa_stream);
case SNDRV_PCM_TRIGGER_DRAIN:
alsa_stream->draining = true;
return bcm2835_audio_drain(alsa_stream);
case SNDRV_PCM_TRIGGER_STOP:
return bcm2835_audio_stop(alsa_stream);
default:
return -EINVAL;
}
}
/* pointer callback */
static snd_pcm_uframes_t
snd_bcm2835_pcm_pointer(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct bcm2835_alsa_stream *alsa_stream = runtime->private_data;
ktime_t now = ktime_get();
/* Give userspace better delay reporting by interpolating between GPU
* notifications, assuming audio speed is close enough to the clock
* used for ktime
*/
if ((ktime_to_ns(alsa_stream->interpolate_start)) &&
(ktime_compare(alsa_stream->interpolate_start, now) < 0)) {
u64 interval =
(ktime_to_ns(ktime_sub(now,
alsa_stream->interpolate_start)));
u64 frames_output_in_interval =
div_u64((interval * runtime->rate), 1000000000);
snd_pcm_sframes_t frames_output_in_interval_sized =
-frames_output_in_interval;
runtime->delay = frames_output_in_interval_sized;
}
return snd_pcm_indirect_playback_pointer(substream,
&alsa_stream->pcm_indirect,
atomic_read(&alsa_stream->pos));
}
/* operators */
static const struct snd_pcm_ops snd_bcm2835_playback_ops = {
.open = snd_bcm2835_playback_open,
.close = snd_bcm2835_playback_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_bcm2835_pcm_hw_params,
.hw_free = snd_bcm2835_pcm_hw_free,
.prepare = snd_bcm2835_pcm_prepare,
.trigger = snd_bcm2835_pcm_trigger,
.pointer = snd_bcm2835_pcm_pointer,
.ack = snd_bcm2835_pcm_ack,
};
static const struct snd_pcm_ops snd_bcm2835_playback_spdif_ops = {
.open = snd_bcm2835_playback_spdif_open,
.close = snd_bcm2835_playback_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_bcm2835_pcm_hw_params,
.hw_free = snd_bcm2835_pcm_hw_free,
.prepare = snd_bcm2835_pcm_prepare,
.trigger = snd_bcm2835_pcm_trigger,
.pointer = snd_bcm2835_pcm_pointer,
.ack = snd_bcm2835_pcm_ack,
};
/* create a pcm device */
int snd_bcm2835_new_pcm(struct bcm2835_chip *chip, const char *name,
int idx, enum snd_bcm2835_route route,
u32 numchannels, bool spdif)
{
struct snd_pcm *pcm;
int err;
err = snd_pcm_new(chip->card, name, idx, numchannels, 0, &pcm);
if (err)
return err;
pcm->private_data = chip;
pcm->nonatomic = true;
staging: bcm2835-audio: Replace unsafe strcpy() with strscpy() [ Upstream commit 4964a4300660d27907ceb655f219ac47e5941534 ] Replace strcpy() with strscpy() in bcm2835-audio/bcm2835.c to prevent the following when loading snd-bcm2835: [ 58.480634] ------------[ cut here ]------------ [ 58.485321] kernel BUG at lib/string.c:1149! [ 58.489650] Internal error: Oops - BUG: 0 [#1] PREEMPT SMP [ 58.495214] Modules linked in: snd_bcm2835(COE+) snd_pcm snd_timer snd dm_multipath scsi_dh_rdac scsi_dh_emc scsi_dh_alua btsdio bluetooth ecdh_generic ecc bcm2835_v4l2(CE) bcm2835_codec(CE) brcmfmac bcm2835_isp(CE) bcm2835_mmal_vchiq(CE) brcmutil cfg80211 v4l2_mem2mem videobuf2_vmalloc videobuf2_dma_contig videobuf2_memops raspberrypi_hwmon videobuf2_v4l2 videobuf2_common videodev bcm2835_gpiomem mc vc_sm_cma(CE) rpivid_mem uio_pdrv_genirq uio sch_fq_codel drm ip_tables x_tables autofs4 btrfs blake2b_generic raid10 raid456 async_raid6_recov async_memcpy async_pq async_xor async_tx xor xor_neon raid6_pq libcrc32c raid1 raid0 multipath linear dwc2 roles spidev udc_core crct10dif_ce xhci_pci xhci_pci_renesas phy_generic aes_neon_bs aes_neon_blk crypto_simd cryptd [ 58.563787] CPU: 3 PID: 1959 Comm: insmod Tainted: G C OE 5.11.0-1001-raspi #1 [ 58.572172] Hardware name: Raspberry Pi 4 Model B Rev 1.2 (DT) [ 58.578086] pstate: 60400005 (nZCv daif +PAN -UAO -TCO BTYPE=--) [ 58.584178] pc : fortify_panic+0x20/0x24 [ 58.588161] lr : fortify_panic+0x20/0x24 [ 58.592136] sp : ffff800010a83990 [ 58.595491] x29: ffff800010a83990 x28: 0000000000000002 [ 58.600879] x27: ffffb0b07cb72928 x26: 0000000000000000 [ 58.606268] x25: ffff39e884973838 x24: ffffb0b07cb74190 [ 58.611655] x23: ffffb0b07cb72030 x22: 0000000000000000 [ 58.617042] x21: ffff39e884973014 x20: ffff39e88b793010 [ 58.622428] x19: ffffb0b07cb72670 x18: 0000000000000030 [ 58.627814] x17: 0000000000000000 x16: ffffb0b092ce2c1c [ 58.633200] x15: ffff39e88b901500 x14: 0720072007200720 [ 58.638588] x13: 0720072007200720 x12: 0720072007200720 [ 58.643979] x11: ffffb0b0936cbdf0 x10: 00000000fffff000 [ 58.649366] x9 : ffffb0b09220cfa8 x8 : 0000000000000000 [ 58.654752] x7 : ffffb0b093673df0 x6 : ffffb0b09364e000 [ 58.660140] x5 : 0000000000000000 x4 : ffff39e93b7db948 [ 58.665526] x3 : ffff39e93b7ebcf0 x2 : 0000000000000000 [ 58.670913] x1 : 0000000000000000 x0 : 0000000000000022 [ 58.676299] Call trace: [ 58.678775] fortify_panic+0x20/0x24 [ 58.682402] snd_bcm2835_alsa_probe+0x5b8/0x7d8 [snd_bcm2835] [ 58.688247] platform_probe+0x74/0xe4 [ 58.691963] really_probe+0xf0/0x510 [ 58.695585] driver_probe_device+0xe0/0x100 [ 58.699826] device_driver_attach+0xcc/0xd4 [ 58.704068] __driver_attach+0xb0/0x17c [ 58.707956] bus_for_each_dev+0x7c/0xd4 [ 58.711843] driver_attach+0x30/0x40 [ 58.715467] bus_add_driver+0x154/0x250 [ 58.719354] driver_register+0x84/0x140 [ 58.723242] __platform_driver_register+0x34/0x40 [ 58.728013] bcm2835_alsa_driver_init+0x30/0x1000 [snd_bcm2835] [ 58.734024] do_one_initcall+0x54/0x300 [ 58.737914] do_init_module+0x60/0x280 [ 58.741719] load_module+0x680/0x770 [ 58.745344] __do_sys_finit_module+0xbc/0x130 [ 58.749761] __arm64_sys_finit_module+0x2c/0x40 [ 58.754356] el0_svc_common.constprop.0+0x88/0x220 [ 58.759216] do_el0_svc+0x30/0xa0 [ 58.762575] el0_svc+0x28/0x70 [ 58.765669] el0_sync_handler+0x1a4/0x1b0 [ 58.769732] el0_sync+0x178/0x180 [ 58.773095] Code: aa0003e1 91366040 910003fd 97ffee21 (d4210000) [ 58.779275] ---[ end trace 29be5b17497bd898 ]--- [ 58.783955] note: insmod[1959] exited with preempt_count 1 [ 58.791921] ------------[ cut here ]------------ For the sake of it, replace all the other occurences of strcpy() under bcm2835-audio/ as well. Signed-off-by: Juerg Haefliger <juergh@canonical.com> Link: https://lore.kernel.org/r/20210205072502.10907-1-juergh@canonical.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Signed-off-by: Sasha Levin <sashal@kernel.org>
2021-02-05 16:25:02 +09:00
strscpy(pcm->name, name, sizeof(pcm->name));
if (!spdif) {
chip->dest = route;
chip->volume = 0;
chip->mute = CTRL_VOL_UNMUTE;
}
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
spdif ? &snd_bcm2835_playback_spdif_ops :
&snd_bcm2835_playback_ops);
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
chip->card->dev, 128 * 1024, 128 * 1024);
if (spdif)
chip->pcm_spdif = pcm;
else
chip->pcm = pcm;
return 0;
}