linux/sound/drivers/pcsp/pcsp.c
Takashi Iwai 6408eac266 ALSA: pcsp: Fix the order of input device unregistration
The current code may access to the already freed object.  The input
device must be accessed and unregistered before freeing the top level
sound object.

Cc: <stable@vger.kernel.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2013-11-14 15:50:10 +01:00

248 lines
5.5 KiB
C

/*
* PC-Speaker driver for Linux
*
* Copyright (C) 1997-2001 David Woodhouse
* Copyright (C) 2001-2008 Stas Sergeev
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <asm/bitops.h>
#include "pcsp_input.h"
#include "pcsp.h"
MODULE_AUTHOR("Stas Sergeev <stsp@users.sourceforge.net>");
MODULE_DESCRIPTION("PC-Speaker driver");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("{{PC-Speaker, pcsp}}");
MODULE_ALIAS("platform:pcspkr");
static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */
static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */
static bool enable = SNDRV_DEFAULT_ENABLE1; /* Enable this card */
static bool nopcm; /* Disable PCM capability of the driver */
module_param(index, int, 0444);
MODULE_PARM_DESC(index, "Index value for pcsp soundcard.");
module_param(id, charp, 0444);
MODULE_PARM_DESC(id, "ID string for pcsp soundcard.");
module_param(enable, bool, 0444);
MODULE_PARM_DESC(enable, "Enable PC-Speaker sound.");
module_param(nopcm, bool, 0444);
MODULE_PARM_DESC(nopcm, "Disable PC-Speaker PCM sound. Only beeps remain.");
struct snd_pcsp pcsp_chip;
static int snd_pcsp_create(struct snd_card *card)
{
static struct snd_device_ops ops = { };
struct timespec tp;
int err;
int div, min_div, order;
hrtimer_get_res(CLOCK_MONOTONIC, &tp);
if (!nopcm) {
if (tp.tv_sec || tp.tv_nsec > PCSP_MAX_PERIOD_NS) {
printk(KERN_ERR "PCSP: Timer resolution is not sufficient "
"(%linS)\n", tp.tv_nsec);
printk(KERN_ERR "PCSP: Make sure you have HPET and ACPI "
"enabled.\n");
printk(KERN_ERR "PCSP: Turned into nopcm mode.\n");
nopcm = 1;
}
}
if (loops_per_jiffy >= PCSP_MIN_LPJ && tp.tv_nsec <= PCSP_MIN_PERIOD_NS)
min_div = MIN_DIV;
else
min_div = MAX_DIV;
#if PCSP_DEBUG
printk(KERN_DEBUG "PCSP: lpj=%li, min_div=%i, res=%li\n",
loops_per_jiffy, min_div, tp.tv_nsec);
#endif
div = MAX_DIV / min_div;
order = fls(div) - 1;
pcsp_chip.max_treble = min(order, PCSP_MAX_TREBLE);
pcsp_chip.treble = min(pcsp_chip.max_treble, PCSP_DEFAULT_TREBLE);
pcsp_chip.playback_ptr = 0;
pcsp_chip.period_ptr = 0;
atomic_set(&pcsp_chip.timer_active, 0);
pcsp_chip.enable = 1;
pcsp_chip.pcspkr = 1;
spin_lock_init(&pcsp_chip.substream_lock);
pcsp_chip.card = card;
pcsp_chip.port = 0x61;
pcsp_chip.irq = -1;
pcsp_chip.dma = -1;
/* Register device */
err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, &pcsp_chip, &ops);
if (err < 0)
return err;
return 0;
}
static int snd_card_pcsp_probe(int devnum, struct device *dev)
{
struct snd_card *card;
int err;
if (devnum != 0)
return -EINVAL;
hrtimer_init(&pcsp_chip.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
pcsp_chip.timer.function = pcsp_do_timer;
err = snd_card_create(index, id, THIS_MODULE, 0, &card);
if (err < 0)
return err;
err = snd_pcsp_create(card);
if (err < 0) {
snd_card_free(card);
return err;
}
if (!nopcm) {
err = snd_pcsp_new_pcm(&pcsp_chip);
if (err < 0) {
snd_card_free(card);
return err;
}
}
err = snd_pcsp_new_mixer(&pcsp_chip, nopcm);
if (err < 0) {
snd_card_free(card);
return err;
}
snd_card_set_dev(pcsp_chip.card, dev);
strcpy(card->driver, "PC-Speaker");
strcpy(card->shortname, "pcsp");
sprintf(card->longname, "Internal PC-Speaker at port 0x%x",
pcsp_chip.port);
err = snd_card_register(card);
if (err < 0) {
snd_card_free(card);
return err;
}
return 0;
}
static int alsa_card_pcsp_init(struct device *dev)
{
int err;
err = snd_card_pcsp_probe(0, dev);
if (err) {
printk(KERN_ERR "PC-Speaker initialization failed.\n");
return err;
}
#ifdef CONFIG_DEBUG_PAGEALLOC
/* Well, CONFIG_DEBUG_PAGEALLOC makes the sound horrible. Lets alert */
printk(KERN_WARNING "PCSP: CONFIG_DEBUG_PAGEALLOC is enabled, "
"which may make the sound noisy.\n");
#endif
return 0;
}
static void alsa_card_pcsp_exit(struct snd_pcsp *chip)
{
snd_card_free(chip->card);
}
static int pcsp_probe(struct platform_device *dev)
{
int err;
err = pcspkr_input_init(&pcsp_chip.input_dev, &dev->dev);
if (err < 0)
return err;
err = alsa_card_pcsp_init(&dev->dev);
if (err < 0) {
pcspkr_input_remove(pcsp_chip.input_dev);
return err;
}
platform_set_drvdata(dev, &pcsp_chip);
return 0;
}
static int pcsp_remove(struct platform_device *dev)
{
struct snd_pcsp *chip = platform_get_drvdata(dev);
pcspkr_input_remove(chip->input_dev);
alsa_card_pcsp_exit(chip);
return 0;
}
static void pcsp_stop_beep(struct snd_pcsp *chip)
{
pcsp_sync_stop(chip);
pcspkr_stop_sound();
}
#ifdef CONFIG_PM_SLEEP
static int pcsp_suspend(struct device *dev)
{
struct snd_pcsp *chip = dev_get_drvdata(dev);
pcsp_stop_beep(chip);
snd_pcm_suspend_all(chip->pcm);
return 0;
}
static SIMPLE_DEV_PM_OPS(pcsp_pm, pcsp_suspend, NULL);
#define PCSP_PM_OPS &pcsp_pm
#else
#define PCSP_PM_OPS NULL
#endif /* CONFIG_PM_SLEEP */
static void pcsp_shutdown(struct platform_device *dev)
{
struct snd_pcsp *chip = platform_get_drvdata(dev);
pcsp_stop_beep(chip);
}
static struct platform_driver pcsp_platform_driver = {
.driver = {
.name = "pcspkr",
.owner = THIS_MODULE,
.pm = PCSP_PM_OPS,
},
.probe = pcsp_probe,
.remove = pcsp_remove,
.shutdown = pcsp_shutdown,
};
static int __init pcsp_init(void)
{
if (!enable)
return -ENODEV;
return platform_driver_register(&pcsp_platform_driver);
}
static void __exit pcsp_exit(void)
{
platform_driver_unregister(&pcsp_platform_driver);
}
module_init(pcsp_init);
module_exit(pcsp_exit);