2608 lines · 64.7 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 <linux/anon_inodes.h>#include <linux/idr.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 snd_fasync *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 */}; #ifdef CONFIG_SND_UTIMER#define SNDRV_UTIMERS_MAX_COUNT 128/* Internal data structure for keeping the state of the userspace-driven timer */struct snd_utimer {	char *name;	struct snd_timer *timer;	unsigned int id;};#endif #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); /* list of open master instances that can accept slave links */static LIST_HEAD(snd_timer_master_list); /* rwlock for timer instance (for trigger actions) */static DEFINE_RWLOCK(timeri_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_obj(*timeri);	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->master_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 /* * refcount management of timer object */static void snd_timer_kref_release(struct kref *kref); static inline void snd_timer_ref_get(struct snd_timer *timer){	kref_get(&timer->kref);} static inline void snd_timer_ref_put(struct snd_timer *timer){	kref_put(&timer->kref, snd_timer_kref_release);} /* * Return the assigned timer for the instance, NULL if not present; * the caller is responsible to call snd_timeri_timer_put(), or use auto-cleanup */struct snd_timer *snd_timeri_timer_get(struct snd_timer_instance *timeri){	struct snd_timer *t; 	guard(read_lock_irqsave)(&timeri_lock);	t = timeri->timer;	if (!t)		return NULL;	snd_timer_ref_get(t);	return t;}EXPORT_SYMBOL_GPL(snd_timeri_timer_get); void snd_timeri_timer_put(struct snd_timer *timer){	snd_timer_ref_put(timer);}EXPORT_SYMBOL_GPL(snd_timeri_timer_put); /* 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++;	snd_timer_ref_get(master->timer);	guard(write_lock_irq)(&timeri_lock);	guard(spinlock)(&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);	return 1;} static bool snd_timer_has_slave_key(const struct snd_timer_instance *timeri){	return !(timeri->flags & SNDRV_TIMER_IFLG_SLAVE) &&		timeri->slave_class > SNDRV_TIMER_SCLASS_NONE;} /* * 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_instance *master;	int err = 0; 	list_for_each_entry(master, &snd_timer_master_list, master_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);	if (snd_timer_has_slave_key(timeri))		list_add_tail(&timeri->master_list, &snd_timer_master_list);	timer->num_instances++;	snd_timer_ref_get(timer);	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); /* remove slave links, called from snd_timer_close_locked() below */static void remove_slave_links(struct snd_timer_instance *timeri,			       struct snd_timer *timer){	struct snd_timer_instance *slave, *tmp; 	guard(write_lock_irq)(&timeri_lock);	guard(spinlock)(&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--;		snd_timer_ref_put(timer);		slave->master = NULL;		slave->timer = NULL;		list_del_init(&slave->ack_list);		list_del_init(&slave->active_list);	}	/* the close is done; a reopen must not see the mark */	timeri->flags &= ~SNDRV_TIMER_IFLG_DEAD;} /* * 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; 	if (timer) {		guard(spinlock_irq)(&timer->lock);		if (timeri->flags & SNDRV_TIMER_IFLG_DEAD)			return; /* already closed */		timeri->flags |= SNDRV_TIMER_IFLG_DEAD;	} 	if (!list_empty(&timeri->open_list)) {		list_del_init(&timeri->open_list);		if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE)			num_slaves--;	} 	if (!list_empty(&timeri->master_list))		list_del_init(&timeri->master_list); 	/* force to stop the timer */	snd_timer_stop(timeri); 	if (timer) {		struct snd_timer_instance *slave;		bool busy; 		timer->num_instances--;		/* unqueue then drain the slaves' callbacks before remove_slave_links() severs them */		spin_lock_irq(&timer->lock);		list_for_each_entry(slave, &timeri->slave_list_head, open_list)

Lines 1–500 of 2608.