| 1 | // SPDX-License-Identifier: GPL-2.0+ |
| 2 | /* |
| 3 | * virtio-snd: Virtio sound device |
| 4 | * Copyright (C) 2022 OpenSynergy GmbH |
| 5 | */ |
| 6 | #include <sound/control.h> |
| 7 | #include <linux/virtio_config.h> |
| 8 | |
| 9 | #include "virtio_card.h" |
| 10 | |
| 11 | /* Map for converting VirtIO types to ALSA types. */ |
| 12 | static const snd_ctl_elem_type_t g_v2a_type_map[] = { |
| 13 | [VIRTIO_SND_CTL_TYPE_BOOLEAN] = SNDRV_CTL_ELEM_TYPE_BOOLEAN, |
| 14 | [VIRTIO_SND_CTL_TYPE_INTEGER] = SNDRV_CTL_ELEM_TYPE_INTEGER, |
| 15 | [VIRTIO_SND_CTL_TYPE_INTEGER64] = SNDRV_CTL_ELEM_TYPE_INTEGER64, |
| 16 | [VIRTIO_SND_CTL_TYPE_ENUMERATED] = SNDRV_CTL_ELEM_TYPE_ENUMERATED, |
| 17 | [VIRTIO_SND_CTL_TYPE_BYTES] = SNDRV_CTL_ELEM_TYPE_BYTES, |
| 18 | [VIRTIO_SND_CTL_TYPE_IEC958] = SNDRV_CTL_ELEM_TYPE_IEC958 |
| 19 | }; |
| 20 | |
| 21 | /* Map for converting VirtIO access rights to ALSA access rights. */ |
| 22 | static const unsigned int g_v2a_access_map[] = { |
| 23 | [VIRTIO_SND_CTL_ACCESS_READ] = SNDRV_CTL_ELEM_ACCESS_READ, |
| 24 | [VIRTIO_SND_CTL_ACCESS_WRITE] = SNDRV_CTL_ELEM_ACCESS_WRITE, |
| 25 | [VIRTIO_SND_CTL_ACCESS_VOLATILE] = SNDRV_CTL_ELEM_ACCESS_VOLATILE, |
| 26 | [VIRTIO_SND_CTL_ACCESS_INACTIVE] = SNDRV_CTL_ELEM_ACCESS_INACTIVE, |
| 27 | [VIRTIO_SND_CTL_ACCESS_TLV_READ] = SNDRV_CTL_ELEM_ACCESS_TLV_READ, |
| 28 | [VIRTIO_SND_CTL_ACCESS_TLV_WRITE] = SNDRV_CTL_ELEM_ACCESS_TLV_WRITE, |
| 29 | [VIRTIO_SND_CTL_ACCESS_TLV_COMMAND] = SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND |
| 30 | }; |
| 31 | |
| 32 | /* Map for converting VirtIO event masks to ALSA event masks. */ |
| 33 | static const unsigned int g_v2a_mask_map[] = { |
| 34 | [VIRTIO_SND_CTL_EVT_MASK_VALUE] = SNDRV_CTL_EVENT_MASK_VALUE, |
| 35 | [VIRTIO_SND_CTL_EVT_MASK_INFO] = SNDRV_CTL_EVENT_MASK_INFO, |
| 36 | [VIRTIO_SND_CTL_EVT_MASK_TLV] = SNDRV_CTL_EVENT_MASK_TLV |
| 37 | }; |
| 38 | |
| 39 | /** |
| 40 | * virtsnd_kctl_info() - Returns information about the control. |
| 41 | * @kcontrol: ALSA control element. |
| 42 | * @uinfo: Element information. |
| 43 | * |
| 44 | * Context: Process context. |
| 45 | * Return: 0 on success, -errno on failure. |
| 46 | */ |
| 47 | static int virtsnd_kctl_info(struct snd_kcontrol *kcontrol, |
| 48 | struct snd_ctl_elem_info *uinfo) |
| 49 | { |
| 50 | struct virtio_snd *snd = snd_kcontrol_chip(kcontrol); |
| 51 | struct virtio_kctl *kctl = &snd->kctls[kcontrol->private_value]; |
| 52 | struct virtio_snd_ctl_info *kinfo = |
| 53 | &snd->kctl_infos[kcontrol->private_value]; |
| 54 | unsigned int i; |
| 55 | |
| 56 | uinfo->type = g_v2a_type_map[le32_to_cpu(kinfo->type)]; |
| 57 | uinfo->count = le32_to_cpu(kinfo->count); |
| 58 | |
| 59 | switch (uinfo->type) { |
| 60 | case SNDRV_CTL_ELEM_TYPE_INTEGER: |
| 61 | uinfo->value.integer.min = |
| 62 | le32_to_cpu(kinfo->value.integer.min); |
| 63 | uinfo->value.integer.max = |
| 64 | le32_to_cpu(kinfo->value.integer.max); |
| 65 | uinfo->value.integer.step = |
| 66 | le32_to_cpu(kinfo->value.integer.step); |
| 67 | |
| 68 | break; |
| 69 | case SNDRV_CTL_ELEM_TYPE_INTEGER64: |
| 70 | uinfo->value.integer64.min = |
| 71 | le64_to_cpu(kinfo->value.integer64.min); |
| 72 | uinfo->value.integer64.max = |
| 73 | le64_to_cpu(kinfo->value.integer64.max); |
| 74 | uinfo->value.integer64.step = |
| 75 | le64_to_cpu(kinfo->value.integer64.step); |
| 76 | |
| 77 | break; |
| 78 | case SNDRV_CTL_ELEM_TYPE_ENUMERATED: |
| 79 | uinfo->value.enumerated.items = |
| 80 | le32_to_cpu(kinfo->value.enumerated.items); |
| 81 | i = uinfo->value.enumerated.item; |
| 82 | if (i >= uinfo->value.enumerated.items) |
| 83 | return -EINVAL; |
| 84 | |
| 85 | strscpy(uinfo->value.enumerated.name, kctl->items[i].item, |
| 86 | sizeof(uinfo->value.enumerated.name)); |
| 87 | |
| 88 | break; |
| 89 | } |
| 90 | |
| 91 | return 0; |
| 92 | } |
| 93 | |
| 94 | /** |
| 95 | * virtsnd_kctl_get() - Read the value from the control. |
| 96 | * @kcontrol: ALSA control element. |
| 97 | * @uvalue: Element value. |
| 98 | * |
| 99 | * Context: Process context. |
| 100 | * Return: 0 on success, -errno on failure. |
| 101 | */ |
| 102 | static int virtsnd_kctl_get(struct snd_kcontrol *kcontrol, |
| 103 | struct snd_ctl_elem_value *uvalue) |
| 104 | { |
| 105 | struct virtio_snd *snd = snd_kcontrol_chip(kcontrol); |
| 106 | struct virtio_snd_ctl_info *kinfo = |
| 107 | &snd->kctl_infos[kcontrol->private_value]; |
| 108 | unsigned int type = le32_to_cpu(kinfo->type); |
| 109 | unsigned int count = le32_to_cpu(kinfo->count); |
| 110 | struct virtio_snd_msg *msg; |
| 111 | struct virtio_snd_ctl_hdr *hdr; |
| 112 | struct virtio_snd_ctl_value *kvalue; |
| 113 | size_t request_size = sizeof(*hdr); |
| 114 | size_t response_size = sizeof(struct virtio_snd_hdr) + sizeof(*kvalue); |
| 115 | unsigned int i; |
| 116 | int rc; |
| 117 | |
| 118 | msg = virtsnd_ctl_msg_alloc(request_size, response_size, GFP_KERNEL); |
| 119 | if (!msg) |
| 120 | return -ENOMEM; |
| 121 | |
| 122 | virtsnd_ctl_msg_ref(msg); |
| 123 | |
| 124 | hdr = virtsnd_ctl_msg_request(msg); |
| 125 | hdr->hdr.code = cpu_to_le32(VIRTIO_SND_R_CTL_READ); |
| 126 | hdr->control_id = cpu_to_le32(kcontrol->private_value); |
| 127 | |
| 128 | rc = virtsnd_ctl_msg_send_sync(snd, msg); |
| 129 | if (rc) |
| 130 | goto on_failure; |
| 131 | |
| 132 | kvalue = (void *)((u8 *)virtsnd_ctl_msg_response(msg) + |
| 133 | sizeof(struct virtio_snd_hdr)); |
| 134 | |
| 135 | switch (type) { |
| 136 | case VIRTIO_SND_CTL_TYPE_BOOLEAN: |
| 137 | case VIRTIO_SND_CTL_TYPE_INTEGER: |
| 138 | for (i = 0; i < count; ++i) |
| 139 | uvalue->value.integer.value[i] = |
| 140 | le32_to_cpu(kvalue->value.integer[i]); |
| 141 | break; |
| 142 | case VIRTIO_SND_CTL_TYPE_INTEGER64: |
| 143 | for (i = 0; i < count; ++i) |
| 144 | uvalue->value.integer64.value[i] = |
| 145 | le64_to_cpu(kvalue->value.integer64[i]); |
| 146 | break; |
| 147 | case VIRTIO_SND_CTL_TYPE_ENUMERATED: |
| 148 | for (i = 0; i < count; ++i) |
| 149 | uvalue->value.enumerated.item[i] = |
| 150 | le32_to_cpu(kvalue->value.enumerated[i]); |
| 151 | break; |
| 152 | case VIRTIO_SND_CTL_TYPE_BYTES: |
| 153 | memcpy(uvalue->value.bytes.data, kvalue->value.bytes, count); |
| 154 | break; |
| 155 | case VIRTIO_SND_CTL_TYPE_IEC958: |
| 156 | memcpy(&uvalue->value.iec958, &kvalue->value.iec958, |
| 157 | sizeof(uvalue->value.iec958)); |
| 158 | break; |
| 159 | } |
| 160 | |
| 161 | on_failure: |
| 162 | virtsnd_ctl_msg_unref(msg); |
| 163 | |
| 164 | return rc; |
| 165 | } |
| 166 | |
| 167 | /** |
| 168 | * virtsnd_kctl_put() - Write the value to the control. |
| 169 | * @kcontrol: ALSA control element. |
| 170 | * @uvalue: Element value. |
| 171 | * |
| 172 | * Context: Process context. |
| 173 | * Return: 0 on success, -errno on failure. |
| 174 | */ |
| 175 | static int virtsnd_kctl_put(struct snd_kcontrol *kcontrol, |
| 176 | struct snd_ctl_elem_value *uvalue) |
| 177 | { |
| 178 | struct virtio_snd *snd = snd_kcontrol_chip(kcontrol); |
| 179 | struct virtio_snd_ctl_info *kinfo = |
| 180 | &snd->kctl_infos[kcontrol->private_value]; |
| 181 | unsigned int type = le32_to_cpu(kinfo->type); |
| 182 | unsigned int count = le32_to_cpu(kinfo->count); |
| 183 | struct virtio_snd_msg *msg; |
| 184 | struct virtio_snd_ctl_hdr *hdr; |
| 185 | struct virtio_snd_ctl_value *kvalue; |
| 186 | size_t request_size = sizeof(*hdr) + sizeof(*kvalue); |
| 187 | size_t response_size = sizeof(struct virtio_snd_hdr); |
| 188 | unsigned int i; |
| 189 | |
| 190 | msg = virtsnd_ctl_msg_alloc(request_size, response_size, GFP_KERNEL); |
| 191 | if (!msg) |
| 192 | return -ENOMEM; |
| 193 | |
| 194 | hdr = virtsnd_ctl_msg_request(msg); |
| 195 | hdr->hdr.code = cpu_to_le32(VIRTIO_SND_R_CTL_WRITE); |
| 196 | hdr->control_id = cpu_to_le32(kcontrol->private_value); |
| 197 | |
| 198 | kvalue = (void *)((u8 *)hdr + sizeof(*hdr)); |
| 199 | |
| 200 | switch (type) { |
| 201 | case VIRTIO_SND_CTL_TYPE_BOOLEAN: |
| 202 | case VIRTIO_SND_CTL_TYPE_INTEGER: |
| 203 | for (i = 0; i < count; ++i) |
| 204 | kvalue->value.integer[i] = |
| 205 | cpu_to_le32(uvalue->value.integer.value[i]); |
| 206 | break; |
| 207 | case VIRTIO_SND_CTL_TYPE_INTEGER64: |
| 208 | for (i = 0; i < count; ++i) |
| 209 | kvalue->value.integer64[i] = |
| 210 | cpu_to_le64(uvalue->value.integer64.value[i]); |
| 211 | break; |
| 212 | case VIRTIO_SND_CTL_TYPE_ENUMERATED: |
| 213 | for (i = 0; i < count; ++i) |
| 214 | kvalue->value.enumerated[i] = |
| 215 | cpu_to_le32(uvalue->value.enumerated.item[i]); |
| 216 | break; |
| 217 | case VIRTIO_SND_CTL_TYPE_BYTES: |
| 218 | memcpy(kvalue->value.bytes, uvalue->value.bytes.data, count); |
| 219 | break; |
| 220 | case VIRTIO_SND_CTL_TYPE_IEC958: |
| 221 | memcpy(&kvalue->value.iec958, &uvalue->value.iec958, |
| 222 | sizeof(kvalue->value.iec958)); |
| 223 | break; |
| 224 | } |
| 225 | |
| 226 | return virtsnd_ctl_msg_send_sync(snd, msg); |
| 227 | } |
| 228 | |
| 229 | /** |
| 230 | * virtsnd_kctl_tlv_op() - Perform an operation on the control's metadata. |
| 231 | * @kcontrol: ALSA control element. |
| 232 | * @op_flag: Operation code (SNDRV_CTL_TLV_OP_XXX). |
| 233 | * @size: Size of the TLV data in bytes. |
| 234 | * @utlv: TLV data. |
| 235 | * |
| 236 | * Context: Process context. |
| 237 | * Return: 0 on success, -errno on failure. |
| 238 | */ |
| 239 | static int virtsnd_kctl_tlv_op(struct snd_kcontrol *kcontrol, int op_flag, |
| 240 | unsigned int size, unsigned int __user *utlv) |
| 241 | { |
| 242 | struct virtio_snd *snd = snd_kcontrol_chip(kcontrol); |
| 243 | struct virtio_snd_msg *msg; |
| 244 | struct virtio_snd_ctl_hdr *hdr; |
| 245 | unsigned int *tlv; |
| 246 | struct scatterlist sg; |
| 247 | int rc; |
| 248 | |
| 249 | msg = virtsnd_ctl_msg_alloc(request_size: sizeof(*hdr), response_size: sizeof(struct virtio_snd_hdr), |
| 250 | GFP_KERNEL); |
| 251 | if (!msg) |
| 252 | return -ENOMEM; |
| 253 | |
| 254 | tlv = kzalloc(size, GFP_KERNEL); |
| 255 | if (!tlv) { |
| 256 | rc = -ENOMEM; |
| 257 | goto on_msg_unref; |
| 258 | } |
| 259 | |
| 260 | sg_init_one(&sg, tlv, size); |
| 261 | |
| 262 | hdr = virtsnd_ctl_msg_request(msg); |
| 263 | hdr->control_id = cpu_to_le32(kcontrol->private_value); |
| 264 | |
| 265 | switch (op_flag) { |
| 266 | case SNDRV_CTL_TLV_OP_READ: |
| 267 | hdr->hdr.code = cpu_to_le32(VIRTIO_SND_R_CTL_TLV_READ); |
| 268 | |
| 269 | rc = virtsnd_ctl_msg_send(snd, msg, NULL, in_sgs: &sg, nowait: false); |
| 270 | if (!rc) { |
| 271 | if (copy_to_user(to: utlv, from: tlv, n: size)) |
| 272 | rc = -EFAULT; |
| 273 | } |
| 274 | |
| 275 | break; |
| 276 | case SNDRV_CTL_TLV_OP_WRITE: |
| 277 | case SNDRV_CTL_TLV_OP_CMD: |
| 278 | if (op_flag == SNDRV_CTL_TLV_OP_WRITE) |
| 279 | hdr->hdr.code = cpu_to_le32(VIRTIO_SND_R_CTL_TLV_WRITE); |
| 280 | else |
| 281 | hdr->hdr.code = |
| 282 | cpu_to_le32(VIRTIO_SND_R_CTL_TLV_COMMAND); |
| 283 | |
| 284 | if (copy_from_user(to: tlv, from: utlv, n: size)) { |
| 285 | rc = -EFAULT; |
| 286 | goto on_msg_unref; |
| 287 | } else { |
| 288 | rc = virtsnd_ctl_msg_send(snd, msg, out_sgs: &sg, NULL, nowait: false); |
| 289 | } |
| 290 | |
| 291 | break; |
| 292 | default: |
| 293 | rc = -EINVAL; |
| 294 | /* We never get here - we listed all values for op_flag */ |
| 295 | WARN_ON(1); |
| 296 | goto on_msg_unref; |
| 297 | } |
| 298 | kfree(objp: tlv); |
| 299 | return rc; |
| 300 | |
| 301 | on_msg_unref: |
| 302 | virtsnd_ctl_msg_unref(msg); |
| 303 | kfree(objp: tlv); |
| 304 | |
| 305 | return rc; |
| 306 | } |
| 307 | |
| 308 | /** |
| 309 | * virtsnd_kctl_get_enum_items() - Query items for the ENUMERATED element type. |
| 310 | * @snd: VirtIO sound device. |
| 311 | * @cid: Control element ID. |
| 312 | * |
| 313 | * This function is called during initial device initialization. |
| 314 | * |
| 315 | * Context: Any context that permits to sleep. |
| 316 | * Return: 0 on success, -errno on failure. |
| 317 | */ |
| 318 | static int virtsnd_kctl_get_enum_items(struct virtio_snd *snd, unsigned int cid) |
| 319 | { |
| 320 | struct virtio_device *vdev = snd->vdev; |
| 321 | struct virtio_snd_ctl_info *kinfo = &snd->kctl_infos[cid]; |
| 322 | struct virtio_kctl *kctl = &snd->kctls[cid]; |
| 323 | struct virtio_snd_msg *msg; |
| 324 | struct virtio_snd_ctl_hdr *hdr; |
| 325 | unsigned int n = le32_to_cpu(kinfo->value.enumerated.items); |
| 326 | struct scatterlist sg; |
| 327 | |
| 328 | msg = virtsnd_ctl_msg_alloc(request_size: sizeof(*hdr), |
| 329 | response_size: sizeof(struct virtio_snd_hdr), GFP_KERNEL); |
| 330 | if (!msg) |
| 331 | return -ENOMEM; |
| 332 | |
| 333 | kctl->items = devm_kcalloc(dev: &vdev->dev, n, size: sizeof(*kctl->items), |
| 334 | GFP_KERNEL); |
| 335 | if (!kctl->items) { |
| 336 | virtsnd_ctl_msg_unref(msg); |
| 337 | return -ENOMEM; |
| 338 | } |
| 339 | |
| 340 | sg_init_one(&sg, kctl->items, n * sizeof(*kctl->items)); |
| 341 | |
| 342 | hdr = virtsnd_ctl_msg_request(msg); |
| 343 | hdr->hdr.code = cpu_to_le32(VIRTIO_SND_R_CTL_ENUM_ITEMS); |
| 344 | hdr->control_id = cpu_to_le32(cid); |
| 345 | |
| 346 | return virtsnd_ctl_msg_send(snd, msg, NULL, in_sgs: &sg, nowait: false); |
| 347 | } |
| 348 | |
| 349 | /** |
| 350 | * virtsnd_kctl_parse_cfg() - Parse the control element configuration. |
| 351 | * @snd: VirtIO sound device. |
| 352 | * |
| 353 | * This function is called during initial device initialization. |
| 354 | * |
| 355 | * Context: Any context that permits to sleep. |
| 356 | * Return: 0 on success, -errno on failure. |
| 357 | */ |
| 358 | int virtsnd_kctl_parse_cfg(struct virtio_snd *snd) |
| 359 | { |
| 360 | struct virtio_device *vdev = snd->vdev; |
| 361 | u32 i; |
| 362 | int rc; |
| 363 | |
| 364 | virtio_cread_le(vdev, struct virtio_snd_config, controls, |
| 365 | &snd->nkctls); |
| 366 | if (!snd->nkctls) |
| 367 | return 0; |
| 368 | |
| 369 | snd->kctl_infos = devm_kcalloc(dev: &vdev->dev, n: snd->nkctls, |
| 370 | size: sizeof(*snd->kctl_infos), GFP_KERNEL); |
| 371 | if (!snd->kctl_infos) |
| 372 | return -ENOMEM; |
| 373 | |
| 374 | snd->kctls = devm_kcalloc(dev: &vdev->dev, n: snd->nkctls, size: sizeof(*snd->kctls), |
| 375 | GFP_KERNEL); |
| 376 | if (!snd->kctls) |
| 377 | return -ENOMEM; |
| 378 | |
| 379 | rc = virtsnd_ctl_query_info(snd, command: VIRTIO_SND_R_CTL_INFO, start_id: 0, count: snd->nkctls, |
| 380 | size: sizeof(*snd->kctl_infos), info: snd->kctl_infos); |
| 381 | if (rc) |
| 382 | return rc; |
| 383 | |
| 384 | for (i = 0; i < snd->nkctls; ++i) { |
| 385 | struct virtio_snd_ctl_info *kinfo = &snd->kctl_infos[i]; |
| 386 | unsigned int type = le32_to_cpu(kinfo->type); |
| 387 | |
| 388 | if (type == VIRTIO_SND_CTL_TYPE_ENUMERATED) { |
| 389 | rc = virtsnd_kctl_get_enum_items(snd, cid: i); |
| 390 | if (rc) |
| 391 | return rc; |
| 392 | } |
| 393 | } |
| 394 | |
| 395 | return 0; |
| 396 | } |
| 397 | |
| 398 | /** |
| 399 | * virtsnd_kctl_build_devs() - Build ALSA control elements. |
| 400 | * @snd: VirtIO sound device. |
| 401 | * |
| 402 | * Context: Any context that permits to sleep. |
| 403 | * Return: 0 on success, -errno on failure. |
| 404 | */ |
| 405 | int virtsnd_kctl_build_devs(struct virtio_snd *snd) |
| 406 | { |
| 407 | unsigned int cid; |
| 408 | |
| 409 | for (cid = 0; cid < snd->nkctls; ++cid) { |
| 410 | struct virtio_snd_ctl_info *kinfo = &snd->kctl_infos[cid]; |
| 411 | struct virtio_kctl *kctl = &snd->kctls[cid]; |
| 412 | struct snd_kcontrol_new kctl_new; |
| 413 | unsigned int i; |
| 414 | int rc; |
| 415 | |
| 416 | memset(&kctl_new, 0, sizeof(kctl_new)); |
| 417 | |
| 418 | kctl_new.iface = SNDRV_CTL_ELEM_IFACE_MIXER; |
| 419 | kctl_new.name = kinfo->name; |
| 420 | kctl_new.index = le32_to_cpu(kinfo->index); |
| 421 | |
| 422 | for (i = 0; i < ARRAY_SIZE(g_v2a_access_map); ++i) |
| 423 | if (le32_to_cpu(kinfo->access) & (1 << i)) |
| 424 | kctl_new.access |= g_v2a_access_map[i]; |
| 425 | |
| 426 | if (kctl_new.access & (SNDRV_CTL_ELEM_ACCESS_TLV_READ | |
| 427 | SNDRV_CTL_ELEM_ACCESS_TLV_WRITE | |
| 428 | SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND)) { |
| 429 | kctl_new.access |= SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; |
| 430 | kctl_new.tlv.c = virtsnd_kctl_tlv_op; |
| 431 | } |
| 432 | |
| 433 | kctl_new.info = virtsnd_kctl_info; |
| 434 | kctl_new.get = virtsnd_kctl_get; |
| 435 | kctl_new.put = virtsnd_kctl_put; |
| 436 | kctl_new.private_value = cid; |
| 437 | |
| 438 | kctl->kctl = snd_ctl_new1(kcontrolnew: &kctl_new, private_data: snd); |
| 439 | if (!kctl->kctl) |
| 440 | return -ENOMEM; |
| 441 | |
| 442 | rc = snd_ctl_add(card: snd->card, kcontrol: kctl->kctl); |
| 443 | if (rc) |
| 444 | return rc; |
| 445 | } |
| 446 | |
| 447 | return 0; |
| 448 | } |
| 449 | |
| 450 | /** |
| 451 | * virtsnd_kctl_event() - Handle the control element event notification. |
| 452 | * @snd: VirtIO sound device. |
| 453 | * @event: VirtIO sound event. |
| 454 | * |
| 455 | * Context: Interrupt context. |
| 456 | */ |
| 457 | void virtsnd_kctl_event(struct virtio_snd *snd, struct virtio_snd_event *event) |
| 458 | { |
| 459 | struct virtio_snd_ctl_event *kevent = |
| 460 | (struct virtio_snd_ctl_event *)event; |
| 461 | struct virtio_kctl *kctl; |
| 462 | unsigned int cid = le16_to_cpu(kevent->control_id); |
| 463 | unsigned int mask = 0; |
| 464 | unsigned int i; |
| 465 | |
| 466 | if (cid >= snd->nkctls) |
| 467 | return; |
| 468 | |
| 469 | for (i = 0; i < ARRAY_SIZE(g_v2a_mask_map); ++i) |
| 470 | if (le16_to_cpu(kevent->mask) & (1 << i)) |
| 471 | mask |= g_v2a_mask_map[i]; |
| 472 | |
| 473 | |
| 474 | kctl = &snd->kctls[cid]; |
| 475 | |
| 476 | snd_ctl_notify(card: snd->card, mask, id: &kctl->kctl->id); |
| 477 | } |
| 478 | |