2128 lines · 53.5 KB · C
RawDownload
/* *  Timers abstract layer *  Copyright (c) by Jaroslav Kysela <perex@perex.cz> * * *   This program is free software; you can redistribute it and/or modify *   it under the terms of the GNU General Public License as published by *   the Free Software Foundation; either version 2 of the License, or *   (at your option) any later version. * *   This program is distributed in the hope that it will be useful, *   but WITHOUT ANY WARRANTY; without even the implied warranty of *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the *   GNU General Public License for more details. * *   You should have received a copy of the GNU General Public License *   along with this program; if not, write to the Free Software *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA * */ #include <linux/delay.h>#include <linux/init.h>#include <linux/slab.h>#include <linux/time.h>#include <linux/mutex.h>#include <linux/device.h>#include <linux/module.h>#include <linux/string.h>#include <linux/sched/signal.h>#include <sound/core.h>#include <sound/timer.h>#include <sound/control.h>#include <sound/info.h>#include <sound/minors.h>#include <sound/initval.h>#include <linux/kmod.h> /* internal flags */#define SNDRV_TIMER_IFLG_PAUSED		0x00010000 #if IS_ENABLED(CONFIG_SND_HRTIMER)#define DEFAULT_TIMER_LIMIT 4#else#define DEFAULT_TIMER_LIMIT 1#endif static int timer_limit = DEFAULT_TIMER_LIMIT;static int timer_tstamp_monotonic = 1;MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>, Takashi Iwai <tiwai@suse.de>");MODULE_DESCRIPTION("ALSA timer interface");MODULE_LICENSE("GPL");module_param(timer_limit, int, 0444);MODULE_PARM_DESC(timer_limit, "Maximum global timers in system.");module_param(timer_tstamp_monotonic, int, 0444);MODULE_PARM_DESC(timer_tstamp_monotonic, "Use posix monotonic clock source for timestamps (default)."); MODULE_ALIAS_CHARDEV(CONFIG_SND_MAJOR, SNDRV_MINOR_TIMER);MODULE_ALIAS("devname:snd/timer"); struct snd_timer_user {	struct snd_timer_instance *timeri;	int tread;		/* enhanced read with timestamps and events */	unsigned long ticks;	unsigned long overrun;	int qhead;	int qtail;	int qused;	int queue_size;	bool disconnected;	struct snd_timer_read *queue;	struct snd_timer_tread *tqueue;	spinlock_t qlock;	unsigned long last_resolution;	unsigned int filter;	struct timespec tstamp;		/* trigger tstamp */	wait_queue_head_t qchange_sleep;	struct fasync_struct *fasync;	struct mutex ioctl_lock;}; /* list of timers */static LIST_HEAD(snd_timer_list); /* list of slave instances */static LIST_HEAD(snd_timer_slave_list); /* lock for slave active lists */static DEFINE_SPINLOCK(slave_active_lock); static DEFINE_MUTEX(register_mutex); static int snd_timer_free(struct snd_timer *timer);static int snd_timer_dev_free(struct snd_device *device);static int snd_timer_dev_register(struct snd_device *device);static int snd_timer_dev_disconnect(struct snd_device *device); static void snd_timer_reschedule(struct snd_timer * timer, unsigned long ticks_left); /* * create a timer instance with the given owner string. * when timer is not NULL, increments the module counter */static struct snd_timer_instance *snd_timer_instance_new(char *owner,							 struct snd_timer *timer){	struct snd_timer_instance *timeri;	timeri = kzalloc(sizeof(*timeri), GFP_KERNEL);	if (timeri == NULL)		return NULL;	timeri->owner = kstrdup(owner, GFP_KERNEL);	if (! timeri->owner) {		kfree(timeri);		return NULL;	}	INIT_LIST_HEAD(&timeri->open_list);	INIT_LIST_HEAD(&timeri->active_list);	INIT_LIST_HEAD(&timeri->ack_list);	INIT_LIST_HEAD(&timeri->slave_list_head);	INIT_LIST_HEAD(&timeri->slave_active_head); 	timeri->timer = timer;	if (timer && !try_module_get(timer->module)) {		kfree(timeri->owner);		kfree(timeri);		return NULL;	} 	return timeri;} /* * find a timer instance from the given timer id */static struct snd_timer *snd_timer_find(struct snd_timer_id *tid){	struct snd_timer *timer = NULL; 	list_for_each_entry(timer, &snd_timer_list, device_list) {		if (timer->tmr_class != tid->dev_class)			continue;		if ((timer->tmr_class == SNDRV_TIMER_CLASS_CARD ||		     timer->tmr_class == SNDRV_TIMER_CLASS_PCM) &&		    (timer->card == NULL ||		     timer->card->number != tid->card))			continue;		if (timer->tmr_device != tid->device)			continue;		if (timer->tmr_subdevice != tid->subdevice)			continue;		return timer;	}	return NULL;} #ifdef CONFIG_MODULES static void snd_timer_request(struct snd_timer_id *tid){	switch (tid->dev_class) {	case SNDRV_TIMER_CLASS_GLOBAL:		if (tid->device < timer_limit)			request_module("snd-timer-%i", tid->device);		break;	case SNDRV_TIMER_CLASS_CARD:	case SNDRV_TIMER_CLASS_PCM:		if (tid->card < snd_ecards_limit)			request_module("snd-card-%i", tid->card);		break;	default:		break;	}} #endif /* * look for a master instance matching with the slave id of the given slave. * when found, relink the open_link of the slave. * * call this with register_mutex down. */static void snd_timer_check_slave(struct snd_timer_instance *slave){	struct snd_timer *timer;	struct snd_timer_instance *master; 	/* FIXME: it's really dumb to look up all entries.. */	list_for_each_entry(timer, &snd_timer_list, device_list) {		list_for_each_entry(master, &timer->open_list_head, open_list) {			if (slave->slave_class == master->slave_class &&			    slave->slave_id == master->slave_id) {				list_move_tail(&slave->open_list,					       &master->slave_list_head);				spin_lock_irq(&slave_active_lock);				slave->master = master;				slave->timer = master->timer;				spin_unlock_irq(&slave_active_lock);				return;			}		}	}} /* * look for slave instances matching with the slave id of the given master. * when found, relink the open_link of slaves. * * call this with register_mutex down. */static void snd_timer_check_master(struct snd_timer_instance *master){	struct snd_timer_instance *slave, *tmp; 	/* check all pending slaves */	list_for_each_entry_safe(slave, tmp, &snd_timer_slave_list, open_list) {		if (slave->slave_class == master->slave_class &&		    slave->slave_id == master->slave_id) {			list_move_tail(&slave->open_list, &master->slave_list_head);			spin_lock_irq(&slave_active_lock);			spin_lock(&master->timer->lock);			slave->master = master;			slave->timer = master->timer;			if (slave->flags & SNDRV_TIMER_IFLG_RUNNING)				list_add_tail(&slave->active_list,					      &master->slave_active_head);			spin_unlock(&master->timer->lock);			spin_unlock_irq(&slave_active_lock);		}	}} /* * open a timer instance * when opening a master, the slave id must be here given. */int snd_timer_open(struct snd_timer_instance **ti,		   char *owner, struct snd_timer_id *tid,		   unsigned int slave_id){	struct snd_timer *timer;	struct snd_timer_instance *timeri = NULL; 	if (tid->dev_class == SNDRV_TIMER_CLASS_SLAVE) {		/* open a slave instance */		if (tid->dev_sclass <= SNDRV_TIMER_SCLASS_NONE ||		    tid->dev_sclass > SNDRV_TIMER_SCLASS_OSS_SEQUENCER) {			pr_debug("ALSA: timer: invalid slave class %i\n",				 tid->dev_sclass);			return -EINVAL;		}		mutex_lock(&register_mutex);		timeri = snd_timer_instance_new(owner, NULL);		if (!timeri) {			mutex_unlock(&register_mutex);			return -ENOMEM;		}		timeri->slave_class = tid->dev_sclass;		timeri->slave_id = tid->device;		timeri->flags |= SNDRV_TIMER_IFLG_SLAVE;		list_add_tail(&timeri->open_list, &snd_timer_slave_list);		snd_timer_check_slave(timeri);		mutex_unlock(&register_mutex);		*ti = timeri;		return 0;	} 	/* open a master instance */	mutex_lock(&register_mutex);	timer = snd_timer_find(tid);#ifdef CONFIG_MODULES	if (!timer) {		mutex_unlock(&register_mutex);		snd_timer_request(tid);		mutex_lock(&register_mutex);		timer = snd_timer_find(tid);	}#endif	if (!timer) {		mutex_unlock(&register_mutex);		return -ENODEV;	}	if (!list_empty(&timer->open_list_head)) {		timeri = list_entry(timer->open_list_head.next,				    struct snd_timer_instance, open_list);		if (timeri->flags & SNDRV_TIMER_IFLG_EXCLUSIVE) {			mutex_unlock(&register_mutex);			return -EBUSY;		}	}	timeri = snd_timer_instance_new(owner, timer);	if (!timeri) {		mutex_unlock(&register_mutex);		return -ENOMEM;	}	/* take a card refcount for safe disconnection */	if (timer->card)		get_device(&timer->card->card_dev);	timeri->slave_class = tid->dev_sclass;	timeri->slave_id = slave_id; 	if (list_empty(&timer->open_list_head) && timer->hw.open) {		int err = timer->hw.open(timer);		if (err) {			kfree(timeri->owner);			kfree(timeri); 			if (timer->card)				put_device(&timer->card->card_dev);			module_put(timer->module);			mutex_unlock(&register_mutex);			return err;		}	} 	list_add_tail(&timeri->open_list, &timer->open_list_head);	snd_timer_check_master(timeri);	mutex_unlock(&register_mutex);	*ti = timeri;	return 0;}EXPORT_SYMBOL(snd_timer_open); /* * close a timer instance */int snd_timer_close(struct snd_timer_instance *timeri){	struct snd_timer *timer = NULL;	struct snd_timer_instance *slave, *tmp; 	if (snd_BUG_ON(!timeri))		return -ENXIO; 	mutex_lock(&register_mutex);	list_del(&timeri->open_list); 	/* force to stop the timer */	snd_timer_stop(timeri); 	timer = timeri->timer;	if (timer) {		/* wait, until the active callback is finished */		spin_lock_irq(&timer->lock);		while (timeri->flags & SNDRV_TIMER_IFLG_CALLBACK) {			spin_unlock_irq(&timer->lock);			udelay(10);			spin_lock_irq(&timer->lock);		}		spin_unlock_irq(&timer->lock); 		/* remove slave links */		spin_lock_irq(&slave_active_lock);		spin_lock(&timer->lock);		list_for_each_entry_safe(slave, tmp, &timeri->slave_list_head,					 open_list) {			list_move_tail(&slave->open_list, &snd_timer_slave_list);			slave->master = NULL;			slave->timer = NULL;			list_del_init(&slave->ack_list);			list_del_init(&slave->active_list);		}		spin_unlock(&timer->lock);		spin_unlock_irq(&slave_active_lock); 		/* slave doesn't need to release timer resources below */		if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE)			timer = NULL;	} 	if (timeri->private_free)		timeri->private_free(timeri);	kfree(timeri->owner);	kfree(timeri); 	if (timer) {		if (list_empty(&timer->open_list_head) && timer->hw.close)			timer->hw.close(timer);		/* release a card refcount for safe disconnection */		if (timer->card)			put_device(&timer->card->card_dev);		module_put(timer->module);	} 	mutex_unlock(&register_mutex);	return 0;}EXPORT_SYMBOL(snd_timer_close); unsigned long snd_timer_resolution(struct snd_timer_instance *timeri){	struct snd_timer * timer; 	if (timeri == NULL)		return 0;	if ((timer = timeri->timer) != NULL) {		if (timer->hw.c_resolution)			return timer->hw.c_resolution(timer);		return timer->hw.resolution;	}	return 0;}EXPORT_SYMBOL(snd_timer_resolution); static void snd_timer_notify1(struct snd_timer_instance *ti, int event){	struct snd_timer *timer;	unsigned long resolution = 0;	struct snd_timer_instance *ts;	struct timespec tstamp; 	if (timer_tstamp_monotonic)		ktime_get_ts(&tstamp);	else		getnstimeofday(&tstamp);	if (snd_BUG_ON(event < SNDRV_TIMER_EVENT_START ||		       event > SNDRV_TIMER_EVENT_PAUSE))		return;	if (event == SNDRV_TIMER_EVENT_START ||	    event == SNDRV_TIMER_EVENT_CONTINUE)		resolution = snd_timer_resolution(ti);	if (ti->ccallback)		ti->ccallback(ti, event, &tstamp, resolution);	if (ti->flags & SNDRV_TIMER_IFLG_SLAVE)		return;	timer = ti->timer;	if (timer == NULL)		return;	if (timer->hw.flags & SNDRV_TIMER_HW_SLAVE)		return;	list_for_each_entry(ts, &ti->slave_active_head, active_list)		if (ts->ccallback)			ts->ccallback(ts, event + 100, &tstamp, resolution);} /* start/continue a master timer */static int snd_timer_start1(struct snd_timer_instance *timeri,			    bool start, unsigned long ticks){	struct snd_timer *timer;	int result;	unsigned long flags; 	timer = timeri->timer;	if (!timer)		return -EINVAL; 	spin_lock_irqsave(&timer->lock, flags);	if (timer->card && timer->card->shutdown) {		result = -ENODEV;		goto unlock;	}	if (timeri->flags & (SNDRV_TIMER_IFLG_RUNNING |			     SNDRV_TIMER_IFLG_START)) {		result = -EBUSY;		goto unlock;	} 	if (start)		timeri->ticks = timeri->cticks = ticks;	else if (!timeri->cticks)		timeri->cticks = 1;	timeri->pticks = 0; 	list_move_tail(&timeri->active_list, &timer->active_list_head);	if (timer->running) {		if (timer->hw.flags & SNDRV_TIMER_HW_SLAVE)			goto __start_now;		timer->flags |= SNDRV_TIMER_FLG_RESCHED;		timeri->flags |= SNDRV_TIMER_IFLG_START;		result = 1; /* delayed start */	} else {		if (start)			timer->sticks = ticks;		timer->hw.start(timer);	      __start_now:		timer->running++;		timeri->flags |= SNDRV_TIMER_IFLG_RUNNING;		result = 0;	}	snd_timer_notify1(timeri, start ? SNDRV_TIMER_EVENT_START :			  SNDRV_TIMER_EVENT_CONTINUE); unlock:	spin_unlock_irqrestore(&timer->lock, flags);	return result;} /* start/continue a slave timer */static int snd_timer_start_slave(struct snd_timer_instance *timeri,				 bool start){	unsigned long flags; 	spin_lock_irqsave(&slave_active_lock, flags);	if (timeri->flags & SNDRV_TIMER_IFLG_RUNNING) {		spin_unlock_irqrestore(&slave_active_lock, flags);		return -EBUSY;	}	timeri->flags |= SNDRV_TIMER_IFLG_RUNNING;	if (timeri->master && timeri->timer) {

Lines 1–500 of 2128.