Commit message: ALSA: msnd: Optimize / harden DSP and MIDI loops

The ISA msnd drivers have loops fetching the ring-buffer head, tail
and size values inside the loops.  Such codes are inefficient and
fragile.

This patch optimizes it, and also adds the sanity check to avoid the
endless loops.

Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=196131
Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=196133
Signed-off-by: Takashi Iwai <tiwai@suse.de>
CVE: CVE-2017-9984
CWE: CWE-125
Vulnerability description: The snd_msnd_interrupt function in sound/isa/msnd/msnd_pinnacle.c in the Linux kernel through 4.11.7 allows local users to cause a denial of service (over-boundary access) or possibly have unspecified other impact by changing the value of a message queue head pointer between two kernel reads of that value, aka a "double fetch" vulnerability.

Project: linux

static void snd_msndmidi_free(struct snd_rawmidi *rmidi)
{
	struct snd_msndmidi *mpu = rmidi->private_data;
	kfree(mpu);
}

Project: linux

static int snd_msndmidi_input_close(struct snd_rawmidi_substream *substream)
{
	struct snd_msndmidi *mpu;

	mpu = substream->rmidi->private_data;
	snd_msnd_send_dsp_cmd(mpu->dev, HDEX_MIDI_IN_STOP);
	clear_bit(MSNDMIDI_MODE_BIT_INPUT, &mpu->mode);
	mpu->substream_input = NULL;
	snd_msnd_disable_irq(mpu->dev);
	return 0;
}

Project: linux

static void snd_msndmidi_input_drop(struct snd_msndmidi *mpu)
{
	u16 tail;

	tail = readw(mpu->dev->MIDQ + JQS_wTail);
	writew(tail, mpu->dev->MIDQ + JQS_wHead);
}

Project: linux

static int snd_msndmidi_input_open(struct snd_rawmidi_substream *substream)
{
	struct snd_msndmidi *mpu;

	snd_printdd("snd_msndmidi_input_open()\n");

	mpu = substream->rmidi->private_data;

	mpu->substream_input = substream;

	snd_msnd_enable_irq(mpu->dev);

	snd_msnd_send_dsp_cmd(mpu->dev, HDEX_MIDI_IN_START);
	set_bit(MSNDMIDI_MODE_BIT_INPUT, &mpu->mode);
	return 0;
}

Project: linux

int snd_msndmidi_new(struct snd_card *card, int device)
{
	struct snd_msnd *chip = card->private_data;
	struct snd_msndmidi *mpu;
	struct snd_rawmidi *rmidi;
	int err;

	err = snd_rawmidi_new(card, "MSND-MIDI", device, 1, 1, &rmidi);
	if (err < 0)
		return err;
	mpu = kzalloc(sizeof(*mpu), GFP_KERNEL);
	if (mpu == NULL) {
		snd_device_free(card, rmidi);
		return -ENOMEM;
	}
	mpu->dev = chip;
	chip->msndmidi_mpu = mpu;
	rmidi->private_data = mpu;
	rmidi->private_free = snd_msndmidi_free;
	spin_lock_init(&mpu->input_lock);
	strcpy(rmidi->name, "MSND MIDI");
	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
			    &snd_msndmidi_input);
	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
	return 0;
}

Project: linux

static void reset_proteus(struct snd_msnd *chip)
{
	outb(HPPRORESET_ON, chip->io + HP_PROR);
	msleep(TIME_PRO_RESET);
	outb(HPPRORESET_OFF, chip->io + HP_PROR);
	msleep(TIME_PRO_RESET_DONE);
}

Project: linux

static void set_default_audio_parameters(struct snd_msnd *chip)
{
	chip->play_sample_size = DEFSAMPLESIZE;
	chip->play_sample_rate = DEFSAMPLERATE;
	chip->play_channels = DEFCHANNELS;
	chip->capture_sample_size = DEFSAMPLESIZE;
	chip->capture_sample_rate = DEFSAMPLERATE;
	chip->capture_channels = DEFCHANNELS;
}

Project: linux

static int snd_msnd_activate_logical(int cfg, int num)
{
	if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
		return -EIO;
	if (snd_msnd_write_cfg(cfg, IREG_ACTIVATE, LD_ACTIVATE))
		return -EIO;
	return 0;
}

Project: linux

static int snd_msnd_attach(struct snd_card *card)
{
	struct snd_msnd *chip = card->private_data;
	int err;
	static struct snd_device_ops ops = {
		.dev_free =      snd_msnd_dev_free,
		};

	err = request_irq(chip->irq, snd_msnd_interrupt, 0, card->shortname,
			  chip);
	if (err < 0) {
		printk(KERN_ERR LOGNAME ": Couldn't grab IRQ %d\n", chip->irq);
		return err;
	}
	if (request_region(chip->io, DSP_NUMIO, card->shortname) == NULL) {
		free_irq(chip->irq, chip);
		return -EBUSY;
	}

	if (!request_mem_region(chip->base, BUFFSIZE, card->shortname)) {
		printk(KERN_ERR LOGNAME
			": unable to grab memory region 0x%lx-0x%lx\n",
			chip->base, chip->base + BUFFSIZE - 1);
		release_region(chip->io, DSP_NUMIO);
		free_irq(chip->irq, chip);
		return -EBUSY;
	}
	chip->mappedbase = ioremap_nocache(chip->base, 0x8000);
	if (!chip->mappedbase) {
		printk(KERN_ERR LOGNAME
			": unable to map memory region 0x%lx-0x%lx\n",
			chip->base, chip->base + BUFFSIZE - 1);
		err = -EIO;
		goto err_release_region;
	}

	err = snd_msnd_dsp_full_reset(card);
	if (err < 0)
		goto err_release_region;

	/* Register device */
	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
	if (err < 0)
		goto err_release_region;

	err = snd_msnd_pcm(card, 0);
	if (err < 0) {
		printk(KERN_ERR LOGNAME ": error creating new PCM device\n");
		goto err_release_region;
	}

	err = snd_msndmix_new(card);
	if (err < 0) {
		printk(KERN_ERR LOGNAME ": error creating new Mixer device\n");
		goto err_release_region;
	}


	if (mpu_io[0] != SNDRV_AUTO_PORT) {
		struct snd_mpu401 *mpu;

		err = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401,
					  mpu_io[0],
					  MPU401_MODE_INPUT |
					  MPU401_MODE_OUTPUT,
					  mpu_irq[0],
					  &chip->rmidi);
		if (err < 0) {
			printk(KERN_ERR LOGNAME
				": error creating new Midi device\n");
			goto err_release_region;
		}
		mpu = chip->rmidi->private_data;

		mpu->open_input = snd_msnd_mpu401_open;
		mpu->close_input = snd_msnd_mpu401_close;
		mpu->private_data = chip;
	}

	disable_irq(chip->irq);
	snd_msnd_calibrate_adc(chip, chip->play_sample_rate);
	snd_msndmix_force_recsrc(chip, 0);

	err = snd_card_register(card);
	if (err < 0)
		goto err_release_region;

	return 0;

err_release_region:
	iounmap(chip->mappedbase);
	release_mem_region(chip->base, BUFFSIZE);
	release_region(chip->io, DSP_NUMIO);
	free_irq(chip->irq, chip);
	return err;
}

Project: linux

static int snd_msnd_calibrate_adc(struct snd_msnd *chip, u16 srate)
{
	snd_printdd("snd_msnd_calibrate_adc(%i)\n", srate);
	writew(srate, chip->SMA + SMA_wCalFreqAtoD);
	if (chip->calibrate_signal == 0)
		writew(readw(chip->SMA + SMA_wCurrHostStatusFlags)
		       | 0x0001, chip->SMA + SMA_wCurrHostStatusFlags);
	else
		writew(readw(chip->SMA + SMA_wCurrHostStatusFlags)
		       & ~0x0001, chip->SMA + SMA_wCurrHostStatusFlags);
	if (snd_msnd_send_word(chip, 0, 0, HDEXAR_CAL_A_TO_D) == 0 &&
	    snd_msnd_send_dsp_cmd_chk(chip, HDEX_AUX_REQ) == 0) {
		schedule_timeout_interruptible(msecs_to_jiffies(333));
		return 0;
	}
	printk(KERN_WARNING LOGNAME ": ADC calibration failed\n");
	return -EIO;
}

Project: linux

static int snd_msnd_dsp_full_reset(struct snd_card *card)
{
	struct snd_msnd *chip = card->private_data;
	int rv;

	if (test_bit(F_RESETTING, &chip->flags) || ++chip->nresets > 10)
		return 0;

	set_bit(F_RESETTING, &chip->flags);
	snd_msnd_dsp_halt(chip, NULL);	/* Unconditionally halt */

	rv = snd_msnd_initialize(card);
	if (rv)
		printk(KERN_WARNING LOGNAME ": DSP reset failed\n");
	snd_msndmix_force_recsrc(chip, 0);
	clear_bit(F_RESETTING, &chip->flags);
	return rv;
}

Project: linux

static void snd_msnd_eval_dsp_msg(struct snd_msnd *chip, u16 wMessage)
{
	switch (HIBYTE(wMessage)) {
	case HIMT_PLAY_DONE: {
		if (chip->banksPlayed < 3)
			snd_printdd("%08X: HIMT_PLAY_DONE: %i\n",
				(unsigned)jiffies, LOBYTE(wMessage));

		if (chip->last_playbank == LOBYTE(wMessage)) {
			snd_printdd("chip.last_playbank == LOBYTE(wMessage)\n");
			break;
		}
		chip->banksPlayed++;

		if (test_bit(F_WRITING, &chip->flags))
			snd_msnd_DAPQ(chip, 0);

		chip->last_playbank = LOBYTE(wMessage);
		chip->playDMAPos += chip->play_period_bytes;
		if (chip->playDMAPos > chip->playLimit)
			chip->playDMAPos = 0;
		snd_pcm_period_elapsed(chip->playback_substream);

		break;
	}
	case HIMT_RECORD_DONE:
		if (chip->last_recbank == LOBYTE(wMessage))
			break;
		chip->last_recbank = LOBYTE(wMessage);
		chip->captureDMAPos += chip->capturePeriodBytes;
		if (chip->captureDMAPos > (chip->captureLimit))
			chip->captureDMAPos = 0;

		if (test_bit(F_READING, &chip->flags))
			snd_msnd_DARQ(chip, chip->last_recbank);

		snd_pcm_period_elapsed(chip->capture_substream);
		break;

	case HIMT_DSP:
		switch (LOBYTE(wMessage)) {
#ifndef MSND_CLASSIC
		case HIDSP_PLAY_UNDER:
#endif
		case HIDSP_INT_PLAY_UNDER:
			snd_printd(KERN_WARNING LOGNAME ": Play underflow %i\n",
				chip->banksPlayed);
			if (chip->banksPlayed > 2)
				clear_bit(F_WRITING, &chip->flags);
			break;

		case HIDSP_INT_RECORD_OVER:
			snd_printd(KERN_WARNING LOGNAME ": Record overflow\n");
			clear_bit(F_READING, &chip->flags);
			break;

		default:
			snd_printd(KERN_WARNING LOGNAME
				   ": DSP message %d 0x%02x\n",
				   LOBYTE(wMessage), LOBYTE(wMessage));
			break;
		}
		break;

	case HIMT_MIDI_IN_UCHAR:
		if (chip->msndmidi_mpu)
			snd_msndmidi_input_read(chip->msndmidi_mpu);
		break;

	default:
		snd_printd(KERN_WARNING LOGNAME ": HIMT message %d 0x%02x\n",
			   HIBYTE(wMessage), HIBYTE(wMessage));
		break;
	}
}

Project: linux

static void __exit snd_msnd_exit(void)
{
#ifdef CONFIG_PNP
	if (pnp_registered)
		pnp_unregister_card_driver(&msnd_pnpc_driver);
	if (isa_registered)
#endif
		isa_unregister_driver(&snd_msnd_driver);
}

Project: linux

static int __init snd_msnd_init(void)
{
	int err;

	err = isa_register_driver(&snd_msnd_driver, SNDRV_CARDS);
#ifdef CONFIG_PNP
	if (!err)
		isa_registered = 1;

	err = pnp_register_card_driver(&msnd_pnpc_driver);
	if (!err)
		pnp_registered = 1;

	if (isa_registered)
		err = 0;
#endif
	return err;
}

Project: linux

static int snd_msnd_init_sma(struct snd_msnd *chip)
{
	static int initted;
	u16 mastVolLeft, mastVolRight;
	unsigned long flags;

#ifdef MSND_CLASSIC
	outb(chip->memid, chip->io + HP_MEMM);
#endif
	outb(HPBLKSEL_0, chip->io + HP_BLKS);
	/* Motorola 56k shared memory base */
	chip->SMA = chip->mappedbase + SMA_STRUCT_START;

	if (initted) {
		mastVolLeft = readw(chip->SMA + SMA_wCurrMastVolLeft);
		mastVolRight = readw(chip->SMA + SMA_wCurrMastVolRight);
	} else
		mastVolLeft = mastVolRight = 0;
	memset_io(chip->mappedbase, 0, 0x8000);

	/* Critical section: bank 1 access */
	spin_lock_irqsave(&chip->lock, flags);
	outb(HPBLKSEL_1, chip->io + HP_BLKS);
	memset_io(chip->mappedbase, 0, 0x8000);
	outb(HPBLKSEL_0, chip->io + HP_BLKS);
	spin_unlock_irqrestore(&chip->lock, flags);

	/* Digital audio play queue */
	chip->DAPQ = chip->mappedbase + DAPQ_OFFSET;
	snd_msnd_init_queue(chip->DAPQ, DAPQ_DATA_BUFF, DAPQ_BUFF_SIZE);

	/* Digital audio record queue */
	chip->DARQ = chip->mappedbase + DARQ_OFFSET;
	snd_msnd_init_queue(chip->DARQ, DARQ_DATA_BUFF, DARQ_BUFF_SIZE);

	/* MIDI out queue */
	chip->MODQ = chip->mappedbase + MODQ_OFFSET;
	snd_msnd_init_queue(chip->MODQ, MODQ_DATA_BUFF, MODQ_BUFF_SIZE);

	/* MIDI in queue */
	chip->MIDQ = chip->mappedbase + MIDQ_OFFSET;
	snd_msnd_init_queue(chip->MIDQ, MIDQ_DATA_BUFF, MIDQ_BUFF_SIZE);

	/* DSP -> host message queue */
	chip->DSPQ = chip->mappedbase + DSPQ_OFFSET;
	snd_msnd_init_queue(chip->DSPQ, DSPQ_DATA_BUFF, DSPQ_BUFF_SIZE);

	/* Setup some DSP values */
#ifndef MSND_CLASSIC
	writew(1, chip->SMA + SMA_wCurrPlayFormat);
	writew(chip->play_sample_size, chip->SMA + SMA_wCurrPlaySampleSize);
	writew(chip->play_channels, chip->SMA + SMA_wCurrPlayChannels);
	writew(chip->play_sample_rate, chip->SMA + SMA_wCurrPlaySampleRate);
#endif
	writew(chip->play_sample_rate, chip->SMA + SMA_wCalFreqAtoD);
	writew(mastVolLeft, chip->SMA + SMA_wCurrMastVolLeft);
	writew(mastVolRight, chip->SMA + SMA_wCurrMastVolRight);
#ifndef MSND_CLASSIC
	writel(0x00010000, chip->SMA + SMA_dwCurrPlayPitch);
	writel(0x00000001, chip->SMA + SMA_dwCurrPlayRate);
#endif
	writew(0x303, chip->SMA + SMA_wCurrInputTagBits);

	initted = 1;

	return 0;
}

Project: linux

static int snd_msnd_initialize(struct snd_card *card)
{
	struct snd_msnd *chip = card->private_data;
	int err, timeout;

#ifdef MSND_CLASSIC
	outb(HPWAITSTATE_0, chip->io + HP_WAIT);
	outb(HPBITMODE_16, chip->io + HP_BITM);

	reset_proteus(chip);
#endif
	err = snd_msnd_init_sma(chip);
	if (err < 0) {
		printk(KERN_WARNING LOGNAME ": Cannot initialize SMA\n");
		return err;
	}

	err = snd_msnd_reset_dsp(chip->io, NULL);
	if (err < 0)
		return err;

	err = upload_dsp_code(card);
	if (err < 0) {
		printk(KERN_WARNING LOGNAME ": Cannot upload DSP code\n");
		return err;
	}

	timeout = 200;

	while (readw(chip->mappedbase)) {
		msleep(1);
		if (!timeout--) {
			snd_printd(KERN_ERR LOGNAME ": DSP reset timeout\n");
			return -EIO;
		}
	}

	snd_msndmix_setup(chip);
	return 0;
}

Project: linux

static int snd_msnd_isa_match(struct device *pdev, unsigned int i)
{
	if (io[i] == SNDRV_AUTO_PORT)
		return 0;

	if (irq[i] == SNDRV_AUTO_PORT || mem[i] == SNDRV_AUTO_PORT) {
		printk(KERN_WARNING LOGNAME ": io, irq and mem must be set\n");
		return 0;
	}

#ifdef MSND_CLASSIC
	if (!(io[i] == 0x290 ||
	      io[i] == 0x260 ||
	      io[i] == 0x250 ||
	      io[i] == 0x240 ||
	      io[i] == 0x230 ||
	      io[i] == 0x220 ||
	      io[i] == 0x210 ||
	      io[i] == 0x3e0)) {
		printk(KERN_ERR LOGNAME ": \"io\" - DSP I/O base must be set "
			" to 0x210, 0x220, 0x230, 0x240, 0x250, 0x260, 0x290, "
			"or 0x3E0\n");
		return 0;
	}
#else
	if (io[i] < 0x100 || io[i] > 0x3e0 || (io[i] % 0x10) != 0) {
		printk(KERN_ERR LOGNAME
			": \"io\" - DSP I/O base must within the range 0x100 "
			"to 0x3E0 and must be evenly divisible by 0x10\n");
		return 0;
	}
#endif /* MSND_CLASSIC */

	if (!(irq[i] == 5 ||
	      irq[i] == 7 ||
	      irq[i] == 9 ||
	      irq[i] == 10 ||
	      irq[i] == 11 ||
	      irq[i] == 12)) {
		printk(KERN_ERR LOGNAME
			": \"irq\" - must be set to 5, 7, 9, 10, 11 or 12\n");
		return 0;
	}

	if (!(mem[i] == 0xb0000 ||
	      mem[i] == 0xc8000 ||
	      mem[i] == 0xd0000 ||
	      mem[i] == 0xd8000 ||
	      mem[i] == 0xe0000 ||
	      mem[i] == 0xe8000)) {
		printk(KERN_ERR LOGNAME ": \"mem\" - must be set to "
		       "0xb0000, 0xc8000, 0xd0000, 0xd8000, 0xe0000 or "
		       "0xe8000\n");
		return 0;
	}

#ifndef MSND_CLASSIC
	if (cfg[i] == SNDRV_AUTO_PORT) {
		printk(KERN_INFO LOGNAME ": Assuming PnP mode\n");
	} else if (cfg[i] != 0x250 && cfg[i] != 0x260 && cfg[i] != 0x270) {
		printk(KERN_INFO LOGNAME
			": Config port must be 0x250, 0x260 or 0x270 "
			"(or unspecified for PnP mode)\n");
		return 0;
	}
#endif /* MSND_CLASSIC */

	return 1;
}

Project: linux

static int snd_msnd_isa_remove(struct device *pdev, unsigned int dev)
{
	snd_msnd_unload(dev_get_drvdata(pdev));
	return 0;
}

Project: linux

static void snd_msnd_mpu401_close(struct snd_mpu401 *mpu)
{
	snd_msnd_send_dsp_cmd(mpu->private_data, HDEX_MIDI_IN_STOP);
	snd_msnd_disable_irq(mpu->private_data);
}

Project: linux

static int snd_msnd_mpu401_open(struct snd_mpu401 *mpu)
{
	snd_msnd_enable_irq(mpu->private_data);
	snd_msnd_send_dsp_cmd(mpu->private_data, HDEX_MIDI_IN_START);
	return 0;
}

Project: linux

static int snd_msnd_pinnacle_cfg_reset(int cfg)
{
	int i;

	/* Reset devices if told to */
	printk(KERN_INFO LOGNAME ": Resetting all devices\n");
	for (i = 0; i < 4; ++i)
		if (snd_msnd_write_cfg_logical(cfg, i, 0, 0, 0, 0))
			return -EIO;

	return 0;
}

Project: linux

static int snd_msnd_pnp_detect(struct pnp_card_link *pcard,
			       const struct pnp_card_device_id *pid)
{
	static int idx;
	struct pnp_dev *pnp_dev;
	struct pnp_dev *mpu_dev;
	struct snd_card *card;
	struct snd_msnd *chip;
	int ret;

	for ( ; idx < SNDRV_CARDS; idx++) {
		if (has_isapnp(idx))
			break;
	}
	if (idx >= SNDRV_CARDS)
		return -ENODEV;

	/*
	 * Check that we still have room for another sound card ...
	 */
	pnp_dev = pnp_request_card_device(pcard, pid->devs[0].id, NULL);
	if (!pnp_dev)
		return -ENODEV;

	mpu_dev = pnp_request_card_device(pcard, pid->devs[1].id, NULL);
	if (!mpu_dev)
		return -ENODEV;

	if (!pnp_is_active(pnp_dev) && pnp_activate_dev(pnp_dev) < 0) {
		printk(KERN_INFO "msnd_pinnacle: device is inactive\n");
		return -EBUSY;
	}

	if (!pnp_is_active(mpu_dev) && pnp_activate_dev(mpu_dev) < 0) {
		printk(KERN_INFO "msnd_pinnacle: MPU device is inactive\n");
		return -EBUSY;
	}

	/*
	 * Create a new ALSA sound card entry, in anticipation
	 * of detecting our hardware ...
	 */
	ret = snd_card_new(&pcard->card->dev,
			   index[idx], id[idx], THIS_MODULE,
			   sizeof(struct snd_msnd), &card);
	if (ret < 0)
		return ret;

	chip = card->private_data;
	chip->card = card;

	/*
	 * Read the correct parameters off the ISA PnP bus ...
	 */
	io[idx] = pnp_port_start(pnp_dev, 0);
	irq[idx] = pnp_irq(pnp_dev, 0);
	mem[idx] = pnp_mem_start(pnp_dev, 0);
	mpu_io[idx] = pnp_port_start(mpu_dev, 0);
	mpu_irq[idx] = pnp_irq(mpu_dev, 0);

	set_default_audio_parameters(chip);
#ifdef MSND_CLASSIC
	chip->type = msndClassic;
#else
	chip->type = msndPinnacle;
#endif
	chip->io = io[idx];
	chip->irq = irq[idx];
	chip->base = mem[idx];

	chip->calibrate_signal = calibrate_signal ? 1 : 0;
	chip->recsrc = 0;
	chip->dspq_data_buff = DSPQ_DATA_BUFF;
	chip->dspq_buff_size = DSPQ_BUFF_SIZE;
	if (write_ndelay[idx])
		clear_bit(F_DISABLE_WRITE_NDELAY, &chip->flags);
	else
		set_bit(F_DISABLE_WRITE_NDELAY, &chip->flags);
#ifndef MSND_CLASSIC
	if (digital[idx])
		set_bit(F_HAVEDIGITAL, &chip->flags);
#endif
	spin_lock_init(&chip->lock);
	ret = snd_msnd_probe(card);
	if (ret < 0) {
		printk(KERN_ERR LOGNAME ": Probe failed\n");
		goto _release_card;
	}

	ret = snd_msnd_attach(card);
	if (ret < 0) {
		printk(KERN_ERR LOGNAME ": Attach failed\n");
		goto _release_card;
	}

	pnp_set_card_drvdata(pcard, card);
	++idx;
	return 0;

_release_card:
	snd_card_free(card);
	return ret;
}

Project: linux

static void snd_msnd_pnp_remove(struct pnp_card_link *pcard)
{
	snd_msnd_unload(pnp_get_card_drvdata(pcard));
	pnp_set_card_drvdata(pcard, NULL);
}

Project: linux

static int snd_msnd_probe(struct snd_card *card)
{
	struct snd_msnd *chip = card->private_data;
	unsigned char info;
#ifndef MSND_CLASSIC
	char *xv, *rev = NULL;
	char *pin = "TB Pinnacle", *fiji = "TB Fiji";
	char *pinfiji = "TB Pinnacle/Fiji";
#endif

	if (!request_region(chip->io, DSP_NUMIO, "probing")) {
		snd_printk(KERN_ERR LOGNAME ": I/O port conflict\n");
		return -ENODEV;
	}

	if (snd_msnd_reset_dsp(chip->io, &info) < 0) {
		release_region(chip->io, DSP_NUMIO);
		return -ENODEV;
	}

#ifdef MSND_CLASSIC
	strcpy(card->shortname, "Classic/Tahiti/Monterey");
	strcpy(card->longname, "Turtle Beach Multisound");
	printk(KERN_INFO LOGNAME ": %s, "
	       "I/O 0x%lx-0x%lx, IRQ %d, memory mapped to 0x%lX-0x%lX\n",
	       card->shortname,
	       chip->io, chip->io + DSP_NUMIO - 1,
	       chip->irq,
	       chip->base, chip->base + 0x7fff);
#else
	switch (info >> 4) {
	case 0xf:
		xv = "<= 1.15";
		break;
	case 0x1:
		xv = "1.18/1.2";
		break;
	case 0x2:
		xv = "1.3";
		break;
	case 0x3:
		xv = "1.4";
		break;
	default:
		xv = "unknown";
		break;
	}

	switch (info & 0x7) {
	case 0x0:
		rev = "I";
		strcpy(card->shortname, pin);
		break;
	case 0x1:
		rev = "F";
		strcpy(card->shortname, pin);
		break;
	case 0x2:
		rev = "G";
		strcpy(card->shortname, pin);
		break;
	case 0x3:
		rev = "H";
		strcpy(card->shortname, pin);
		break;
	case 0x4:
		rev = "E";
		strcpy(card->shortname, fiji);
		break;
	case 0x5:
		rev = "C";
		strcpy(card->shortname, fiji);
		break;
	case 0x6:
		rev = "D";
		strcpy(card->shortname, fiji);
		break;
	case 0x7:
		rev = "A-B (Fiji) or A-E (Pinnacle)";
		strcpy(card->shortname, pinfiji);
		break;
	}
	strcpy(card->longname, "Turtle Beach Multisound Pinnacle");
	printk(KERN_INFO LOGNAME ": %s revision %s, Xilinx version %s, "
	       "I/O 0x%lx-0x%lx, IRQ %d, memory mapped to 0x%lX-0x%lX\n",
	       card->shortname,
	       rev, xv,
	       chip->io, chip->io + DSP_NUMIO - 1,
	       chip->irq,
	       chip->base, chip->base + 0x7fff);
#endif

	release_region(chip->io, DSP_NUMIO);
	return 0;
}

Project: linux

static int snd_msnd_reset_dsp(long io, unsigned char *info)
{
	int timeout = 100;

	outb(HPDSPRESET_ON, io + HP_DSPR);
	msleep(1);
#ifndef MSND_CLASSIC
	if (info)
		*info = inb(io + HP_INFO);
#endif
	outb(HPDSPRESET_OFF, io + HP_DSPR);
	msleep(1);
	while (timeout-- > 0) {
		if (inb(io + HP_CVR) == HP_CVR_DEF)
			return 0;
		msleep(1);
	}
	snd_printk(KERN_ERR LOGNAME ": Cannot reset DSP\n");

	return -EIO;
}

Project: linux

static int snd_msnd_send_dsp_cmd_chk(struct snd_msnd *chip, u8 cmd)
{
	if (snd_msnd_send_dsp_cmd(chip, cmd) == 0)
		return 0;
	snd_msnd_dsp_full_reset(chip->card);
	return snd_msnd_send_dsp_cmd(chip, cmd);
}

Project: linux

static void snd_msnd_unload(struct snd_card *card)
{
	struct snd_msnd *chip = card->private_data;

	iounmap(chip->mappedbase);
	release_mem_region(chip->base, BUFFSIZE);
	release_region(chip->io, DSP_NUMIO);
	free_irq(chip->irq, chip);
	snd_card_free(card);
}

Project: linux

static int snd_msnd_write_cfg(int cfg, int reg, int value)
{
	outb(reg, cfg);
	outb(value, cfg + 1);
	if (value != inb(cfg + 1)) {
		printk(KERN_ERR LOGNAME ": snd_msnd_write_cfg: I/O error\n");
		return -EIO;
	}
	return 0;
}

Project: linux

static int snd_msnd_write_cfg_io0(int cfg, int num, u16 io)
{
	if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
		return -EIO;
	if (snd_msnd_write_cfg(cfg, IREG_IO0_BASEHI, HIBYTE(io)))
		return -EIO;
	if (snd_msnd_write_cfg(cfg, IREG_IO0_BASELO, LOBYTE(io)))
		return -EIO;
	return 0;
}

Project: linux

static int snd_msnd_write_cfg_io1(int cfg, int num, u16 io)
{
	if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
		return -EIO;
	if (snd_msnd_write_cfg(cfg, IREG_IO1_BASEHI, HIBYTE(io)))
		return -EIO;
	if (snd_msnd_write_cfg(cfg, IREG_IO1_BASELO, LOBYTE(io)))
		return -EIO;
	return 0;
}

Project: linux

static int snd_msnd_write_cfg_irq(int cfg, int num, u16 irq)
{
	if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
		return -EIO;
	if (snd_msnd_write_cfg(cfg, IREG_IRQ_NUMBER, LOBYTE(irq)))
		return -EIO;
	if (snd_msnd_write_cfg(cfg, IREG_IRQ_TYPE, IRQTYPE_EDGE))
		return -EIO;
	return 0;
}

Project: linux

static int snd_msnd_write_cfg_logical(int cfg, int num, u16 io0,
				      u16 io1, u16 irq, int mem)
{
	if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
		return -EIO;
	if (snd_msnd_write_cfg_io0(cfg, num, io0))
		return -EIO;
	if (snd_msnd_write_cfg_io1(cfg, num, io1))
		return -EIO;
	if (snd_msnd_write_cfg_irq(cfg, num, irq))
		return -EIO;
	if (snd_msnd_write_cfg_mem(cfg, num, mem))
		return -EIO;
	if (snd_msnd_activate_logical(cfg, num))
		return -EIO;
	return 0;
}