diff options
author | Jaroslav Kysela <perex@suse.cz> | 2006-07-05 11:34:51 -0400 |
---|---|---|
committer | Jaroslav Kysela <perex@suse.cz> | 2006-09-23 04:37:26 -0400 |
commit | 8aa9b586e42099817163aba01d925c2660c4dbbe (patch) | |
tree | b70172eafcb672074fda1858c7a9c5779a1132f8 /sound/core | |
parent | 6bbe13ecbbce4415a5a7959b3bc35b18313025e0 (diff) |
[ALSA] Control API - more robust TLV implementation
- added callback option
- added READ/WRITE/COMMAND flags to access member
- added WRITE/COMMAND ioctls
- added SNDRV_CTL_EVENT_MASK_TLV for TLV change notifications
- added TLV support to ELEM_ADD ioctl
Signed-off-by: Jaroslav Kysela <perex@suse.cz>
Diffstat (limited to 'sound/core')
-rw-r--r-- | sound/core/control.c | 139 |
1 files changed, 109 insertions, 30 deletions
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: |