diff mbox series

[v2,3/4] ALSA: timer: Introduce virtual userspace-driven timers

Message ID 20240729085905.6602-4-ivan.orlov0322@gmail.com
State Superseded
Headers show
Series Introduce userspace-driven ALSA timers | expand

Commit Message

Ivan Orlov July 29, 2024, 8:59 a.m. UTC
Implement two ioctl calls in order to support virtual userspace-driven
ALSA timers.

The first ioctl is SNDRV_TIMER_IOCTL_CREATE, which gets the
snd_utimer_info struct as a parameter and returns a file descriptor of
a virtual timer. It also updates the `id` field of the snd_utimer_info
struct, which provides a unique identifier for the timer (basically,
the subdevice number which can be used when creating timer instances).

This patch also introduces a tiny id allocator for the userspace-driven
timers, which guarantees that we don't have more than 128 of them in the
system.

Another ioctl is SNDRV_TIMER_IOCTL_TRIGGER, which allows us to trigger
the virtual timer (and calls snd_timer_interrupt for the timer under
the hood), causing all of the timer instances binded to this timer to
execute their callbacks.

The maximum amount of ticks available for the timer is 1 for the sake of
simplification of the userspace API. 'start', 'stop', 'open' and 'close'
callbacks for the userspace-driven timers are empty since we don't
really do any hardware initialization here.

Suggested-by: Axel Holzinger <aholzinger@gmx.de>
Signed-off-by: Ivan Orlov <ivan.orlov0322@gmail.com>
---
V1 -> V2:
- Add missing kfree for the utimer name in snd_utimer_free
- Remove extra newline in sound core Kconfig
- Use IDA allocator API to allocate utimer ids
- Use kasprintf for the timer name instead of kzalloc + sprintf

 include/uapi/sound/asound.h |  17 +++
 sound/core/Kconfig          |  10 ++
 sound/core/timer.c          | 213 ++++++++++++++++++++++++++++++++++++
 3 files changed, 240 insertions(+)

Comments

Takashi Iwai July 29, 2024, 2:02 p.m. UTC | #1
On Mon, 29 Jul 2024 10:59:04 +0200,
Ivan Orlov wrote:
> --- a/include/uapi/sound/asound.h
> +++ b/include/uapi/sound/asound.h
(snip)
> +/*
> + * This structure describes the userspace-driven timer. Such timers are purely virtual,
> + * and can only be triggered from software (for instance, by userspace application).
> + */
> +struct snd_utimer_info {
> +	/*
> +	 * To pretend being a normal timer, we need to know the frame rate and
> +	 * the period size in frames.
> +	 */
> +	snd_pcm_uframes_t frame_rate;
> +	snd_pcm_uframes_t period_size;

The units in timer API should be independent from PCM.
So use the explicit type such as __u64 here (so that you don't need
the compat ioctl conversion, too).

> +	unsigned int id;
> +};

We often put some reserved fields for future extension.
But I'm not sure whether it's needed at this time for this kind of
simple interface, though.

>  #define SNDRV_TIMER_IOCTL_PVERSION	_IOR('T', 0x00, int)
>  #define SNDRV_TIMER_IOCTL_NEXT_DEVICE	_IOWR('T', 0x01, struct snd_timer_id)
>  #define SNDRV_TIMER_IOCTL_TREAD_OLD	_IOW('T', 0x02, int)
> @@ -990,6 +1005,8 @@ struct snd_timer_status {
>  #define SNDRV_TIMER_IOCTL_CONTINUE	_IO('T', 0xa2)
>  #define SNDRV_TIMER_IOCTL_PAUSE		_IO('T', 0xa3)
>  #define SNDRV_TIMER_IOCTL_TREAD64	_IOW('T', 0xa4, int)
> +#define SNDRV_TIMER_IOCTL_CREATE	_IOWR('T', 0xa5, struct snd_utimer_info)
> +#define SNDRV_TIMER_IOCTL_TRIGGER	_IO('T', 0xa6)

Once after adding the new API, don't forget to bump the protocol
version defined in SNDRV_TIMER_VERSION.

> --- a/sound/core/timer.c
> +++ b/sound/core/timer.c
(snip)
> +#ifdef CONFIG_SND_UTIMER
> +/*
> + * Since userspace-driven timers are passed to userspace, we need to have an identifier
> + * which will allow us to use them (basically, the subdevice number of udriven timer).
> + */
> +DEFINE_IDA(snd_utimer_ids);

Missing static.

> +static int snd_utimer_create(struct snd_utimer_info *utimer_info,
> +			     struct snd_utimer **r_utimer)
> +{
(snip)
> +	err = snd_timer_new(NULL, utimer->name, &tid, &timer);
> +	if (err < 0) {
> +		pr_err("Can't create userspace-driven timer\n");
> +		goto err_timer_new;
> +	}
> +
> +	timer->module = THIS_MODULE;
> +	timer->hw = timer_hw;
> +	timer->hw.resolution = NANO / utimer_info->frame_rate * utimer_info->period_size;

A sanity check is definitely needed for parameters like this.
e.g. you'd hit a zero-division Oops with this code.
Also, the resolution should be neither too small nor too high.

> +static int snd_utimer_ioctl_create(struct file *file,
> +				   struct snd_utimer_info __user *_utimer_info)
> +{
> +	struct snd_utimer *utimer;
> +	struct snd_utimer_info *utimer_info;
> +	int err;
> +
> +	utimer_info = memdup_user(_utimer_info, sizeof(*utimer_info));
> +	if (IS_ERR(utimer_info))
> +		return PTR_ERR(no_free_ptr(utimer_info));

no_free_ptr() is used only for the automatic cleanup stuff.

> +static int snd_utimer_ioctl_create(struct file *file,
> +				   struct snd_utimer_info __user *_utimer_info)
> +{
> +	return -EINVAL;

Better to keep -ENOTTY?


thanks,

Takashi
Ivan Orlov July 29, 2024, 10:37 p.m. UTC | #2
On 7/29/24 15:02, Takashi Iwai wrote:
> On Mon, 29 Jul 2024 10:59:04 +0200,
> Ivan Orlov wrote:
>> --- a/include/uapi/sound/asound.h
>> +++ b/include/uapi/sound/asound.h
> (snip)

Hi Takashi,

Thank you so much for the review.

>> +/*
>> + * This structure describes the userspace-driven timer. Such timers are purely virtual,
>> + * and can only be triggered from software (for instance, by userspace application).
>> + */
>> +struct snd_utimer_info {
>> +	/*
>> +	 * To pretend being a normal timer, we need to know the frame rate and
>> +	 * the period size in frames.
>> +	 */
>> +	snd_pcm_uframes_t frame_rate;
>> +	snd_pcm_uframes_t period_size;
> 
> The units in timer API should be independent from PCM.
> So use the explicit type such as __u64 here (so that you don't need
> the compat ioctl conversion, too).
> 

Alright, I'll use __u64 here (initially I thought it is going to be more 
clear because it specifies the unit `period_size` is stored in, but I 
agree that it should be completely independent from pcm).

>> +	unsigned int id;
>> +};
> 
> We often put some reserved fields for future extension.
> But I'm not sure whether it's needed at this time for this kind of
> simple interface, though.
> 

Yeah, I don't think we are going to add anything else to the timers 
anytime soon... However, I'll probably add a small reserved space here 
(16 bytes, for instance), just in case we decide to add more parameters. 
Thanks!

>>   #define SNDRV_TIMER_IOCTL_PVERSION	_IOR('T', 0x00, int)
>>   #define SNDRV_TIMER_IOCTL_NEXT_DEVICE	_IOWR('T', 0x01, struct snd_timer_id)
>>   #define SNDRV_TIMER_IOCTL_TREAD_OLD	_IOW('T', 0x02, int)
>> @@ -990,6 +1005,8 @@ struct snd_timer_status {
>>   #define SNDRV_TIMER_IOCTL_CONTINUE	_IO('T', 0xa2)
>>   #define SNDRV_TIMER_IOCTL_PAUSE		_IO('T', 0xa3)
>>   #define SNDRV_TIMER_IOCTL_TREAD64	_IOW('T', 0xa4, int)
>> +#define SNDRV_TIMER_IOCTL_CREATE	_IOWR('T', 0xa5, struct snd_utimer_info)
>> +#define SNDRV_TIMER_IOCTL_TRIGGER	_IO('T', 0xa6)
> 
> Once after adding the new API, don't forget to bump the protocol
> version defined in SNDRV_TIMER_VERSION.
> 

Ah, alright, I'll fix that in V3. Sorry, haven't noticed it :(

>> --- a/sound/core/timer.c
>> +++ b/sound/core/timer.c
> (snip)
>> +#ifdef CONFIG_SND_UTIMER
>> +/*
>> + * Since userspace-driven timers are passed to userspace, we need to have an identifier
>> + * which will allow us to use them (basically, the subdevice number of udriven timer).
>> + */
>> +DEFINE_IDA(snd_utimer_ids);
> 
> Missing static.
> 

Ah, yes, I missed the static here. thanks!

>> +static int snd_utimer_create(struct snd_utimer_info *utimer_info,
>> +			     struct snd_utimer **r_utimer)
>> +{
> (snip)
>> +	err = snd_timer_new(NULL, utimer->name, &tid, &timer);
>> +	if (err < 0) {
>> +		pr_err("Can't create userspace-driven timer\n");
>> +		goto err_timer_new;
>> +	}
>> +
>> +	timer->module = THIS_MODULE;
>> +	timer->hw = timer_hw;
>> +	timer->hw.resolution = NANO / utimer_info->frame_rate * utimer_info->period_size;
> 
> A sanity check is definitely needed for parameters like this.
> e.g. you'd hit a zero-division Oops with this code.
> Also, the resolution should be neither too small nor too high.
> 

Yeah, allowing zero division here (and overflows) is a very bad idea... 
I'll add some checks in V3.

>> +static int snd_utimer_ioctl_create(struct file *file,
>> +				   struct snd_utimer_info __user *_utimer_info)
>> +{
>> +	struct snd_utimer *utimer;
>> +	struct snd_utimer_info *utimer_info;
>> +	int err;
>> +
>> +	utimer_info = memdup_user(_utimer_info, sizeof(*utimer_info));
>> +	if (IS_ERR(utimer_info))
>> +		return PTR_ERR(no_free_ptr(utimer_info));
> 
> no_free_ptr() is used only for the automatic cleanup stuff.
> 

Probably, I should use automatic cleanup here as well (as it is done in 
snd_timer_user_ginfo).

>> +static int snd_utimer_ioctl_create(struct file *file,
>> +				   struct snd_utimer_info __user *_utimer_info)
>> +{
>> +	return -EINVAL;
> 
> Better to keep -ENOTTY?

Initial idea was that EINVAL here would say that the functionality is 
supported, but disabled in the config, but it seems like it breaks the 
convention this way so I'm going to change this to ENOTTY in the next 
version.

Thank you!
kernel test robot July 30, 2024, 2:57 a.m. UTC | #3
Hi Ivan,

kernel test robot noticed the following build warnings:

[auto build test WARNING on tiwai-sound/for-next]
[also build test WARNING on tiwai-sound/for-linus linus/master v6.11-rc1 next-20240729]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Ivan-Orlov/ALSA-aloop-Allow-using-global-timers/20240729-171015
base:   https://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git for-next
patch link:    https://lore.kernel.org/r/20240729085905.6602-4-ivan.orlov0322%40gmail.com
patch subject: [PATCH v2 3/4] ALSA: timer: Introduce virtual userspace-driven timers
config: nios2-randconfig-r113-20240730 (https://download.01.org/0day-ci/archive/20240730/202407301002.SaoBM0NA-lkp@intel.com/config)
compiler: nios2-linux-gcc (GCC) 14.1.0
reproduce: (https://download.01.org/0day-ci/archive/20240730/202407301002.SaoBM0NA-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202407301002.SaoBM0NA-lkp@intel.com/

sparse warnings: (new ones prefixed by >>)
>> sound/core/timer.c:2030:1: sparse: sparse: symbol 'snd_utimer_ids' was not declared. Should it be static?
   sound/core/timer.c:230:12: sparse: sparse: context imbalance in 'check_matching_master_slave' - different lock contexts for basic block
   sound/core/timer.c:405:9: sparse: sparse: context imbalance in 'remove_slave_links' - wrong count at exit
   sound/core/timer.c:456:9: sparse: sparse: context imbalance in 'snd_timer_close_locked' - wrong count at exit
   sound/core/timer.c:492:15: sparse: sparse: context imbalance in 'snd_timer_resolution' - different lock contexts for basic block
   sound/core/timer.c:541:12: sparse: sparse: context imbalance in 'snd_timer_start1' - wrong count at exit
   sound/core/timer.c:596:12: sparse: sparse: context imbalance in 'snd_timer_start_slave' - wrong count at exit
   sound/core/timer.c:616:12: sparse: sparse: context imbalance in 'snd_timer_stop1' - wrong count at exit
   sound/core/timer.c:673:9: sparse: sparse: context imbalance in 'snd_timer_stop_slave' - wrong count at exit
   sound/core/timer.c:780:25: sparse: sparse: context imbalance in 'snd_timer_process_callbacks' - unexpected unlock
   sound/core/timer.c:798:9: sparse: sparse: context imbalance in 'snd_timer_clear_callbacks' - wrong count at exit
   sound/core/timer.c:806:13: sparse: sparse: context imbalance in 'snd_timer_work' - different lock contexts for basic block
   sound/core/timer.c:825:6: sparse: sparse: context imbalance in 'snd_timer_interrupt' - different lock contexts for basic block
   sound/core/timer.c:1049:6: sparse: sparse: context imbalance in 'snd_timer_notify' - different lock contexts for basic block
   sound/core/timer.c:1323:9: sparse: sparse: context imbalance in 'snd_timer_user_interrupt' - wrong count at exit
   sound/core/timer.c:1430:12: sparse: sparse: context imbalance in 'realloc_user_queue' - wrong count at exit
   sound/core/timer.c:1672:12: sparse: sparse: context imbalance in 'snd_timer_user_gstatus' - different lock contexts for basic block
   sound/core/timer.c:2412:9: sparse: sparse: context imbalance in 'snd_timer_user_poll' - wrong count at exit

vim +/snd_utimer_ids +2030 sound/core/timer.c

  2024	
  2025	#ifdef CONFIG_SND_UTIMER
  2026	/*
  2027	 * Since userspace-driven timers are passed to userspace, we need to have an identifier
  2028	 * which will allow us to use them (basically, the subdevice number of udriven timer).
  2029	 */
> 2030	DEFINE_IDA(snd_utimer_ids);
  2031
diff mbox series

Patch

diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h
index 8bf7e8a0eb6f..ade952a54edd 100644
--- a/include/uapi/sound/asound.h
+++ b/include/uapi/sound/asound.h
@@ -894,6 +894,7 @@  enum {
 #define SNDRV_TIMER_GLOBAL_RTC		1	/* unused */
 #define SNDRV_TIMER_GLOBAL_HPET		2
 #define SNDRV_TIMER_GLOBAL_HRTIMER	3
+#define SNDRV_TIMER_GLOBAL_UDRIVEN	4
 
 /* info flags */
 #define SNDRV_TIMER_FLG_SLAVE		(1<<0)	/* cannot be controlled */
@@ -974,6 +975,20 @@  struct snd_timer_status {
 };
 #endif
 
+/*
+ * This structure describes the userspace-driven timer. Such timers are purely virtual,
+ * and can only be triggered from software (for instance, by userspace application).
+ */
+struct snd_utimer_info {
+	/*
+	 * To pretend being a normal timer, we need to know the frame rate and
+	 * the period size in frames.
+	 */
+	snd_pcm_uframes_t frame_rate;
+	snd_pcm_uframes_t period_size;
+	unsigned int id;
+};
+
 #define SNDRV_TIMER_IOCTL_PVERSION	_IOR('T', 0x00, int)
 #define SNDRV_TIMER_IOCTL_NEXT_DEVICE	_IOWR('T', 0x01, struct snd_timer_id)
 #define SNDRV_TIMER_IOCTL_TREAD_OLD	_IOW('T', 0x02, int)
@@ -990,6 +1005,8 @@  struct snd_timer_status {
 #define SNDRV_TIMER_IOCTL_CONTINUE	_IO('T', 0xa2)
 #define SNDRV_TIMER_IOCTL_PAUSE		_IO('T', 0xa3)
 #define SNDRV_TIMER_IOCTL_TREAD64	_IOW('T', 0xa4, int)
+#define SNDRV_TIMER_IOCTL_CREATE	_IOWR('T', 0xa5, struct snd_utimer_info)
+#define SNDRV_TIMER_IOCTL_TRIGGER	_IO('T', 0xa6)
 
 #if __BITS_PER_LONG == 64
 #define SNDRV_TIMER_IOCTL_TREAD SNDRV_TIMER_IOCTL_TREAD_OLD
diff --git a/sound/core/Kconfig b/sound/core/Kconfig
index b970a1734647..670b26cf3065 100644
--- a/sound/core/Kconfig
+++ b/sound/core/Kconfig
@@ -251,6 +251,16 @@  config SND_JACK_INJECTION_DEBUG
 	  Say Y if you are debugging via jack injection interface.
 	  If unsure select "N".
 
+config SND_UTIMER
+	bool "Enable support for userspace-controlled virtual timers"
+	depends on SND_TIMER
+	help
+	  Say Y to enable the support of userspace-controlled timers. These
+	  timers are purely virtual, and they are supposed to be triggered
+	  from userspace. They could be quite useful when synchronizing the
+	  sound timing with userspace applications (for instance, when sending
+	  data through snd-aloop).
+
 config SND_VMASTER
 	bool
 
diff --git a/sound/core/timer.c b/sound/core/timer.c
index d104adc75a8b..6e445df7d9a0 100644
--- a/sound/core/timer.c
+++ b/sound/core/timer.c
@@ -13,6 +13,9 @@ 
 #include <linux/module.h>
 #include <linux/string.h>
 #include <linux/sched/signal.h>
+#include <linux/anon_inodes.h>
+#include <linux/idr.h>
+#include <linux/units.h>
 #include <sound/core.h>
 #include <sound/timer.h>
 #include <sound/control.h>
@@ -109,6 +112,16 @@  struct snd_timer_status64 {
 	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 */
@@ -2009,6 +2022,204 @@  enum {
 	SNDRV_TIMER_IOCTL_PAUSE_OLD = _IO('T', 0x23),
 };
 
+#ifdef CONFIG_SND_UTIMER
+/*
+ * Since userspace-driven timers are passed to userspace, we need to have an identifier
+ * which will allow us to use them (basically, the subdevice number of udriven timer).
+ */
+DEFINE_IDA(snd_utimer_ids);
+
+static void snd_utimer_put_id(struct snd_utimer *utimer)
+{
+	int timer_id = utimer->id;
+
+	snd_BUG_ON(timer_id < 0 || timer_id >= SNDRV_UTIMERS_MAX_COUNT);
+	ida_free(&snd_utimer_ids, timer_id);
+}
+
+static int snd_utimer_take_id(void)
+{
+	return ida_alloc_max(&snd_utimer_ids, SNDRV_UTIMERS_MAX_COUNT - 1, GFP_KERNEL);
+}
+
+static void snd_utimer_free(struct snd_utimer *utimer)
+{
+	snd_timer_free(utimer->timer);
+	snd_utimer_put_id(utimer);
+	kfree(utimer->name);
+	kfree(utimer);
+}
+
+static int snd_utimer_release(struct inode *inode, struct file *file)
+{
+	struct snd_utimer *utimer = (struct snd_utimer *)file->private_data;
+
+	snd_utimer_free(utimer);
+	return 0;
+}
+
+static int snd_utimer_trigger(struct file *file)
+{
+	struct snd_utimer *utimer = (struct snd_utimer *)file->private_data;
+
+	snd_timer_interrupt(utimer->timer, utimer->timer->sticks);
+	return 0;
+}
+
+static long snd_utimer_ioctl(struct file *file, unsigned int ioctl, unsigned long arg)
+{
+	switch (ioctl) {
+	case SNDRV_TIMER_IOCTL_TRIGGER:
+		return snd_utimer_trigger(file);
+	}
+
+	return -ENOTTY;
+}
+
+static const struct file_operations snd_utimer_fops = {
+	.llseek = noop_llseek,
+	.release = snd_utimer_release,
+	.unlocked_ioctl = snd_utimer_ioctl,
+};
+
+static int snd_utimer_start(struct snd_timer *t)
+{
+	return 0;
+}
+
+static int snd_utimer_stop(struct snd_timer *t)
+{
+	return 0;
+}
+
+static int snd_utimer_open(struct snd_timer *t)
+{
+	return 0;
+}
+
+static int snd_utimer_close(struct snd_timer *t)
+{
+	return 0;
+}
+
+static const struct snd_timer_hardware timer_hw = {
+	.flags = SNDRV_TIMER_HW_AUTO | SNDRV_TIMER_HW_WORK,
+	.open = snd_utimer_open,
+	.close = snd_utimer_close,
+	.start = snd_utimer_start,
+	.stop = snd_utimer_stop,
+};
+
+static int snd_utimer_create(struct snd_utimer_info *utimer_info,
+			     struct snd_utimer **r_utimer)
+{
+	struct snd_utimer *utimer;
+	struct snd_timer *timer;
+	struct snd_timer_id tid;
+	int utimer_id;
+	int err = 0;
+
+	utimer = kzalloc(sizeof(*utimer), GFP_KERNEL);
+	if (!utimer)
+		return -ENOMEM;
+
+	/* We hold the ioctl lock here so we won't get a race condition when allocating id */
+	utimer_id = snd_utimer_take_id();
+	if (utimer_id < 0) {
+		err = utimer_id;
+		goto err_take_id;
+	}
+
+	utimer->name = kasprintf(GFP_KERNEL, "snd-utimer%d", utimer_id);
+	if (!utimer->name) {
+		err = -ENOMEM;
+		goto err_get_name;
+	}
+
+	utimer->id = utimer_id;
+
+	tid.dev_sclass = SNDRV_TIMER_SCLASS_APPLICATION;
+	tid.dev_class = SNDRV_TIMER_CLASS_GLOBAL;
+	tid.card = -1;
+	tid.device = SNDRV_TIMER_GLOBAL_UDRIVEN;
+	tid.subdevice = utimer_id;
+
+	err = snd_timer_new(NULL, utimer->name, &tid, &timer);
+	if (err < 0) {
+		pr_err("Can't create userspace-driven timer\n");
+		goto err_timer_new;
+	}
+
+	timer->module = THIS_MODULE;
+	timer->hw = timer_hw;
+	timer->hw.resolution = NANO / utimer_info->frame_rate * utimer_info->period_size;
+	timer->hw.ticks = 1;
+	timer->max_instances = MAX_SLAVE_INSTANCES;
+
+	utimer->timer = timer;
+
+	err = snd_timer_global_register(timer);
+	if (err < 0) {
+		pr_err("Can't register a userspace-driven timer\n");
+		goto err_timer_reg;
+	}
+
+	*r_utimer = utimer;
+	return 0;
+
+err_timer_reg:
+	snd_timer_free(timer);
+err_timer_new:
+	kfree(utimer->name);
+err_get_name:
+	snd_utimer_put_id(utimer);
+err_take_id:
+	kfree(utimer);
+
+	return err;
+}
+
+static int snd_utimer_ioctl_create(struct file *file,
+				   struct snd_utimer_info __user *_utimer_info)
+{
+	struct snd_utimer *utimer;
+	struct snd_utimer_info *utimer_info;
+	int err;
+
+	utimer_info = memdup_user(_utimer_info, sizeof(*utimer_info));
+	if (IS_ERR(utimer_info))
+		return PTR_ERR(no_free_ptr(utimer_info));
+
+	err = snd_utimer_create(utimer_info, &utimer);
+	if (err < 0) {
+		kfree(utimer_info);
+		return err;
+	}
+
+	utimer_info->id = utimer->id;
+
+	err = copy_to_user(_utimer_info, utimer_info, sizeof(*utimer_info));
+	if (err) {
+		snd_utimer_free(utimer);
+		kfree(utimer_info);
+		return -EFAULT;
+	}
+
+	kfree(utimer_info);
+
+	return anon_inode_getfd(utimer->name, &snd_utimer_fops, utimer, O_RDWR | O_CLOEXEC);
+}
+
+#else
+
+static int snd_utimer_ioctl_create(struct file *file,
+				   struct snd_utimer_info __user *_utimer_info)
+{
+	return -EINVAL;
+}
+
+#endif
+
 static long __snd_timer_user_ioctl(struct file *file, unsigned int cmd,
 				 unsigned long arg, bool compat)
 {
@@ -2053,6 +2264,8 @@  static long __snd_timer_user_ioctl(struct file *file, unsigned int cmd,
 	case SNDRV_TIMER_IOCTL_PAUSE:
 	case SNDRV_TIMER_IOCTL_PAUSE_OLD:
 		return snd_timer_user_pause(file);
+	case SNDRV_TIMER_IOCTL_CREATE:
+		return snd_utimer_ioctl_create(file, argp);
 	}
 	return -ENOTTY;
 }