diff options
author | Clemens Ladisch <clemens@ladisch.de> | 2011-09-04 16:14:15 -0400 |
---|---|---|
committer | Clemens Ladisch <clemens@ladisch.de> | 2013-10-20 16:07:57 -0400 |
commit | 0c29c9180fe14b0abb4bfc68b37dda66254689b3 (patch) | |
tree | 0f47ad91b1d042b6ac851057c3d95485a5fd8cc5 /sound | |
parent | 4ed31f20bb5bb90f003c91734c6b9d18169ae27e (diff) |
ALSA: dice: implement hwdep device
Implement the hwdep locking and notification mechanisms.
Signed-off-by: Clemens Ladisch <clemens@ladisch.de>
Diffstat (limited to 'sound')
-rw-r--r-- | sound/firewire/dice.c | 225 |
1 files changed, 203 insertions, 22 deletions
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index d0575a96ea70..7225878a09cc 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c | |||
@@ -5,6 +5,7 @@ | |||
5 | * Licensed under the terms of the GNU General Public License, version 2. | 5 | * Licensed under the terms of the GNU General Public License, version 2. |
6 | */ | 6 | */ |
7 | 7 | ||
8 | #include <linux/compat.h> | ||
8 | #include <linux/delay.h> | 9 | #include <linux/delay.h> |
9 | #include <linux/device.h> | 10 | #include <linux/device.h> |
10 | #include <linux/firewire.h> | 11 | #include <linux/firewire.h> |
@@ -13,8 +14,11 @@ | |||
13 | #include <linux/mod_devicetable.h> | 14 | #include <linux/mod_devicetable.h> |
14 | #include <linux/mutex.h> | 15 | #include <linux/mutex.h> |
15 | #include <linux/slab.h> | 16 | #include <linux/slab.h> |
17 | #include <linux/spinlock.h> | ||
18 | #include <linux/wait.h> | ||
16 | #include <sound/control.h> | 19 | #include <sound/control.h> |
17 | #include <sound/core.h> | 20 | #include <sound/core.h> |
21 | #include <sound/firewire.h> | ||
18 | #include <sound/hwdep.h> | 22 | #include <sound/hwdep.h> |
19 | #include <sound/initval.h> | 23 | #include <sound/initval.h> |
20 | #include <sound/pcm.h> | 24 | #include <sound/pcm.h> |
@@ -233,13 +237,18 @@ | |||
233 | struct dice { | 237 | struct dice { |
234 | struct snd_card *card; | 238 | struct snd_card *card; |
235 | struct fw_unit *unit; | 239 | struct fw_unit *unit; |
240 | spinlock_t lock; | ||
236 | struct mutex mutex; | 241 | struct mutex mutex; |
237 | unsigned int global_offset; | 242 | unsigned int global_offset; |
238 | unsigned int rx_offset; | 243 | unsigned int rx_offset; |
239 | struct fw_address_handler notification_handler; | 244 | struct fw_address_handler notification_handler; |
240 | int owner_generation; | 245 | int owner_generation; |
246 | int dev_lock_count; /* > 0 driver, < 0 userspace */ | ||
247 | bool dev_lock_changed; | ||
241 | bool global_enabled; | 248 | bool global_enabled; |
242 | bool stream_running; | 249 | bool stream_running; |
250 | wait_queue_head_t hwdep_wait; | ||
251 | u32 notification_bits; | ||
243 | struct snd_pcm_substream *pcm; | 252 | struct snd_pcm_substream *pcm; |
244 | struct fw_iso_resources resources; | 253 | struct fw_iso_resources resources; |
245 | struct amdtp_out_stream stream; | 254 | struct amdtp_out_stream stream; |
@@ -259,6 +268,47 @@ static const unsigned int dice_rates[] = { | |||
259 | [6] = 192000, | 268 | [6] = 192000, |
260 | }; | 269 | }; |
261 | 270 | ||
271 | static void dice_lock_changed(struct dice *dice) | ||
272 | { | ||
273 | dice->dev_lock_changed = true; | ||
274 | wake_up(&dice->hwdep_wait); | ||
275 | } | ||
276 | |||
277 | static int dice_try_lock(struct dice *dice) | ||
278 | { | ||
279 | int err; | ||
280 | |||
281 | spin_lock_irq(&dice->lock); | ||
282 | |||
283 | if (dice->dev_lock_count < 0) { | ||
284 | err = -EBUSY; | ||
285 | goto out; | ||
286 | } | ||
287 | |||
288 | if (dice->dev_lock_count++ == 0) | ||
289 | dice_lock_changed(dice); | ||
290 | err = 0; | ||
291 | |||
292 | out: | ||
293 | spin_unlock_irq(&dice->lock); | ||
294 | |||
295 | return err; | ||
296 | } | ||
297 | |||
298 | static void dice_unlock(struct dice *dice) | ||
299 | { | ||
300 | spin_lock_irq(&dice->lock); | ||
301 | |||
302 | if (WARN_ON(dice->dev_lock_count <= 0)) | ||
303 | goto out; | ||
304 | |||
305 | if (--dice->dev_lock_count == 0) | ||
306 | dice_lock_changed(dice); | ||
307 | |||
308 | out: | ||
309 | spin_unlock_irq(&dice->lock); | ||
310 | } | ||
311 | |||
262 | static inline u64 global_address(struct dice *dice, unsigned int offset) | 312 | static inline u64 global_address(struct dice *dice, unsigned int offset) |
263 | { | 313 | { |
264 | return DICE_PRIVATE_SPACE + dice->global_offset + offset; | 314 | return DICE_PRIVATE_SPACE + dice->global_offset + offset; |
@@ -496,6 +546,7 @@ static void dice_notification(struct fw_card *card, struct fw_request *request, | |||
496 | void *data, size_t length, void *callback_data) | 546 | void *data, size_t length, void *callback_data) |
497 | { | 547 | { |
498 | struct dice *dice = callback_data; | 548 | struct dice *dice = callback_data; |
549 | unsigned long flags; | ||
499 | 550 | ||
500 | if (tcode != TCODE_WRITE_QUADLET_REQUEST) { | 551 | if (tcode != TCODE_WRITE_QUADLET_REQUEST) { |
501 | fw_send_response(card, request, RCODE_TYPE_ERROR); | 552 | fw_send_response(card, request, RCODE_TYPE_ERROR); |
@@ -505,9 +556,11 @@ static void dice_notification(struct fw_card *card, struct fw_request *request, | |||
505 | fw_send_response(card, request, RCODE_ADDRESS_ERROR); | 556 | fw_send_response(card, request, RCODE_ADDRESS_ERROR); |
506 | return; | 557 | return; |
507 | } | 558 | } |
508 | dev_dbg(&dice->unit->device, | 559 | spin_lock_irqsave(&dice->lock, flags); |
509 | "notification: %08x\n", be32_to_cpup(data)); | 560 | dice->notification_bits |= be32_to_cpup(data); |
561 | spin_unlock_irqrestore(&dice->lock, flags); | ||
510 | fw_send_response(card, request, RCODE_COMPLETE); | 562 | fw_send_response(card, request, RCODE_COMPLETE); |
563 | wake_up(&dice->hwdep_wait); | ||
511 | } | 564 | } |
512 | 565 | ||
513 | static int dice_open(struct snd_pcm_substream *substream) | 566 | static int dice_open(struct snd_pcm_substream *substream) |
@@ -531,26 +584,32 @@ static int dice_open(struct snd_pcm_substream *substream) | |||
531 | unsigned int rate; | 584 | unsigned int rate; |
532 | int err; | 585 | int err; |
533 | 586 | ||
587 | err = dice_try_lock(dice); | ||
588 | if (err < 0) | ||
589 | goto error; | ||
590 | |||
534 | err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, | 591 | err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, |
535 | global_address(dice, GLOBAL_CLOCK_SELECT), | 592 | global_address(dice, GLOBAL_CLOCK_SELECT), |
536 | &clock_sel, 4); | 593 | &clock_sel, 4); |
537 | if (err < 0) | 594 | if (err < 0) |
538 | return err; | 595 | goto err_lock; |
539 | rate = (be32_to_cpu(clock_sel) & CLOCK_RATE_MASK) >> CLOCK_RATE_SHIFT; | 596 | rate = (be32_to_cpu(clock_sel) & CLOCK_RATE_MASK) >> CLOCK_RATE_SHIFT; |
540 | if (rate >= ARRAY_SIZE(dice_rates)) | 597 | if (rate >= ARRAY_SIZE(dice_rates)) { |
541 | return -ENXIO; | 598 | err = -ENXIO; |
599 | goto err_lock; | ||
600 | } | ||
542 | rate = dice_rates[rate]; | 601 | rate = dice_rates[rate]; |
543 | 602 | ||
544 | err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, | 603 | err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, |
545 | rx_address(dice, RX_NUMBER_AUDIO), | 604 | rx_address(dice, RX_NUMBER_AUDIO), |
546 | &number_audio, 4); | 605 | &number_audio, 4); |
547 | if (err < 0) | 606 | if (err < 0) |
548 | return err; | 607 | goto err_lock; |
549 | err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, | 608 | err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, |
550 | rx_address(dice, RX_NUMBER_MIDI), | 609 | rx_address(dice, RX_NUMBER_MIDI), |
551 | &number_midi, 4); | 610 | &number_midi, 4); |
552 | if (err < 0) | 611 | if (err < 0) |
553 | return err; | 612 | goto err_lock; |
554 | 613 | ||
555 | runtime->hw = hardware; | 614 | runtime->hw = hardware; |
556 | 615 | ||
@@ -568,17 +627,26 @@ static int dice_open(struct snd_pcm_substream *substream) | |||
568 | SNDRV_PCM_HW_PARAM_PERIOD_TIME, | 627 | SNDRV_PCM_HW_PARAM_PERIOD_TIME, |
569 | 5000, 8192000); | 628 | 5000, 8192000); |
570 | if (err < 0) | 629 | if (err < 0) |
571 | return err; | 630 | goto err_lock; |
572 | 631 | ||
573 | err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); | 632 | err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); |
574 | if (err < 0) | 633 | if (err < 0) |
575 | return err; | 634 | goto err_lock; |
576 | 635 | ||
577 | return 0; | 636 | return 0; |
637 | |||
638 | err_lock: | ||
639 | dice_unlock(dice); | ||
640 | error: | ||
641 | return err; | ||
578 | } | 642 | } |
579 | 643 | ||
580 | static int dice_close(struct snd_pcm_substream *substream) | 644 | static int dice_close(struct snd_pcm_substream *substream) |
581 | { | 645 | { |
646 | struct dice *dice = substream->private_data; | ||
647 | |||
648 | dice_unlock(dice); | ||
649 | |||
582 | return 0; | 650 | return 0; |
583 | } | 651 | } |
584 | 652 | ||
@@ -783,45 +851,156 @@ static int dice_create_pcm(struct dice *dice) | |||
783 | return 0; | 851 | return 0; |
784 | } | 852 | } |
785 | 853 | ||
786 | // TODO: implement these | ||
787 | |||
788 | static long dice_hwdep_read(struct snd_hwdep *hwdep, char __user *buf, | 854 | static long dice_hwdep_read(struct snd_hwdep *hwdep, char __user *buf, |
789 | long count, loff_t *offset) | 855 | long count, loff_t *offset) |
790 | { | 856 | { |
791 | return -EIO; | 857 | struct dice *dice = hwdep->private_data; |
858 | DEFINE_WAIT(wait); | ||
859 | union snd_firewire_event event; | ||
860 | |||
861 | spin_lock_irq(&dice->lock); | ||
862 | |||
863 | while (!dice->dev_lock_changed && dice->notification_bits == 0) { | ||
864 | prepare_to_wait(&dice->hwdep_wait, &wait, TASK_INTERRUPTIBLE); | ||
865 | spin_unlock_irq(&dice->lock); | ||
866 | schedule(); | ||
867 | finish_wait(&dice->hwdep_wait, &wait); | ||
868 | if (signal_pending(current)) | ||
869 | return -ERESTARTSYS; | ||
870 | spin_lock_irq(&dice->lock); | ||
871 | } | ||
872 | |||
873 | memset(&event, 0, sizeof(event)); | ||
874 | if (dice->dev_lock_changed) { | ||
875 | event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; | ||
876 | event.lock_status.status = dice->dev_lock_count > 0; | ||
877 | dice->dev_lock_changed = false; | ||
878 | |||
879 | count = min(count, (long)sizeof(event.lock_status)); | ||
880 | } else { | ||
881 | event.dice_notification.type = SNDRV_FIREWIRE_EVENT_DICE_NOTIFICATION; | ||
882 | event.dice_notification.notification = dice->notification_bits; | ||
883 | dice->notification_bits = 0; | ||
884 | |||
885 | count = min(count, (long)sizeof(event.dice_notification)); | ||
886 | } | ||
887 | |||
888 | spin_unlock_irq(&dice->lock); | ||
889 | |||
890 | if (copy_to_user(buf, &event, count)) | ||
891 | return -EFAULT; | ||
892 | |||
893 | return count; | ||
792 | } | 894 | } |
793 | 895 | ||
794 | static int dice_hwdep_open(struct snd_hwdep *hwdep, struct file *file) | 896 | static unsigned int dice_hwdep_poll(struct snd_hwdep *hwdep, struct file *file, |
897 | poll_table *wait) | ||
795 | { | 898 | { |
796 | return -EIO; | 899 | struct dice *dice = hwdep->private_data; |
900 | unsigned int events; | ||
901 | |||
902 | poll_wait(file, &dice->hwdep_wait, wait); | ||
903 | |||
904 | spin_lock_irq(&dice->lock); | ||
905 | if (dice->dev_lock_changed || dice->notification_bits != 0) | ||
906 | events = POLLIN | POLLRDNORM; | ||
907 | else | ||
908 | events = 0; | ||
909 | spin_unlock_irq(&dice->lock); | ||
910 | |||
911 | return events; | ||
797 | } | 912 | } |
798 | 913 | ||
799 | static int dice_hwdep_release(struct snd_hwdep *hwdep, struct file *file) | 914 | static int dice_hwdep_get_info(struct dice *dice, void __user *arg) |
800 | { | 915 | { |
916 | struct fw_device *dev = fw_parent_device(dice->unit); | ||
917 | struct snd_firewire_get_info info; | ||
918 | |||
919 | memset(&info, 0, sizeof(info)); | ||
920 | info.type = SNDRV_FIREWIRE_TYPE_DICE; | ||
921 | info.card = dev->card->index; | ||
922 | *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); | ||
923 | *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); | ||
924 | strlcpy(info.device_name, dev_name(&dev->device), | ||
925 | sizeof(info.device_name)); | ||
926 | |||
927 | if (copy_to_user(arg, &info, sizeof(info))) | ||
928 | return -EFAULT; | ||
929 | |||
801 | return 0; | 930 | return 0; |
802 | } | 931 | } |
803 | 932 | ||
804 | static unsigned int dice_hwdep_poll(struct snd_hwdep *hwdep, struct file *file, | 933 | static int dice_hwdep_lock(struct dice *dice) |
805 | poll_table *wait) | 934 | { |
935 | int err; | ||
936 | |||
937 | spin_lock_irq(&dice->lock); | ||
938 | |||
939 | if (dice->dev_lock_count == 0) { | ||
940 | dice->dev_lock_count = -1; | ||
941 | err = 0; | ||
942 | } else { | ||
943 | err = -EBUSY; | ||
944 | } | ||
945 | |||
946 | spin_unlock_irq(&dice->lock); | ||
947 | |||
948 | return err; | ||
949 | } | ||
950 | |||
951 | static int dice_hwdep_unlock(struct dice *dice) | ||
806 | { | 952 | { |
807 | return POLLERR | POLLHUP; | 953 | int err; |
954 | |||
955 | spin_lock_irq(&dice->lock); | ||
956 | |||
957 | if (dice->dev_lock_count == -1) { | ||
958 | dice->dev_lock_count = 0; | ||
959 | err = 0; | ||
960 | } else { | ||
961 | err = -EBADFD; | ||
962 | } | ||
963 | |||
964 | spin_unlock_irq(&dice->lock); | ||
965 | |||
966 | return err; | ||
808 | } | 967 | } |
809 | 968 | ||
810 | static int dice_hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, | 969 | static int dice_hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, |
811 | unsigned int cmd, unsigned long arg) | 970 | unsigned int cmd, unsigned long arg) |
812 | { | 971 | { |
813 | return -EIO; | 972 | struct dice *dice = hwdep->private_data; |
973 | |||
974 | switch (cmd) { | ||
975 | case SNDRV_FIREWIRE_IOCTL_GET_INFO: | ||
976 | return dice_hwdep_get_info(dice, (void __user *)arg); | ||
977 | case SNDRV_FIREWIRE_IOCTL_LOCK: | ||
978 | return dice_hwdep_lock(dice); | ||
979 | case SNDRV_FIREWIRE_IOCTL_UNLOCK: | ||
980 | return dice_hwdep_unlock(dice); | ||
981 | default: | ||
982 | return -ENOIOCTLCMD; | ||
983 | } | ||
984 | } | ||
985 | |||
986 | #ifdef CONFIG_COMPAT | ||
987 | static int dice_hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, | ||
988 | unsigned int cmd, unsigned long arg) | ||
989 | { | ||
990 | return dice_hwdep_ioctl(hwdep, file, cmd, | ||
991 | (unsigned long)compat_ptr(arg)); | ||
814 | } | 992 | } |
993 | #else | ||
994 | #define dice_hwdep_compat_ioctl NULL | ||
995 | #endif | ||
815 | 996 | ||
816 | static int dice_create_hwdep(struct dice *dice) | 997 | static int dice_create_hwdep(struct dice *dice) |
817 | { | 998 | { |
818 | static const struct snd_hwdep_ops ops = { | 999 | static const struct snd_hwdep_ops ops = { |
819 | .read = dice_hwdep_read, | 1000 | .read = dice_hwdep_read, |
820 | .open = dice_hwdep_open, | ||
821 | .release = dice_hwdep_release, | ||
822 | .poll = dice_hwdep_poll, | 1001 | .poll = dice_hwdep_poll, |
823 | .ioctl = dice_hwdep_ioctl, | 1002 | .ioctl = dice_hwdep_ioctl, |
824 | .ioctl_compat = dice_hwdep_ioctl, | 1003 | .ioctl_compat = dice_hwdep_compat_ioctl, |
825 | }; | 1004 | }; |
826 | struct snd_hwdep *hwdep; | 1005 | struct snd_hwdep *hwdep; |
827 | int err; | 1006 | int err; |
@@ -922,8 +1101,10 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) | |||
922 | 1101 | ||
923 | dice = card->private_data; | 1102 | dice = card->private_data; |
924 | dice->card = card; | 1103 | dice->card = card; |
1104 | spin_lock_init(&dice->lock); | ||
925 | mutex_init(&dice->mutex); | 1105 | mutex_init(&dice->mutex); |
926 | dice->unit = unit; | 1106 | dice->unit = unit; |
1107 | init_waitqueue_head(&dice->hwdep_wait); | ||
927 | 1108 | ||
928 | err = dice_init_offsets(dice); | 1109 | err = dice_init_offsets(dice); |
929 | if (err < 0) | 1110 | if (err < 0) |