cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

pcsp_lib.c (9048B)


      1// SPDX-License-Identifier: GPL-2.0
      2/*
      3 * PC-Speaker driver for Linux
      4 *
      5 * Copyright (C) 1993-1997  Michael Beck
      6 * Copyright (C) 1997-2001  David Woodhouse
      7 * Copyright (C) 2001-2008  Stas Sergeev
      8 */
      9
     10#include <linux/module.h>
     11#include <linux/gfp.h>
     12#include <linux/moduleparam.h>
     13#include <linux/interrupt.h>
     14#include <linux/io.h>
     15#include <sound/pcm.h>
     16#include "pcsp.h"
     17
     18static bool nforce_wa;
     19module_param(nforce_wa, bool, 0444);
     20MODULE_PARM_DESC(nforce_wa, "Apply NForce chipset workaround "
     21		"(expect bad sound)");
     22
     23#define DMIX_WANTS_S16	1
     24
     25/*
     26 * Call snd_pcm_period_elapsed in a work
     27 * This avoids spinlock messes and long-running irq contexts
     28 */
     29static void pcsp_call_pcm_elapsed(struct work_struct *work)
     30{
     31	if (atomic_read(&pcsp_chip.timer_active)) {
     32		struct snd_pcm_substream *substream;
     33		substream = pcsp_chip.playback_substream;
     34		if (substream)
     35			snd_pcm_period_elapsed(substream);
     36	}
     37}
     38
     39static DECLARE_WORK(pcsp_pcm_work, pcsp_call_pcm_elapsed);
     40
     41/* write the port and returns the next expire time in ns;
     42 * called at the trigger-start and in hrtimer callback
     43 */
     44static u64 pcsp_timer_update(struct snd_pcsp *chip)
     45{
     46	unsigned char timer_cnt, val;
     47	u64 ns;
     48	struct snd_pcm_substream *substream;
     49	struct snd_pcm_runtime *runtime;
     50	unsigned long flags;
     51
     52	if (chip->thalf) {
     53		outb(chip->val61, 0x61);
     54		chip->thalf = 0;
     55		return chip->ns_rem;
     56	}
     57
     58	substream = chip->playback_substream;
     59	if (!substream)
     60		return 0;
     61
     62	runtime = substream->runtime;
     63	/* assume it is mono! */
     64	val = runtime->dma_area[chip->playback_ptr + chip->fmt_size - 1];
     65	if (chip->is_signed)
     66		val ^= 0x80;
     67	timer_cnt = val * CUR_DIV() / 256;
     68
     69	if (timer_cnt && chip->enable) {
     70		raw_spin_lock_irqsave(&i8253_lock, flags);
     71		if (!nforce_wa) {
     72			outb_p(chip->val61, 0x61);
     73			outb_p(timer_cnt, 0x42);
     74			outb(chip->val61 ^ 1, 0x61);
     75		} else {
     76			outb(chip->val61 ^ 2, 0x61);
     77			chip->thalf = 1;
     78		}
     79		raw_spin_unlock_irqrestore(&i8253_lock, flags);
     80	}
     81
     82	chip->ns_rem = PCSP_PERIOD_NS();
     83	ns = (chip->thalf ? PCSP_CALC_NS(timer_cnt) : chip->ns_rem);
     84	chip->ns_rem -= ns;
     85	return ns;
     86}
     87
     88static void pcsp_pointer_update(struct snd_pcsp *chip)
     89{
     90	struct snd_pcm_substream *substream;
     91	size_t period_bytes, buffer_bytes;
     92	int periods_elapsed;
     93	unsigned long flags;
     94
     95	/* update the playback position */
     96	substream = chip->playback_substream;
     97	if (!substream)
     98		return;
     99
    100	period_bytes = snd_pcm_lib_period_bytes(substream);
    101	buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
    102
    103	spin_lock_irqsave(&chip->substream_lock, flags);
    104	chip->playback_ptr += PCSP_INDEX_INC() * chip->fmt_size;
    105	periods_elapsed = chip->playback_ptr - chip->period_ptr;
    106	if (periods_elapsed < 0) {
    107#if PCSP_DEBUG
    108		printk(KERN_INFO "PCSP: buffer_bytes mod period_bytes != 0 ? "
    109			"(%zi %zi %zi)\n",
    110			chip->playback_ptr, period_bytes, buffer_bytes);
    111#endif
    112		periods_elapsed += buffer_bytes;
    113	}
    114	periods_elapsed /= period_bytes;
    115	/* wrap the pointer _before_ calling snd_pcm_period_elapsed(),
    116	 * or ALSA will BUG on us. */
    117	chip->playback_ptr %= buffer_bytes;
    118
    119	if (periods_elapsed) {
    120		chip->period_ptr += periods_elapsed * period_bytes;
    121		chip->period_ptr %= buffer_bytes;
    122		queue_work(system_highpri_wq, &pcsp_pcm_work);
    123	}
    124	spin_unlock_irqrestore(&chip->substream_lock, flags);
    125}
    126
    127enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle)
    128{
    129	struct snd_pcsp *chip = container_of(handle, struct snd_pcsp, timer);
    130	int pointer_update;
    131	u64 ns;
    132
    133	if (!atomic_read(&chip->timer_active) || !chip->playback_substream)
    134		return HRTIMER_NORESTART;
    135
    136	pointer_update = !chip->thalf;
    137	ns = pcsp_timer_update(chip);
    138	if (!ns) {
    139		printk(KERN_WARNING "PCSP: unexpected stop\n");
    140		return HRTIMER_NORESTART;
    141	}
    142
    143	if (pointer_update)
    144		pcsp_pointer_update(chip);
    145
    146	hrtimer_forward_now(handle, ns_to_ktime(ns));
    147
    148	return HRTIMER_RESTART;
    149}
    150
    151static int pcsp_start_playing(struct snd_pcsp *chip)
    152{
    153#if PCSP_DEBUG
    154	printk(KERN_INFO "PCSP: start_playing called\n");
    155#endif
    156	if (atomic_read(&chip->timer_active)) {
    157		printk(KERN_ERR "PCSP: Timer already active\n");
    158		return -EIO;
    159	}
    160
    161	raw_spin_lock(&i8253_lock);
    162	chip->val61 = inb(0x61) | 0x03;
    163	outb_p(0x92, 0x43);	/* binary, mode 1, LSB only, ch 2 */
    164	raw_spin_unlock(&i8253_lock);
    165	atomic_set(&chip->timer_active, 1);
    166	chip->thalf = 0;
    167
    168	hrtimer_start(&pcsp_chip.timer, 0, HRTIMER_MODE_REL);
    169	return 0;
    170}
    171
    172static void pcsp_stop_playing(struct snd_pcsp *chip)
    173{
    174#if PCSP_DEBUG
    175	printk(KERN_INFO "PCSP: stop_playing called\n");
    176#endif
    177	if (!atomic_read(&chip->timer_active))
    178		return;
    179
    180	atomic_set(&chip->timer_active, 0);
    181	raw_spin_lock(&i8253_lock);
    182	/* restore the timer */
    183	outb_p(0xb6, 0x43);	/* binary, mode 3, LSB/MSB, ch 2 */
    184	outb(chip->val61 & 0xFC, 0x61);
    185	raw_spin_unlock(&i8253_lock);
    186}
    187
    188/*
    189 * Force to stop and sync the stream
    190 */
    191void pcsp_sync_stop(struct snd_pcsp *chip)
    192{
    193	local_irq_disable();
    194	pcsp_stop_playing(chip);
    195	local_irq_enable();
    196	hrtimer_cancel(&chip->timer);
    197	cancel_work_sync(&pcsp_pcm_work);
    198}
    199
    200static int snd_pcsp_playback_close(struct snd_pcm_substream *substream)
    201{
    202	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
    203#if PCSP_DEBUG
    204	printk(KERN_INFO "PCSP: close called\n");
    205#endif
    206	pcsp_sync_stop(chip);
    207	chip->playback_substream = NULL;
    208	return 0;
    209}
    210
    211static int snd_pcsp_playback_hw_params(struct snd_pcm_substream *substream,
    212				       struct snd_pcm_hw_params *hw_params)
    213{
    214	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
    215	pcsp_sync_stop(chip);
    216	return 0;
    217}
    218
    219static int snd_pcsp_playback_hw_free(struct snd_pcm_substream *substream)
    220{
    221	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
    222#if PCSP_DEBUG
    223	printk(KERN_INFO "PCSP: hw_free called\n");
    224#endif
    225	pcsp_sync_stop(chip);
    226	return 0;
    227}
    228
    229static int snd_pcsp_playback_prepare(struct snd_pcm_substream *substream)
    230{
    231	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
    232	pcsp_sync_stop(chip);
    233	chip->playback_ptr = 0;
    234	chip->period_ptr = 0;
    235	chip->fmt_size =
    236		snd_pcm_format_physical_width(substream->runtime->format) >> 3;
    237	chip->is_signed = snd_pcm_format_signed(substream->runtime->format);
    238#if PCSP_DEBUG
    239	printk(KERN_INFO "PCSP: prepare called, "
    240			"size=%zi psize=%zi f=%zi f1=%i fsize=%i\n",
    241			snd_pcm_lib_buffer_bytes(substream),
    242			snd_pcm_lib_period_bytes(substream),
    243			snd_pcm_lib_buffer_bytes(substream) /
    244			snd_pcm_lib_period_bytes(substream),
    245			substream->runtime->periods,
    246			chip->fmt_size);
    247#endif
    248	return 0;
    249}
    250
    251static int snd_pcsp_trigger(struct snd_pcm_substream *substream, int cmd)
    252{
    253	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
    254#if PCSP_DEBUG
    255	printk(KERN_INFO "PCSP: trigger called\n");
    256#endif
    257	switch (cmd) {
    258	case SNDRV_PCM_TRIGGER_START:
    259	case SNDRV_PCM_TRIGGER_RESUME:
    260		return pcsp_start_playing(chip);
    261	case SNDRV_PCM_TRIGGER_STOP:
    262	case SNDRV_PCM_TRIGGER_SUSPEND:
    263		pcsp_stop_playing(chip);
    264		break;
    265	default:
    266		return -EINVAL;
    267	}
    268	return 0;
    269}
    270
    271static snd_pcm_uframes_t snd_pcsp_playback_pointer(struct snd_pcm_substream
    272						   *substream)
    273{
    274	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
    275	unsigned int pos;
    276	spin_lock(&chip->substream_lock);
    277	pos = chip->playback_ptr;
    278	spin_unlock(&chip->substream_lock);
    279	return bytes_to_frames(substream->runtime, pos);
    280}
    281
    282static const struct snd_pcm_hardware snd_pcsp_playback = {
    283	.info = (SNDRV_PCM_INFO_INTERLEAVED |
    284		 SNDRV_PCM_INFO_HALF_DUPLEX |
    285		 SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID),
    286	.formats = (SNDRV_PCM_FMTBIT_U8
    287#if DMIX_WANTS_S16
    288		    | SNDRV_PCM_FMTBIT_S16_LE
    289#endif
    290	    ),
    291	.rates = SNDRV_PCM_RATE_KNOT,
    292	.rate_min = PCSP_DEFAULT_SRATE,
    293	.rate_max = PCSP_DEFAULT_SRATE,
    294	.channels_min = 1,
    295	.channels_max = 1,
    296	.buffer_bytes_max = PCSP_BUFFER_SIZE,
    297	.period_bytes_min = 64,
    298	.period_bytes_max = PCSP_MAX_PERIOD_SIZE,
    299	.periods_min = 2,
    300	.periods_max = PCSP_MAX_PERIODS,
    301	.fifo_size = 0,
    302};
    303
    304static int snd_pcsp_playback_open(struct snd_pcm_substream *substream)
    305{
    306	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
    307	struct snd_pcm_runtime *runtime = substream->runtime;
    308#if PCSP_DEBUG
    309	printk(KERN_INFO "PCSP: open called\n");
    310#endif
    311	if (atomic_read(&chip->timer_active)) {
    312		printk(KERN_ERR "PCSP: still active!!\n");
    313		return -EBUSY;
    314	}
    315	runtime->hw = snd_pcsp_playback;
    316	chip->playback_substream = substream;
    317	return 0;
    318}
    319
    320static const struct snd_pcm_ops snd_pcsp_playback_ops = {
    321	.open = snd_pcsp_playback_open,
    322	.close = snd_pcsp_playback_close,
    323	.hw_params = snd_pcsp_playback_hw_params,
    324	.hw_free = snd_pcsp_playback_hw_free,
    325	.prepare = snd_pcsp_playback_prepare,
    326	.trigger = snd_pcsp_trigger,
    327	.pointer = snd_pcsp_playback_pointer,
    328};
    329
    330int snd_pcsp_new_pcm(struct snd_pcsp *chip)
    331{
    332	int err;
    333
    334	err = snd_pcm_new(chip->card, "pcspeaker", 0, 1, 0, &chip->pcm);
    335	if (err < 0)
    336		return err;
    337
    338	snd_pcm_set_ops(chip->pcm, SNDRV_PCM_STREAM_PLAYBACK,
    339			&snd_pcsp_playback_ops);
    340
    341	chip->pcm->private_data = chip;
    342	chip->pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
    343	strcpy(chip->pcm->name, "pcsp");
    344
    345	snd_pcm_set_managed_buffer_all(chip->pcm,
    346				       SNDRV_DMA_TYPE_CONTINUOUS,
    347				       NULL,
    348				       PCSP_BUFFER_SIZE,
    349				       PCSP_BUFFER_SIZE);
    350
    351	return 0;
    352}