2348 lines · 58.4 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* *  Timers abstract layer *  Copyright (c) by Jaroslav Kysela <perex@perex.cz> */ #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#define SNDRV_TIMER_IFLG_DEAD		0x00020000 #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"); enum timer_tread_format {	TREAD_FORMAT_NONE = 0,	TREAD_FORMAT_TIME64,	TREAD_FORMAT_TIME32,}; struct snd_timer_tread32 {	int event;	s32 tstamp_sec;	s32 tstamp_nsec;	unsigned int val;}; struct snd_timer_tread64 {	int event;	u8 pad1[4];	s64 tstamp_sec;	s64 tstamp_nsec;	unsigned int val;	u8 pad2[4];}; 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_tread64 *tqueue;	spinlock_t qlock;	unsigned long last_resolution;	unsigned int filter;	struct timespec64 tstamp;		/* trigger tstamp */	wait_queue_head_t qchange_sleep;	struct fasync_struct *fasync;	struct mutex ioctl_lock;}; struct snd_timer_status32 {	s32 tstamp_sec;			/* Timestamp - last update */	s32 tstamp_nsec;	unsigned int resolution;	/* current period resolution in ns */	unsigned int lost;		/* counter of master tick lost */	unsigned int overrun;		/* count of read queue overruns */	unsigned int queue;		/* used queue size */	unsigned char reserved[64];	/* reserved */}; #define SNDRV_TIMER_IOCTL_STATUS32	_IOR('T', 0x14, struct snd_timer_status32) struct snd_timer_status64 {	s64 tstamp_sec;			/* Timestamp - last update */	s64 tstamp_nsec;	unsigned int resolution;	/* current period resolution in ns */	unsigned int lost;		/* counter of master tick lost */	unsigned int overrun;		/* count of read queue overruns */	unsigned int queue;		/* used queue size */	unsigned char reserved[64];	/* reserved */}; #define SNDRV_TIMER_IOCTL_STATUS64	_IOR('T', 0x14, struct snd_timer_status64) /* 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); #define MAX_SLAVE_INSTANCES	1000static int num_slaves; 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. */struct snd_timer_instance *snd_timer_instance_new(const char *owner){	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); 	return timeri;}EXPORT_SYMBOL(snd_timer_instance_new); void snd_timer_instance_free(struct snd_timer_instance *timeri){	if (timeri) {		if (timeri->private_free)			timeri->private_free(timeri);		kfree(timeri->owner);		kfree(timeri);	}}EXPORT_SYMBOL(snd_timer_instance_free); /* * 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; 	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 /* move the slave if it belongs to the master; return 1 if match */static int check_matching_master_slave(struct snd_timer_instance *master,				       struct snd_timer_instance *slave){	if (slave->slave_class != master->slave_class ||	    slave->slave_id != master->slave_id)		return 0;	if (master->timer->num_instances >= master->timer->max_instances)		return -EBUSY;	list_move_tail(&slave->open_list, &master->slave_list_head);	master->timer->num_instances++;	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);	return 1;} /* * 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 int snd_timer_check_slave(struct snd_timer_instance *slave){	struct snd_timer *timer;	struct snd_timer_instance *master;	int err = 0; 	/* 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) {			err = check_matching_master_slave(master, slave);			if (err != 0) /* match found or error */				goto out;		}	} out:	return err < 0 ? err : 0;} /* * 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 int snd_timer_check_master(struct snd_timer_instance *master){	struct snd_timer_instance *slave, *tmp;	int err = 0; 	/* check all pending slaves */	list_for_each_entry_safe(slave, tmp, &snd_timer_slave_list, open_list) {		err = check_matching_master_slave(master, slave);		if (err < 0)			break;	}	return err < 0 ? err : 0;} static void snd_timer_close_locked(struct snd_timer_instance *timeri,				   struct device **card_devp_to_put); /* * open a timer instance * when opening a master, the slave id must be here given. */int snd_timer_open(struct snd_timer_instance *timeri,		   struct snd_timer_id *tid,		   unsigned int slave_id){	struct snd_timer *timer;	struct device *card_dev_to_put = NULL;	int err; 	mutex_lock(&register_mutex);	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);			err = -EINVAL;			goto unlock;		}		if (num_slaves >= MAX_SLAVE_INSTANCES) {			err = -EBUSY;			goto unlock;		}		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);		num_slaves++;		err = snd_timer_check_slave(timeri);		goto list_added;	} 	/* open a master instance */	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) {		err = -ENODEV;		goto unlock;	}	if (!list_empty(&timer->open_list_head)) {		struct snd_timer_instance *t =			list_entry(timer->open_list_head.next,				    struct snd_timer_instance, open_list);		if (t->flags & SNDRV_TIMER_IFLG_EXCLUSIVE) {			err = -EBUSY;			goto unlock;		}	}	if (timer->num_instances >= timer->max_instances) {		err = -EBUSY;		goto unlock;	}	if (!try_module_get(timer->module)) {		err = -EBUSY;		goto unlock;	}	/* take a card refcount for safe disconnection */	if (timer->card) {		get_device(&timer->card->card_dev);		card_dev_to_put = &timer->card->card_dev;	} 	if (list_empty(&timer->open_list_head) && timer->hw.open) {		err = timer->hw.open(timer);		if (err) {			module_put(timer->module);			goto unlock;		}	} 	timeri->timer = timer;	timeri->slave_class = tid->dev_sclass;	timeri->slave_id = slave_id; 	list_add_tail(&timeri->open_list, &timer->open_list_head);	timer->num_instances++;	err = snd_timer_check_master(timeri);list_added:	if (err < 0)		snd_timer_close_locked(timeri, &card_dev_to_put);  unlock:	mutex_unlock(&register_mutex);	/* put_device() is called after unlock for avoiding deadlock */	if (err < 0 && card_dev_to_put)		put_device(card_dev_to_put);	return err;}EXPORT_SYMBOL(snd_timer_open); /* * close a timer instance * call this with register_mutex down. */static void snd_timer_close_locked(struct snd_timer_instance *timeri,				   struct device **card_devp_to_put){	struct snd_timer *timer = timeri->timer;	struct snd_timer_instance *slave, *tmp; 	if (timer) {		spin_lock_irq(&timer->lock);		timeri->flags |= SNDRV_TIMER_IFLG_DEAD;		spin_unlock_irq(&timer->lock);	} 	if (!list_empty(&timeri->open_list)) {		list_del_init(&timeri->open_list);		if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE)			num_slaves--;	} 	/* force to stop the timer */	snd_timer_stop(timeri); 	if (timer) {		timer->num_instances--;		/* 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);		timeri->timer = NULL;		list_for_each_entry_safe(slave, tmp, &timeri->slave_list_head,					 open_list) {			list_move_tail(&slave->open_list, &snd_timer_slave_list);			timer->num_instances--;			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 (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)			*card_devp_to_put = &timer->card->card_dev;		module_put(timer->module);	}} /* * close a timer instance */void snd_timer_close(struct snd_timer_instance *timeri){	struct device *card_dev_to_put = NULL; 	if (snd_BUG_ON(!timeri))		return; 	mutex_lock(&register_mutex);	snd_timer_close_locked(timeri, &card_dev_to_put);	mutex_unlock(&register_mutex);	/* put_device() is called after unlock for avoiding deadlock */	if (card_dev_to_put)		put_device(card_dev_to_put);}EXPORT_SYMBOL(snd_timer_close); static unsigned long snd_timer_hw_resolution(struct snd_timer *timer){	if (timer->hw.c_resolution)		return timer->hw.c_resolution(timer);	else		return timer->hw.resolution;} unsigned long snd_timer_resolution(struct snd_timer_instance *timeri){	struct snd_timer * timer;	unsigned long ret = 0;	unsigned long flags; 	if (timeri == NULL)		return 0;	timer = timeri->timer;	if (timer) {		spin_lock_irqsave(&timer->lock, flags);		ret = snd_timer_hw_resolution(timer);		spin_unlock_irqrestore(&timer->lock, flags);	}	return ret;}EXPORT_SYMBOL(snd_timer_resolution); static void snd_timer_notify1(struct snd_timer_instance *ti, int event){	struct snd_timer *timer = ti->timer;	unsigned long resolution = 0;

Lines 1–500 of 2348.