diff options
-rw-r--r-- | include/sound/asound.h | 10 | ||||
-rw-r--r-- | include/sound/control.h | 16 | ||||
-rw-r--r-- | sound/core/control.c | 139 |
3 files changed, 132 insertions, 33 deletions
diff --git a/include/sound/asound.h b/include/sound/asound.h index 76a20406bd18..c1621c650a9a 100644 --- a/include/sound/asound.h +++ b/include/sound/asound.h | |||
@@ -727,10 +727,15 @@ typedef int __bitwise snd_ctl_elem_iface_t; | |||
727 | #define SNDRV_CTL_ELEM_ACCESS_WRITE (1<<1) | 727 | #define SNDRV_CTL_ELEM_ACCESS_WRITE (1<<1) |
728 | #define SNDRV_CTL_ELEM_ACCESS_READWRITE (SNDRV_CTL_ELEM_ACCESS_READ|SNDRV_CTL_ELEM_ACCESS_WRITE) | 728 | #define SNDRV_CTL_ELEM_ACCESS_READWRITE (SNDRV_CTL_ELEM_ACCESS_READ|SNDRV_CTL_ELEM_ACCESS_WRITE) |
729 | #define SNDRV_CTL_ELEM_ACCESS_VOLATILE (1<<2) /* control value may be changed without a notification */ | 729 | #define SNDRV_CTL_ELEM_ACCESS_VOLATILE (1<<2) /* control value may be changed without a notification */ |
730 | #define SNDRV_CTL_ELEM_ACCESS_TIMESTAMP (1<<2) /* when was control changed */ | 730 | #define SNDRV_CTL_ELEM_ACCESS_TIMESTAMP (1<<3) /* when was control changed */ |
731 | #define SNDRV_CTL_ELEM_ACCESS_TLV_READ (1<<4) /* TLV read is possible */ | ||
732 | #define SNDRV_CTL_ELEM_ACCESS_TLV_WRITE (1<<5) /* TLV write is possible */ | ||
733 | #define SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE (SNDRV_CTL_ELEM_ACCESS_TLV_READ|SNDRV_CTL_ELEM_ACCESS_TLV_WRITE) | ||
734 | #define SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND (1<<6) /* TLV command is possible */ | ||
731 | #define SNDRV_CTL_ELEM_ACCESS_INACTIVE (1<<8) /* control does actually nothing, but may be updated */ | 735 | #define SNDRV_CTL_ELEM_ACCESS_INACTIVE (1<<8) /* control does actually nothing, but may be updated */ |
732 | #define SNDRV_CTL_ELEM_ACCESS_LOCK (1<<9) /* write lock */ | 736 | #define SNDRV_CTL_ELEM_ACCESS_LOCK (1<<9) /* write lock */ |
733 | #define SNDRV_CTL_ELEM_ACCESS_OWNER (1<<10) /* write lock owner */ | 737 | #define SNDRV_CTL_ELEM_ACCESS_OWNER (1<<10) /* write lock owner */ |
738 | #define SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK (1<<28) /* kernel use a TLV callback */ | ||
734 | #define SNDRV_CTL_ELEM_ACCESS_USER (1<<29) /* user space element */ | 739 | #define SNDRV_CTL_ELEM_ACCESS_USER (1<<29) /* user space element */ |
735 | #define SNDRV_CTL_ELEM_ACCESS_DINDIRECT (1<<30) /* indirect access for matrix dimensions in the info structure */ | 740 | #define SNDRV_CTL_ELEM_ACCESS_DINDIRECT (1<<30) /* indirect access for matrix dimensions in the info structure */ |
736 | #define SNDRV_CTL_ELEM_ACCESS_INDIRECT (1<<31) /* indirect access for element value in the value structure */ | 741 | #define SNDRV_CTL_ELEM_ACCESS_INDIRECT (1<<31) /* indirect access for element value in the value structure */ |
@@ -838,6 +843,8 @@ enum { | |||
838 | SNDRV_CTL_IOCTL_ELEM_REPLACE = _IOWR('U', 0x18, struct snd_ctl_elem_info), | 843 | SNDRV_CTL_IOCTL_ELEM_REPLACE = _IOWR('U', 0x18, struct snd_ctl_elem_info), |
839 | SNDRV_CTL_IOCTL_ELEM_REMOVE = _IOWR('U', 0x19, struct snd_ctl_elem_id), | 844 | SNDRV_CTL_IOCTL_ELEM_REMOVE = _IOWR('U', 0x19, struct snd_ctl_elem_id), |
840 | SNDRV_CTL_IOCTL_TLV_READ = _IOWR('U', 0x1a, struct snd_ctl_tlv), | 845 | SNDRV_CTL_IOCTL_TLV_READ = _IOWR('U', 0x1a, struct snd_ctl_tlv), |
846 | SNDRV_CTL_IOCTL_TLV_WRITE = _IOWR('U', 0x1b, struct snd_ctl_tlv), | ||
847 | SNDRV_CTL_IOCTL_TLV_COMMAND = _IOWR('U', 0x1c, struct snd_ctl_tlv), | ||
841 | SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE = _IOWR('U', 0x20, int), | 848 | SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE = _IOWR('U', 0x20, int), |
842 | SNDRV_CTL_IOCTL_HWDEP_INFO = _IOR('U', 0x21, struct snd_hwdep_info), | 849 | SNDRV_CTL_IOCTL_HWDEP_INFO = _IOR('U', 0x21, struct snd_hwdep_info), |
843 | SNDRV_CTL_IOCTL_PCM_NEXT_DEVICE = _IOR('U', 0x30, int), | 850 | SNDRV_CTL_IOCTL_PCM_NEXT_DEVICE = _IOR('U', 0x30, int), |
@@ -862,6 +869,7 @@ enum sndrv_ctl_event_type { | |||
862 | #define SNDRV_CTL_EVENT_MASK_VALUE (1<<0) /* element value was changed */ | 869 | #define SNDRV_CTL_EVENT_MASK_VALUE (1<<0) /* element value was changed */ |
863 | #define SNDRV_CTL_EVENT_MASK_INFO (1<<1) /* element info was changed */ | 870 | #define SNDRV_CTL_EVENT_MASK_INFO (1<<1) /* element info was changed */ |
864 | #define SNDRV_CTL_EVENT_MASK_ADD (1<<2) /* element was added */ | 871 | #define SNDRV_CTL_EVENT_MASK_ADD (1<<2) /* element was added */ |
872 | #define SNDRV_CTL_EVENT_MASK_TLV (1<<3) /* element TLV tree was changed */ | ||
865 | #define SNDRV_CTL_EVENT_MASK_REMOVE (~0U) /* element was removed */ | 873 | #define SNDRV_CTL_EVENT_MASK_REMOVE (~0U) /* element was removed */ |
866 | 874 | ||
867 | struct snd_ctl_event { | 875 | struct snd_ctl_event { |
diff --git a/include/sound/control.h b/include/sound/control.h index a93a58d0e688..e3905c5a0950 100644 --- a/include/sound/control.h +++ b/include/sound/control.h | |||
@@ -30,6 +30,11 @@ struct snd_kcontrol; | |||
30 | typedef int (snd_kcontrol_info_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_info * uinfo); | 30 | typedef int (snd_kcontrol_info_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_info * uinfo); |
31 | typedef int (snd_kcontrol_get_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol); | 31 | typedef int (snd_kcontrol_get_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol); |
32 | typedef int (snd_kcontrol_put_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol); | 32 | typedef int (snd_kcontrol_put_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol); |
33 | typedef int (snd_kcontrol_tlv_rw_t)(struct snd_kcontrol *kcontrol, | ||
34 | int op_flag, /* 0=read,1=write,-1=command */ | ||
35 | unsigned int size, | ||
36 | unsigned int __user *tlv); | ||
37 | |||
33 | 38 | ||
34 | struct snd_kcontrol_new { | 39 | struct snd_kcontrol_new { |
35 | snd_ctl_elem_iface_t iface; /* interface identifier */ | 40 | snd_ctl_elem_iface_t iface; /* interface identifier */ |
@@ -42,7 +47,10 @@ struct snd_kcontrol_new { | |||
42 | snd_kcontrol_info_t *info; | 47 | snd_kcontrol_info_t *info; |
43 | snd_kcontrol_get_t *get; | 48 | snd_kcontrol_get_t *get; |
44 | snd_kcontrol_put_t *put; | 49 | snd_kcontrol_put_t *put; |
45 | unsigned int *tlv; | 50 | union { |
51 | snd_kcontrol_tlv_rw_t *c; | ||
52 | unsigned int *p; | ||
53 | } tlv; | ||
46 | unsigned long private_value; | 54 | unsigned long private_value; |
47 | }; | 55 | }; |
48 | 56 | ||
@@ -59,7 +67,11 @@ struct snd_kcontrol { | |||
59 | snd_kcontrol_info_t *info; | 67 | snd_kcontrol_info_t *info; |
60 | snd_kcontrol_get_t *get; | 68 | snd_kcontrol_get_t *get; |
61 | snd_kcontrol_put_t *put; | 69 | snd_kcontrol_put_t *put; |
62 | unsigned int *tlv; | 70 | snd_kcontrol_tlv_rw_t *tlv_rw; |
71 | union { | ||
72 | snd_kcontrol_tlv_rw_t *c; | ||
73 | unsigned int *p; | ||
74 | } tlv; | ||
63 | unsigned long private_value; | 75 | unsigned long private_value; |
64 | void *private_data; | 76 | void *private_data; |
65 | void (*private_free)(struct snd_kcontrol *kcontrol); | 77 | void (*private_free)(struct snd_kcontrol *kcontrol); |
diff --git a/sound/core/control.c b/sound/core/control.c index f0c7272a2d48..31ad58154c06 100644 --- a/sound/core/control.c +++ b/sound/core/control.c | |||
@@ -236,12 +236,16 @@ struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol, | |||
236 | kctl.id.index = ncontrol->index; | 236 | kctl.id.index = ncontrol->index; |
237 | kctl.count = ncontrol->count ? ncontrol->count : 1; | 237 | kctl.count = ncontrol->count ? ncontrol->count : 1; |
238 | access = ncontrol->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE : | 238 | access = ncontrol->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE : |
239 | (ncontrol->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE|SNDRV_CTL_ELEM_ACCESS_INACTIVE| | 239 | (ncontrol->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE| |
240 | SNDRV_CTL_ELEM_ACCESS_DINDIRECT|SNDRV_CTL_ELEM_ACCESS_INDIRECT)); | 240 | SNDRV_CTL_ELEM_ACCESS_INACTIVE| |
241 | SNDRV_CTL_ELEM_ACCESS_DINDIRECT| | ||
242 | SNDRV_CTL_ELEM_ACCESS_INDIRECT| | ||
243 | SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE| | ||
244 | SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK)); | ||
241 | kctl.info = ncontrol->info; | 245 | kctl.info = ncontrol->info; |
242 | kctl.get = ncontrol->get; | 246 | kctl.get = ncontrol->get; |
243 | kctl.put = ncontrol->put; | 247 | kctl.put = ncontrol->put; |
244 | kctl.tlv = ncontrol->tlv; | 248 | kctl.tlv.p = ncontrol->tlv.p; |
245 | kctl.private_value = ncontrol->private_value; | 249 | kctl.private_value = ncontrol->private_value; |
246 | kctl.private_data = private_data; | 250 | kctl.private_data = private_data; |
247 | return snd_ctl_new(&kctl, access); | 251 | return snd_ctl_new(&kctl, access); |
@@ -883,6 +887,8 @@ struct user_element { | |||
883 | struct snd_ctl_elem_info info; | 887 | struct snd_ctl_elem_info info; |
884 | void *elem_data; /* element data */ | 888 | void *elem_data; /* element data */ |
885 | unsigned long elem_data_size; /* size of element data in bytes */ | 889 | unsigned long elem_data_size; /* size of element data in bytes */ |
890 | void *tlv_data; /* TLV data */ | ||
891 | unsigned long tlv_data_size; /* TLV data size */ | ||
886 | void *priv_data; /* private data (like strings for enumerated type) */ | 892 | void *priv_data; /* private data (like strings for enumerated type) */ |
887 | unsigned long priv_data_size; /* size of private data in bytes */ | 893 | unsigned long priv_data_size; /* size of private data in bytes */ |
888 | }; | 894 | }; |
@@ -917,9 +923,46 @@ static int snd_ctl_elem_user_put(struct snd_kcontrol *kcontrol, | |||
917 | return change; | 923 | return change; |
918 | } | 924 | } |
919 | 925 | ||
926 | static int snd_ctl_elem_user_tlv(struct snd_kcontrol *kcontrol, | ||
927 | int op_flag, | ||
928 | unsigned int size, | ||
929 | unsigned int __user *tlv) | ||
930 | { | ||
931 | struct user_element *ue = kcontrol->private_data; | ||
932 | int change = 0; | ||
933 | void *new_data; | ||
934 | |||
935 | if (op_flag > 0) { | ||
936 | if (size > 1024 * 128) /* sane value */ | ||
937 | return -EINVAL; | ||
938 | new_data = kmalloc(size, GFP_KERNEL); | ||
939 | if (new_data == NULL) | ||
940 | return -ENOMEM; | ||
941 | if (copy_from_user(new_data, tlv, size)) { | ||
942 | kfree(new_data); | ||
943 | return -EFAULT; | ||
944 | } | ||
945 | change = ue->tlv_data_size != size; | ||
946 | if (!change) | ||
947 | change = memcmp(ue->tlv_data, new_data, size); | ||
948 | kfree(ue->tlv_data); | ||
949 | ue->tlv_data = new_data; | ||
950 | ue->tlv_data_size = size; | ||
951 | } else { | ||
952 | if (size < ue->tlv_data_size) | ||
953 | return -ENOSPC; | ||
954 | if (copy_to_user(tlv, ue->tlv_data, ue->tlv_data_size)) | ||
955 | return -EFAULT; | ||
956 | } | ||
957 | return change; | ||
958 | } | ||
959 | |||
920 | static void snd_ctl_elem_user_free(struct snd_kcontrol *kcontrol) | 960 | static void snd_ctl_elem_user_free(struct snd_kcontrol *kcontrol) |
921 | { | 961 | { |
922 | kfree(kcontrol->private_data); | 962 | struct user_element *ue = kcontrol->private_data; |
963 | if (ue->tlv_data) | ||
964 | kfree(ue->tlv_data); | ||
965 | kfree(ue); | ||
923 | } | 966 | } |
924 | 967 | ||
925 | static int snd_ctl_elem_add(struct snd_ctl_file *file, | 968 | static int snd_ctl_elem_add(struct snd_ctl_file *file, |
@@ -938,7 +981,8 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file, | |||
938 | return -EINVAL; | 981 | return -EINVAL; |
939 | access = info->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE : | 982 | access = info->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE : |
940 | (info->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE| | 983 | (info->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE| |
941 | SNDRV_CTL_ELEM_ACCESS_INACTIVE)); | 984 | SNDRV_CTL_ELEM_ACCESS_INACTIVE| |
985 | SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE)); | ||
942 | info->id.numid = 0; | 986 | info->id.numid = 0; |
943 | memset(&kctl, 0, sizeof(kctl)); | 987 | memset(&kctl, 0, sizeof(kctl)); |
944 | down_write(&card->controls_rwsem); | 988 | down_write(&card->controls_rwsem); |
@@ -964,6 +1008,10 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file, | |||
964 | kctl.get = snd_ctl_elem_user_get; | 1008 | kctl.get = snd_ctl_elem_user_get; |
965 | if (access & SNDRV_CTL_ELEM_ACCESS_WRITE) | 1009 | if (access & SNDRV_CTL_ELEM_ACCESS_WRITE) |
966 | kctl.put = snd_ctl_elem_user_put; | 1010 | kctl.put = snd_ctl_elem_user_put; |
1011 | if (access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE) { | ||
1012 | kctl.tlv.c = snd_ctl_elem_user_tlv; | ||
1013 | access |= SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; | ||
1014 | } | ||
967 | switch (info->type) { | 1015 | switch (info->type) { |
968 | case SNDRV_CTL_ELEM_TYPE_BOOLEAN: | 1016 | case SNDRV_CTL_ELEM_TYPE_BOOLEAN: |
969 | private_size = sizeof(char); | 1017 | private_size = sizeof(char); |
@@ -1068,38 +1116,65 @@ static int snd_ctl_subscribe_events(struct snd_ctl_file *file, int __user *ptr) | |||
1068 | return 0; | 1116 | return 0; |
1069 | } | 1117 | } |
1070 | 1118 | ||
1071 | static int snd_ctl_tlv_read(struct snd_card *card, | 1119 | static int snd_ctl_tlv_ioctl(struct snd_ctl_file *file, |
1072 | struct snd_ctl_tlv __user *_tlv) | 1120 | struct snd_ctl_tlv __user *_tlv, |
1121 | int op_flag) | ||
1073 | { | 1122 | { |
1123 | struct snd_card *card = file->card; | ||
1074 | struct snd_ctl_tlv tlv; | 1124 | struct snd_ctl_tlv tlv; |
1075 | struct snd_kcontrol *kctl; | 1125 | struct snd_kcontrol *kctl; |
1126 | struct snd_kcontrol_volatile *vd; | ||
1076 | unsigned int len; | 1127 | unsigned int len; |
1077 | int err = 0; | 1128 | int err = 0; |
1078 | 1129 | ||
1079 | if (copy_from_user(&tlv, _tlv, sizeof(tlv))) | 1130 | if (copy_from_user(&tlv, _tlv, sizeof(tlv))) |
1080 | return -EFAULT; | 1131 | return -EFAULT; |
1081 | if (tlv.length < sizeof(unsigned int) * 3) | 1132 | if (tlv.length < sizeof(unsigned int) * 3) |
1082 | return -EINVAL; | 1133 | return -EINVAL; |
1083 | down_read(&card->controls_rwsem); | 1134 | down_read(&card->controls_rwsem); |
1084 | kctl = snd_ctl_find_numid(card, tlv.numid); | 1135 | kctl = snd_ctl_find_numid(card, tlv.numid); |
1085 | if (kctl == NULL) { | 1136 | if (kctl == NULL) { |
1086 | err = -ENOENT; | 1137 | err = -ENOENT; |
1087 | goto __kctl_end; | 1138 | goto __kctl_end; |
1088 | } | 1139 | } |
1089 | if (kctl->tlv == NULL) { | 1140 | if (kctl->tlv.p == NULL) { |
1090 | err = -ENXIO; | 1141 | err = -ENXIO; |
1091 | goto __kctl_end; | 1142 | goto __kctl_end; |
1092 | } | 1143 | } |
1093 | len = kctl->tlv[1] + 2 * sizeof(unsigned int); | 1144 | vd = &kctl->vd[tlv.numid - kctl->id.numid]; |
1094 | if (tlv.length < len) { | 1145 | if ((op_flag == 0 && (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_READ) == 0) || |
1095 | err = -ENOMEM; | 1146 | (op_flag > 0 && (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_WRITE) == 0) || |
1096 | goto __kctl_end; | 1147 | (op_flag < 0 && (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND) == 0)) { |
1097 | } | 1148 | err = -ENXIO; |
1098 | if (copy_to_user(_tlv->tlv, kctl->tlv, len)) | 1149 | goto __kctl_end; |
1099 | err = -EFAULT; | 1150 | } |
1151 | if (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) { | ||
1152 | if (file && vd->owner != NULL && vd->owner != file) { | ||
1153 | err = -EPERM; | ||
1154 | goto __kctl_end; | ||
1155 | } | ||
1156 | err = kctl->tlv.c(kctl, op_flag, tlv.length, _tlv->tlv); | ||
1157 | if (err > 0) { | ||
1158 | up_read(&card->controls_rwsem); | ||
1159 | snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_TLV, &kctl->id); | ||
1160 | return 0; | ||
1161 | } | ||
1162 | } else { | ||
1163 | if (op_flag) { | ||
1164 | err = -ENXIO; | ||
1165 | goto __kctl_end; | ||
1166 | } | ||
1167 | len = kctl->tlv.p[1] + 2 * sizeof(unsigned int); | ||
1168 | if (tlv.length < len) { | ||
1169 | err = -ENOMEM; | ||
1170 | goto __kctl_end; | ||
1171 | } | ||
1172 | if (copy_to_user(_tlv->tlv, kctl->tlv.p, len)) | ||
1173 | err = -EFAULT; | ||
1174 | } | ||
1100 | __kctl_end: | 1175 | __kctl_end: |
1101 | up_read(&card->controls_rwsem); | 1176 | up_read(&card->controls_rwsem); |
1102 | return err; | 1177 | return err; |
1103 | } | 1178 | } |
1104 | 1179 | ||
1105 | static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg) | 1180 | static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg) |
@@ -1140,8 +1215,12 @@ static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg | |||
1140 | return snd_ctl_elem_remove(ctl, argp); | 1215 | return snd_ctl_elem_remove(ctl, argp); |
1141 | case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS: | 1216 | case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS: |
1142 | return snd_ctl_subscribe_events(ctl, ip); | 1217 | return snd_ctl_subscribe_events(ctl, ip); |
1143 | case SNDRV_CTL_IOCTL_TLV_READ: | 1218 | case SNDRV_CTL_IOCTL_TLV_READ: |
1144 | return snd_ctl_tlv_read(card, argp); | 1219 | return snd_ctl_tlv_ioctl(ctl, argp, 0); |
1220 | case SNDRV_CTL_IOCTL_TLV_WRITE: | ||
1221 | return snd_ctl_tlv_ioctl(ctl, argp, 1); | ||
1222 | case SNDRV_CTL_IOCTL_TLV_COMMAND: | ||
1223 | return snd_ctl_tlv_ioctl(ctl, argp, -1); | ||
1145 | case SNDRV_CTL_IOCTL_POWER: | 1224 | case SNDRV_CTL_IOCTL_POWER: |
1146 | return -ENOPROTOOPT; | 1225 | return -ENOPROTOOPT; |
1147 | case SNDRV_CTL_IOCTL_POWER_STATE: | 1226 | case SNDRV_CTL_IOCTL_POWER_STATE: |