diff options
Diffstat (limited to 'sound/core/control.c')
-rw-r--r-- | sound/core/control.c | 84 |
1 files changed, 78 insertions, 6 deletions
diff --git a/sound/core/control.c b/sound/core/control.c index f8c5be464510..978fe1a8e9f0 100644 --- a/sound/core/control.c +++ b/sound/core/control.c | |||
@@ -989,7 +989,6 @@ struct user_element { | |||
989 | void *tlv_data; /* TLV data */ | 989 | void *tlv_data; /* TLV data */ |
990 | unsigned long tlv_data_size; /* TLV data size */ | 990 | unsigned long tlv_data_size; /* TLV data size */ |
991 | void *priv_data; /* private data (like strings for enumerated type) */ | 991 | void *priv_data; /* private data (like strings for enumerated type) */ |
992 | unsigned long priv_data_size; /* size of private data in bytes */ | ||
993 | }; | 992 | }; |
994 | 993 | ||
995 | static int snd_ctl_elem_user_info(struct snd_kcontrol *kcontrol, | 994 | static int snd_ctl_elem_user_info(struct snd_kcontrol *kcontrol, |
@@ -1001,6 +1000,28 @@ static int snd_ctl_elem_user_info(struct snd_kcontrol *kcontrol, | |||
1001 | return 0; | 1000 | return 0; |
1002 | } | 1001 | } |
1003 | 1002 | ||
1003 | static int snd_ctl_elem_user_enum_info(struct snd_kcontrol *kcontrol, | ||
1004 | struct snd_ctl_elem_info *uinfo) | ||
1005 | { | ||
1006 | struct user_element *ue = kcontrol->private_data; | ||
1007 | const char *names; | ||
1008 | unsigned int item; | ||
1009 | |||
1010 | item = uinfo->value.enumerated.item; | ||
1011 | |||
1012 | *uinfo = ue->info; | ||
1013 | |||
1014 | item = min(item, uinfo->value.enumerated.items - 1); | ||
1015 | uinfo->value.enumerated.item = item; | ||
1016 | |||
1017 | names = ue->priv_data; | ||
1018 | for (; item > 0; --item) | ||
1019 | names += strlen(names) + 1; | ||
1020 | strcpy(uinfo->value.enumerated.name, names); | ||
1021 | |||
1022 | return 0; | ||
1023 | } | ||
1024 | |||
1004 | static int snd_ctl_elem_user_get(struct snd_kcontrol *kcontrol, | 1025 | static int snd_ctl_elem_user_get(struct snd_kcontrol *kcontrol, |
1005 | struct snd_ctl_elem_value *ucontrol) | 1026 | struct snd_ctl_elem_value *ucontrol) |
1006 | { | 1027 | { |
@@ -1055,11 +1076,46 @@ static int snd_ctl_elem_user_tlv(struct snd_kcontrol *kcontrol, | |||
1055 | return change; | 1076 | return change; |
1056 | } | 1077 | } |
1057 | 1078 | ||
1079 | static int snd_ctl_elem_init_enum_names(struct user_element *ue) | ||
1080 | { | ||
1081 | char *names, *p; | ||
1082 | size_t buf_len, name_len; | ||
1083 | unsigned int i; | ||
1084 | |||
1085 | if (ue->info.value.enumerated.names_length > 64 * 1024) | ||
1086 | return -EINVAL; | ||
1087 | |||
1088 | names = memdup_user( | ||
1089 | (const void __user *)ue->info.value.enumerated.names_ptr, | ||
1090 | ue->info.value.enumerated.names_length); | ||
1091 | if (IS_ERR(names)) | ||
1092 | return PTR_ERR(names); | ||
1093 | |||
1094 | /* check that there are enough valid names */ | ||
1095 | buf_len = ue->info.value.enumerated.names_length; | ||
1096 | p = names; | ||
1097 | for (i = 0; i < ue->info.value.enumerated.items; ++i) { | ||
1098 | name_len = strnlen(p, buf_len); | ||
1099 | if (name_len == 0 || name_len >= 64 || name_len == buf_len) { | ||
1100 | kfree(names); | ||
1101 | return -EINVAL; | ||
1102 | } | ||
1103 | p += name_len + 1; | ||
1104 | buf_len -= name_len + 1; | ||
1105 | } | ||
1106 | |||
1107 | ue->priv_data = names; | ||
1108 | ue->info.value.enumerated.names_ptr = 0; | ||
1109 | |||
1110 | return 0; | ||
1111 | } | ||
1112 | |||
1058 | static void snd_ctl_elem_user_free(struct snd_kcontrol *kcontrol) | 1113 | static void snd_ctl_elem_user_free(struct snd_kcontrol *kcontrol) |
1059 | { | 1114 | { |
1060 | struct user_element *ue = kcontrol->private_data; | 1115 | struct user_element *ue = kcontrol->private_data; |
1061 | if (ue->tlv_data) | 1116 | |
1062 | kfree(ue->tlv_data); | 1117 | kfree(ue->tlv_data); |
1118 | kfree(ue->priv_data); | ||
1063 | kfree(ue); | 1119 | kfree(ue); |
1064 | } | 1120 | } |
1065 | 1121 | ||
@@ -1072,8 +1128,8 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file, | |||
1072 | long private_size; | 1128 | long private_size; |
1073 | struct user_element *ue; | 1129 | struct user_element *ue; |
1074 | int idx, err; | 1130 | int idx, err; |
1075 | 1131 | ||
1076 | if (card->user_ctl_count >= MAX_USER_CONTROLS) | 1132 | if (!replace && card->user_ctl_count >= MAX_USER_CONTROLS) |
1077 | return -ENOMEM; | 1133 | return -ENOMEM; |
1078 | if (info->count < 1) | 1134 | if (info->count < 1) |
1079 | return -EINVAL; | 1135 | return -EINVAL; |
@@ -1101,7 +1157,10 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file, | |||
1101 | memcpy(&kctl.id, &info->id, sizeof(info->id)); | 1157 | memcpy(&kctl.id, &info->id, sizeof(info->id)); |
1102 | kctl.count = info->owner ? info->owner : 1; | 1158 | kctl.count = info->owner ? info->owner : 1; |
1103 | access |= SNDRV_CTL_ELEM_ACCESS_USER; | 1159 | access |= SNDRV_CTL_ELEM_ACCESS_USER; |
1104 | kctl.info = snd_ctl_elem_user_info; | 1160 | if (info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) |
1161 | kctl.info = snd_ctl_elem_user_enum_info; | ||
1162 | else | ||
1163 | kctl.info = snd_ctl_elem_user_info; | ||
1105 | if (access & SNDRV_CTL_ELEM_ACCESS_READ) | 1164 | if (access & SNDRV_CTL_ELEM_ACCESS_READ) |
1106 | kctl.get = snd_ctl_elem_user_get; | 1165 | kctl.get = snd_ctl_elem_user_get; |
1107 | if (access & SNDRV_CTL_ELEM_ACCESS_WRITE) | 1166 | if (access & SNDRV_CTL_ELEM_ACCESS_WRITE) |
@@ -1122,6 +1181,11 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file, | |||
1122 | if (info->count > 64) | 1181 | if (info->count > 64) |
1123 | return -EINVAL; | 1182 | return -EINVAL; |
1124 | break; | 1183 | break; |
1184 | case SNDRV_CTL_ELEM_TYPE_ENUMERATED: | ||
1185 | private_size = sizeof(unsigned int); | ||
1186 | if (info->count > 128 || info->value.enumerated.items == 0) | ||
1187 | return -EINVAL; | ||
1188 | break; | ||
1125 | case SNDRV_CTL_ELEM_TYPE_BYTES: | 1189 | case SNDRV_CTL_ELEM_TYPE_BYTES: |
1126 | private_size = sizeof(unsigned char); | 1190 | private_size = sizeof(unsigned char); |
1127 | if (info->count > 512) | 1191 | if (info->count > 512) |
@@ -1143,9 +1207,17 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file, | |||
1143 | ue->info.access = 0; | 1207 | ue->info.access = 0; |
1144 | ue->elem_data = (char *)ue + sizeof(*ue); | 1208 | ue->elem_data = (char *)ue + sizeof(*ue); |
1145 | ue->elem_data_size = private_size; | 1209 | ue->elem_data_size = private_size; |
1210 | if (ue->info.type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) { | ||
1211 | err = snd_ctl_elem_init_enum_names(ue); | ||
1212 | if (err < 0) { | ||
1213 | kfree(ue); | ||
1214 | return err; | ||
1215 | } | ||
1216 | } | ||
1146 | kctl.private_free = snd_ctl_elem_user_free; | 1217 | kctl.private_free = snd_ctl_elem_user_free; |
1147 | _kctl = snd_ctl_new(&kctl, access); | 1218 | _kctl = snd_ctl_new(&kctl, access); |
1148 | if (_kctl == NULL) { | 1219 | if (_kctl == NULL) { |
1220 | kfree(ue->priv_data); | ||
1149 | kfree(ue); | 1221 | kfree(ue); |
1150 | return -ENOMEM; | 1222 | return -ENOMEM; |
1151 | } | 1223 | } |