// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbaudio.h" #include "card.h" #include "helper.h" #include "pcm.h" #include "usb_audio_qmi_v01.h" #define BUS_INTERVAL_FULL_SPEED 1000 /* in us */ #define BUS_INTERVAL_HIGHSPEED_AND_ABOVE 125 /* in us */ #define MAX_BINTERVAL_ISOC_EP 16 #define DEV_RELEASE_WAIT_TIMEOUT 10000 /* in ms */ #define SND_PCM_CARD_NUM_MASK 0xffff0000 #define SND_PCM_DEV_NUM_MASK 0xff00 #define SND_PCM_STREAM_DIRECTION 0xff #define PREPEND_SID_TO_IOVA(iova, sid) (u64)(((u64)(iova)) | \ (((u64)sid) << 32)) /* event ring iova base address */ #define IOVA_BASE 0x1000 #define IOVA_XFER_RING_BASE (IOVA_BASE + PAGE_SIZE * (SNDRV_CARDS + 1)) #define IOVA_XFER_BUF_BASE (IOVA_XFER_RING_BASE + PAGE_SIZE * SNDRV_CARDS * 32) #define IOVA_XFER_RING_MAX (IOVA_XFER_BUF_BASE - PAGE_SIZE) #define IOVA_XFER_BUF_MAX (0xfffff000 - PAGE_SIZE) #define MAX_XFER_BUFF_LEN (24 * PAGE_SIZE) struct iova_info { struct list_head list; unsigned long start_iova; size_t size; bool in_use; }; struct intf_info { unsigned long data_xfer_ring_va; size_t data_xfer_ring_size; unsigned long sync_xfer_ring_va; size_t sync_xfer_ring_size; unsigned long xfer_buf_va; size_t xfer_buf_size; phys_addr_t xfer_buf_pa; unsigned int data_ep_pipe; unsigned int sync_ep_pipe; u8 *xfer_buf; u8 intf_num; u8 pcm_card_num; u8 pcm_dev_num; u8 direction; bool in_use; }; struct uaudio_dev { struct usb_device *udev; /* audio control interface */ struct usb_host_interface *ctrl_intf; unsigned int card_num; unsigned int usb_core_id; atomic_t in_use; struct kref kref; wait_queue_head_t disconnect_wq; /* interface specific */ int num_intf; struct intf_info *info; }; static struct uaudio_dev uadev[SNDRV_CARDS]; struct uaudio_qmi_dev { struct device *dev; u32 sid; u32 intr_num; struct iommu_domain *domain; /* list to keep track of available iova */ struct list_head xfer_ring_list; size_t xfer_ring_iova_size; unsigned long curr_xfer_ring_iova; struct list_head xfer_buf_list; size_t xfer_buf_iova_size; unsigned long curr_xfer_buf_iova; /* bit fields representing pcm card enabled */ unsigned long card_slot; /* indicate event ring mapped or not */ bool er_mapped; }; static struct uaudio_qmi_dev *uaudio_qdev; struct uaudio_qmi_svc { struct qmi_handle *uaudio_svc_hdl; struct work_struct qmi_disconnect_work; struct workqueue_struct *uaudio_wq; struct sockaddr_qrtr client_sq; bool client_connected; void *uaudio_ipc_log; }; static struct uaudio_qmi_svc *uaudio_svc; static void handle_uaudio_stream_req(struct qmi_handle *handle, struct sockaddr_qrtr *sq, struct qmi_txn *txn, const void *decoded_msg); static struct qmi_msg_handler uaudio_stream_req_handlers = { .type = QMI_REQUEST, .msg_id = QMI_UAUDIO_STREAM_REQ_V01, .ei = qmi_uaudio_stream_req_msg_v01_ei, .decoded_size = QMI_UAUDIO_STREAM_REQ_MSG_V01_MAX_MSG_LEN, .fn = handle_uaudio_stream_req, }; enum mem_type { MEM_EVENT_RING, MEM_XFER_RING, MEM_XFER_BUF, }; enum usb_qmi_audio_format { USB_QMI_PCM_FORMAT_S8 = 0, USB_QMI_PCM_FORMAT_U8, USB_QMI_PCM_FORMAT_S16_LE, USB_QMI_PCM_FORMAT_S16_BE, USB_QMI_PCM_FORMAT_U16_LE, USB_QMI_PCM_FORMAT_U16_BE, USB_QMI_PCM_FORMAT_S24_LE, USB_QMI_PCM_FORMAT_S24_BE, USB_QMI_PCM_FORMAT_U24_LE, USB_QMI_PCM_FORMAT_U24_BE, USB_QMI_PCM_FORMAT_S24_3LE, USB_QMI_PCM_FORMAT_S24_3BE, USB_QMI_PCM_FORMAT_U24_3LE, USB_QMI_PCM_FORMAT_U24_3BE, USB_QMI_PCM_FORMAT_S32_LE, USB_QMI_PCM_FORMAT_S32_BE, USB_QMI_PCM_FORMAT_U32_LE, USB_QMI_PCM_FORMAT_U32_BE, }; #define uaudio_print(level, fmt, ...) do { \ ipc_log_string(uaudio_svc->uaudio_ipc_log, "%s%s: " fmt, "", __func__,\ ##__VA_ARGS__); \ printk("%s%s: " fmt, level, __func__, ##__VA_ARGS__); \ } while (0) #ifdef CONFIG_DYNAMIC_DEBUG #define uaudio_dbg(fmt, ...) do { \ ipc_log_string(uaudio_svc->uaudio_ipc_log, "%s: " fmt, __func__,\ ##__VA_ARGS__); \ dynamic_pr_debug("%s: " fmt, __func__, ##__VA_ARGS__); \ } while (0) #else #define uaudio_dbg(fmt, ...) uaudio_print(KERN_DEBUG, fmt, ##__VA_ARGS__) #endif #define uaudio_err(fmt, ...) uaudio_print(KERN_ERR, fmt, ##__VA_ARGS__) #define NUM_LOG_PAGES 10 static void uaudio_iommu_unmap(enum mem_type mtype, unsigned long va, size_t iova_size, size_t mapped_iova_size); static enum usb_audio_device_speed_enum_v01 get_speed_info(enum usb_device_speed udev_speed) { switch (udev_speed) { case USB_SPEED_LOW: return USB_AUDIO_DEVICE_SPEED_LOW_V01; case USB_SPEED_FULL: return USB_AUDIO_DEVICE_SPEED_FULL_V01; case USB_SPEED_HIGH: return USB_AUDIO_DEVICE_SPEED_HIGH_V01; case USB_SPEED_SUPER: return USB_AUDIO_DEVICE_SPEED_SUPER_V01; case USB_SPEED_SUPER_PLUS: return USB_AUDIO_DEVICE_SPEED_SUPER_PLUS_V01; default: uaudio_err("udev speed %d\n", udev_speed); return USB_AUDIO_DEVICE_SPEED_INVALID_V01; } } static unsigned long uaudio_get_iova(unsigned long *curr_iova, size_t *curr_iova_size, struct list_head *head, size_t size) { struct iova_info *info, *new_info = NULL; struct list_head *curr_head; unsigned long va = 0; size_t tmp_size = size; bool found = false; if (size % PAGE_SIZE) { uaudio_err("size %zu is not page size multiple\n", size); goto done; } if (size > *curr_iova_size) { uaudio_err("size %zu > curr size %zu\n", size, *curr_iova_size); goto done; } if (*curr_iova_size == 0) { uaudio_err("iova mapping is full\n"); goto done; } list_for_each_entry(info, head, list) { /* exact size iova_info */ if (!info->in_use && info->size == size) { info->in_use = true; va = info->start_iova; *curr_iova_size -= size; found = true; uaudio_dbg("exact size :%zu found\n", size); goto done; } else if (!info->in_use && tmp_size >= info->size) { if (!new_info) new_info = info; uaudio_dbg("partial size: %zu found\n", info->size); tmp_size -= info->size; if (tmp_size) continue; va = new_info->start_iova; for (curr_head = &new_info->list; curr_head != &info->list; curr_head = curr_head->next) { new_info = list_entry(curr_head, struct iova_info, list); new_info->in_use = true; } info->in_use = true; *curr_iova_size -= size; found = true; goto done; } else { /* iova region in use */ new_info = NULL; tmp_size = size; } } info = kzalloc(sizeof(struct iova_info), GFP_KERNEL); if (!info) { va = 0; goto done; } va = info->start_iova = *curr_iova; info->size = size; info->in_use = true; *curr_iova += size; *curr_iova_size -= size; found = true; list_add_tail(&info->list, head); done: if (!found) uaudio_err("unable to find %zu size iova\n", size); else uaudio_dbg("va:%lu curr_iova:%lu curr_iova_size:%zu\n", va, *curr_iova, *curr_iova_size); return va; } static unsigned long uaudio_iommu_map(enum mem_type mtype, phys_addr_t pa, size_t size, struct sg_table *sgt) { unsigned long va_sg, va = 0; bool map = true; int i, ret; size_t sg_len, total_len = 0; struct scatterlist *sg; phys_addr_t pa_sg; switch (mtype) { case MEM_EVENT_RING: va = IOVA_BASE; /* er already mapped */ if (uaudio_qdev->er_mapped) map = false; break; case MEM_XFER_RING: va = uaudio_get_iova(&uaudio_qdev->curr_xfer_ring_iova, &uaudio_qdev->xfer_ring_iova_size, &uaudio_qdev->xfer_ring_list, size); break; case MEM_XFER_BUF: va = uaudio_get_iova(&uaudio_qdev->curr_xfer_buf_iova, &uaudio_qdev->xfer_buf_iova_size, &uaudio_qdev->xfer_buf_list, size); break; default: uaudio_err("unknown mem type %d\n", mtype); } if (!va || !map) goto done; if (!sgt) goto skip_sgt_map; va_sg = va; for_each_sg(sgt->sgl, sg, sgt->nents, i) { sg_len = PAGE_ALIGN(sg->offset + sg->length); pa_sg = page_to_phys(sg_page(sg)); ret = iommu_map(uaudio_qdev->domain, va_sg, pa_sg, sg_len, IOMMU_READ | IOMMU_WRITE | IOMMU_MMIO); if (ret) { uaudio_err("mapping failed ret%d\n", ret); uaudio_err("memtype:%d, pa:%pK iova:%lu sg_len:%zu\n", mtype, &pa_sg, va_sg, sg_len); uaudio_iommu_unmap(MEM_XFER_BUF, va, size, total_len); va = 0; goto done; } uaudio_dbg("memtype %d:map pa:%pK to iova:%lu len:%zu\n", mtype, &pa_sg, va_sg, sg_len); va_sg += sg_len; total_len += sg_len; } if (size != total_len) { uaudio_err("iova size %zu != mapped iova size %zu\n", size, total_len); uaudio_iommu_unmap(MEM_XFER_BUF, va, size, total_len); va = 0; } return va; skip_sgt_map: uaudio_dbg("memtype:%d map pa:%pK to iova %lu size:%zu\n", mtype, &pa, va, size); ret = iommu_map(uaudio_qdev->domain, va, pa, size, IOMMU_READ | IOMMU_WRITE | IOMMU_MMIO); if (ret) uaudio_err("failed to map pa:%pK iova:%lu memtype:%d ret:%d\n", &pa, va, mtype, ret); done: return va; } static void uaudio_put_iova(unsigned long va, size_t size, struct list_head *head, size_t *curr_iova_size) { struct iova_info *info; size_t tmp_size = size; bool found = false; list_for_each_entry(info, head, list) { if (info->start_iova == va) { if (!info->in_use) { uaudio_err("va %lu is not in use\n", va); return; } found = true; info->in_use = false; if (info->size == size) goto done; } if (found && tmp_size >= info->size) { info->in_use = false; tmp_size -= info->size; if (!tmp_size) goto done; } } if (!found) { uaudio_err("unable to find the va %lu\n", va); return; } done: *curr_iova_size += size; uaudio_dbg("curr_iova_size %zu\n", *curr_iova_size); } static void uaudio_iommu_unmap(enum mem_type mtype, unsigned long va, size_t iova_size, size_t mapped_iova_size) { size_t umap_size; bool unmap = true; if (!va || !iova_size) return; switch (mtype) { case MEM_EVENT_RING: if (uaudio_qdev->er_mapped) uaudio_qdev->er_mapped = false; else unmap = false; break; case MEM_XFER_RING: uaudio_put_iova(va, iova_size, &uaudio_qdev->xfer_ring_list, &uaudio_qdev->xfer_ring_iova_size); break; case MEM_XFER_BUF: uaudio_put_iova(va, iova_size, &uaudio_qdev->xfer_buf_list, &uaudio_qdev->xfer_buf_iova_size); break; default: uaudio_err("unknown mem type %d\n", mtype); unmap = false; } if (!unmap || !mapped_iova_size) return; uaudio_dbg("memtype %d: unmap iova %lu size %zu\n", mtype, va, mapped_iova_size); umap_size = iommu_unmap(uaudio_qdev->domain, va, mapped_iova_size); if (umap_size != mapped_iova_size) uaudio_err("unmapped size %zu for iova %lu of mapped size %zu\n", umap_size, va, mapped_iova_size); } static int prepare_qmi_response(struct snd_usb_substream *subs, struct qmi_uaudio_stream_req_msg_v01 *req_msg, struct qmi_uaudio_stream_resp_msg_v01 *resp, int info_idx) { struct usb_interface *iface; struct usb_host_interface *alts; struct usb_interface_descriptor *altsd; struct usb_interface_assoc_descriptor *assoc; struct usb_host_endpoint *ep; struct uac_format_type_i_continuous_descriptor *fmt; struct uac_format_type_i_discrete_descriptor *fmt_v1; struct uac_format_type_i_ext_descriptor *fmt_v2; struct uac1_as_header_descriptor *as; int ret; int protocol, card_num, pcm_dev_num; void *hdr_ptr; u8 *xfer_buf; unsigned int data_ep_pipe = 0, sync_ep_pipe = 0; u32 len, mult, remainder, xfer_buf_len; unsigned long va, tr_data_va = 0, tr_sync_va = 0; phys_addr_t xhci_pa, xfer_buf_pa, tr_data_pa = 0, tr_sync_pa = 0; dma_addr_t dma; struct sg_table sgt; iface = usb_ifnum_to_if(subs->dev, subs->interface); if (!iface) { uaudio_err("interface # %d does not exist\n", subs->interface); ret = -ENODEV; goto err; } assoc = iface->intf_assoc; pcm_dev_num = (req_msg->usb_token & SND_PCM_DEV_NUM_MASK) >> 8; card_num = (req_msg->usb_token & SND_PCM_CARD_NUM_MASK) >> 16; xfer_buf_len = req_msg->xfer_buff_size; alts = &iface->altsetting[subs->altset_idx]; altsd = get_iface_desc(alts); protocol = altsd->bInterfaceProtocol; /* get format type */ if (protocol != UAC_VERSION_3) { fmt = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_FORMAT_TYPE); if (!fmt) { uaudio_err("%u:%d : no UAC_FORMAT_TYPE desc\n", subs->interface, subs->altset_idx); ret = -ENODEV; goto err; } } if (!uadev[card_num].ctrl_intf) { uaudio_err("audio ctrl intf info not cached\n"); ret = -ENODEV; goto err; } if (protocol != UAC_VERSION_3) { hdr_ptr = snd_usb_find_csint_desc( uadev[card_num].ctrl_intf->extra, uadev[card_num].ctrl_intf->extralen, NULL, UAC_HEADER); if (!hdr_ptr) { uaudio_err("no UAC_HEADER desc\n"); ret = -ENODEV; goto err; } } if (protocol == UAC_VERSION_1) { as = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL); if (!as) { uaudio_err("%u:%d : no UAC_AS_GENERAL desc\n", subs->interface, subs->altset_idx); ret = -ENODEV; goto err; } resp->data_path_delay = as->bDelay; resp->data_path_delay_valid = 1; fmt_v1 = (struct uac_format_type_i_discrete_descriptor *)fmt; resp->usb_audio_subslot_size = fmt_v1->bSubframeSize; resp->usb_audio_subslot_size_valid = 1; resp->usb_audio_spec_revision = ((struct uac1_ac_header_descriptor *)hdr_ptr)->bcdADC; resp->usb_audio_spec_revision_valid = 1; } else if (protocol == UAC_VERSION_2) { fmt_v2 = (struct uac_format_type_i_ext_descriptor *)fmt; resp->usb_audio_subslot_size = fmt_v2->bSubslotSize; resp->usb_audio_subslot_size_valid = 1; resp->usb_audio_spec_revision = ((struct uac2_ac_header_descriptor *)hdr_ptr)->bcdADC; resp->usb_audio_spec_revision_valid = 1; } else if (protocol == UAC_VERSION_3) { if (assoc->bFunctionSubClass == UAC3_FUNCTION_SUBCLASS_FULL_ADC_3_0) { uaudio_err("full adc is not supported\n"); ret = -EINVAL; } switch (le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize)) { case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_16: case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_16: case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_16: case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_16: { resp->usb_audio_subslot_size = 0x2; break; } case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_24: case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_24: case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_24: case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_24: { resp->usb_audio_subslot_size = 0x3; break; } default: uaudio_err("%d: %u: Invalid wMaxPacketSize\n", subs->interface, subs->altset_idx); ret = -EINVAL; goto err; } resp->usb_audio_subslot_size_valid = 1; } else { uaudio_err("unknown protocol version %x\n", protocol); ret = -ENODEV; goto err; } resp->slot_id = subs->dev->slot_id; resp->slot_id_valid = 1; memcpy(&resp->std_as_opr_intf_desc, &alts->desc, sizeof(alts->desc)); resp->std_as_opr_intf_desc_valid = 1; ep = usb_pipe_endpoint(subs->dev, subs->data_endpoint->pipe); if (!ep) { uaudio_err("data ep # %d context is null\n", subs->data_endpoint->ep_num); ret = -ENODEV; goto err; } data_ep_pipe = subs->data_endpoint->pipe; memcpy(&resp->std_as_data_ep_desc, &ep->desc, sizeof(ep->desc)); resp->std_as_data_ep_desc_valid = 1; tr_data_pa = usb_get_xfer_ring_phys_addr(subs->dev, ep, &dma); if (!tr_data_pa) { uaudio_err("failed to get data ep ring dma address\n"); ret = -ENODEV; goto err; } resp->xhci_mem_info.tr_data.pa = dma; if (subs->sync_endpoint) { ep = usb_pipe_endpoint(subs->dev, subs->sync_endpoint->pipe); if (!ep) { uaudio_dbg("implicit fb on data ep\n"); goto skip_sync_ep; } sync_ep_pipe = subs->sync_endpoint->pipe; memcpy(&resp->std_as_sync_ep_desc, &ep->desc, sizeof(ep->desc)); resp->std_as_sync_ep_desc_valid = 1; tr_sync_pa = usb_get_xfer_ring_phys_addr(subs->dev, ep, &dma); if (!tr_sync_pa) { uaudio_err("failed to get sync ep ring dma address\n"); ret = -ENODEV; goto err; } resp->xhci_mem_info.tr_sync.pa = dma; } skip_sync_ep: resp->interrupter_num = uaudio_qdev->intr_num; resp->interrupter_num_valid = 1; ret = usb_get_controller_id(subs->dev); if (ret < 0) goto err; resp->controller_num = ret; resp->controller_num_valid = 1; /* map xhci data structures PA memory to iova */ /* event ring */ ret = usb_sec_event_ring_setup(subs->dev, resp->interrupter_num); if (ret) { uaudio_err("failed to setup sec event ring ret %d\n", ret); goto err; } xhci_pa = usb_get_sec_event_ring_phys_addr(subs->dev, resp->interrupter_num, &dma); if (!xhci_pa) { uaudio_err("failed to get sec event ring dma address\n"); ret = -ENODEV; goto err; } va = uaudio_iommu_map(MEM_EVENT_RING, xhci_pa, PAGE_SIZE, NULL); if (!va) { ret = -ENOMEM; goto err; } resp->xhci_mem_info.evt_ring.va = PREPEND_SID_TO_IOVA(va, uaudio_qdev->sid); resp->xhci_mem_info.evt_ring.pa = dma; resp->xhci_mem_info.evt_ring.size = PAGE_SIZE; uaudio_qdev->er_mapped = true; resp->speed_info = get_speed_info(subs->dev->speed); if (resp->speed_info == USB_AUDIO_DEVICE_SPEED_INVALID_V01) { ret = -ENODEV; goto unmap_er; } resp->speed_info_valid = 1; /* data transfer ring */ va = uaudio_iommu_map(MEM_XFER_RING, tr_data_pa, PAGE_SIZE, NULL); if (!va) { ret = -ENOMEM; goto unmap_er; } tr_data_va = va; resp->xhci_mem_info.tr_data.va = PREPEND_SID_TO_IOVA(va, uaudio_qdev->sid); resp->xhci_mem_info.tr_data.size = PAGE_SIZE; /* sync transfer ring */ if (!resp->xhci_mem_info.tr_sync.pa) goto skip_sync; xhci_pa = resp->xhci_mem_info.tr_sync.pa; va = uaudio_iommu_map(MEM_XFER_RING, tr_sync_pa, PAGE_SIZE, NULL); if (!va) { ret = -ENOMEM; goto unmap_data; } tr_sync_va = va; resp->xhci_mem_info.tr_sync.va = PREPEND_SID_TO_IOVA(va, uaudio_qdev->sid); resp->xhci_mem_info.tr_sync.size = PAGE_SIZE; skip_sync: /* xfer buffer, multiple of 4K only */ if (!xfer_buf_len) xfer_buf_len = PAGE_SIZE; mult = xfer_buf_len / PAGE_SIZE; remainder = xfer_buf_len % PAGE_SIZE; len = mult * PAGE_SIZE; len += remainder ? PAGE_SIZE : 0; if (len > MAX_XFER_BUFF_LEN) { uaudio_err("req buf len %d > max buf len %lu, setting %lu\n", len, MAX_XFER_BUFF_LEN, MAX_XFER_BUFF_LEN); len = MAX_XFER_BUFF_LEN; } xfer_buf = usb_alloc_coherent(subs->dev, len, GFP_KERNEL, &xfer_buf_pa); if (!xfer_buf) { ret = -ENOMEM; goto unmap_sync; } dma_get_sgtable(subs->dev->bus->sysdev, &sgt, xfer_buf, xfer_buf_pa, len); va = uaudio_iommu_map(MEM_XFER_BUF, xfer_buf_pa, len, &sgt); if (!va) { ret = -ENOMEM; goto unmap_sync; } resp->xhci_mem_info.xfer_buff.pa = xfer_buf_pa; resp->xhci_mem_info.xfer_buff.size = len; resp->xhci_mem_info.xfer_buff.va = PREPEND_SID_TO_IOVA(va, uaudio_qdev->sid); resp->xhci_mem_info_valid = 1; sg_free_table(&sgt); if (!atomic_read(&uadev[card_num].in_use)) { kref_init(&uadev[card_num].kref); init_waitqueue_head(&uadev[card_num].disconnect_wq); uadev[card_num].num_intf = subs->dev->config->desc.bNumInterfaces; uadev[card_num].info = kcalloc(uadev[card_num].num_intf, sizeof(struct intf_info), GFP_KERNEL); if (!uadev[card_num].info) { ret = -ENOMEM; goto unmap_sync; } uadev[card_num].udev = subs->dev; atomic_set(&uadev[card_num].in_use, 1); } else { kref_get(&uadev[card_num].kref); } uadev[card_num].card_num = card_num; uadev[card_num].usb_core_id = resp->controller_num; /* cache intf specific info to use it for unmap and free xfer buf */ uadev[card_num].info[info_idx].data_xfer_ring_va = tr_data_va; uadev[card_num].info[info_idx].data_xfer_ring_size = PAGE_SIZE; uadev[card_num].info[info_idx].sync_xfer_ring_va = tr_sync_va; uadev[card_num].info[info_idx].sync_xfer_ring_size = PAGE_SIZE; uadev[card_num].info[info_idx].xfer_buf_va = va; uadev[card_num].info[info_idx].xfer_buf_pa = xfer_buf_pa; uadev[card_num].info[info_idx].xfer_buf_size = len; uadev[card_num].info[info_idx].data_ep_pipe = data_ep_pipe; uadev[card_num].info[info_idx].sync_ep_pipe = sync_ep_pipe; uadev[card_num].info[info_idx].xfer_buf = xfer_buf; uadev[card_num].info[info_idx].pcm_card_num = card_num; uadev[card_num].info[info_idx].pcm_dev_num = pcm_dev_num; uadev[card_num].info[info_idx].direction = subs->direction; uadev[card_num].info[info_idx].intf_num = subs->interface; uadev[card_num].info[info_idx].in_use = true; set_bit(card_num, &uaudio_qdev->card_slot); return 0; unmap_sync: usb_free_coherent(subs->dev, len, xfer_buf, xfer_buf_pa); uaudio_iommu_unmap(MEM_XFER_RING, tr_sync_va, PAGE_SIZE, PAGE_SIZE); unmap_data: uaudio_iommu_unmap(MEM_XFER_RING, tr_data_va, PAGE_SIZE, PAGE_SIZE); unmap_er: uaudio_iommu_unmap(MEM_EVENT_RING, IOVA_BASE, PAGE_SIZE, PAGE_SIZE); err: return ret; } static void uaudio_dev_intf_cleanup(struct usb_device *udev, struct intf_info *info) { uaudio_iommu_unmap(MEM_XFER_RING, info->data_xfer_ring_va, info->data_xfer_ring_size, info->data_xfer_ring_size); info->data_xfer_ring_va = 0; info->data_xfer_ring_size = 0; uaudio_iommu_unmap(MEM_XFER_RING, info->sync_xfer_ring_va, info->sync_xfer_ring_size, info->sync_xfer_ring_size); info->sync_xfer_ring_va = 0; info->sync_xfer_ring_size = 0; uaudio_iommu_unmap(MEM_XFER_BUF, info->xfer_buf_va, info->xfer_buf_size, info->xfer_buf_size); info->xfer_buf_va = 0; usb_free_coherent(udev, info->xfer_buf_size, info->xfer_buf, info->xfer_buf_pa); info->xfer_buf_size = 0; info->xfer_buf = NULL; info->xfer_buf_pa = 0; info->in_use = false; } static void uaudio_dev_cleanup(struct uaudio_dev *dev) { int if_idx; /* free xfer buffer and unmap xfer ring and buf per interface */ for (if_idx = 0; if_idx < dev->num_intf; if_idx++) { if (!dev->info[if_idx].in_use) continue; uaudio_dev_intf_cleanup(dev->udev, &dev->info[if_idx]); uaudio_dbg("release resources: intf# %d card# %d\n", dev->info[if_idx].intf_num, dev->card_num); } dev->num_intf = 0; /* free interface info */ kfree(dev->info); dev->info = NULL; clear_bit(dev->card_num, &uaudio_qdev->card_slot); /* all audio devices are disconnected */ if (!uaudio_qdev->card_slot) { uaudio_iommu_unmap(MEM_EVENT_RING, IOVA_BASE, PAGE_SIZE, PAGE_SIZE); usb_sec_event_ring_cleanup(dev->udev, uaudio_qdev->intr_num); uaudio_dbg("all audio devices disconnected\n"); } dev->udev = NULL; } static void uaudio_disconnect_cb(struct snd_usb_audio *chip) { int ret; struct uaudio_dev *dev; int card_num = chip->card_num; struct uaudio_qmi_svc *svc = uaudio_svc; struct qmi_uaudio_stream_ind_msg_v01 disconnect_ind = {0}; uaudio_dbg("for card# %d\n", card_num); if (card_num >= SNDRV_CARDS) { uaudio_err("invalid card number\n"); return; } mutex_lock(&chip->dev_lock); dev = &uadev[card_num]; /* clean up */ if (!dev->udev) { uaudio_dbg("no clean up required\n"); goto done; } if (atomic_read(&dev->in_use)) { mutex_unlock(&chip->dev_lock); uaudio_dbg("sending qmi indication disconnect\n"); uaudio_dbg("sq->sq_family:%x sq->sq_node:%x sq->sq_port:%x\n", svc->client_sq.sq_family, svc->client_sq.sq_node, svc->client_sq.sq_port); disconnect_ind.dev_event = USB_AUDIO_DEV_DISCONNECT_V01; disconnect_ind.slot_id = dev->udev->slot_id; disconnect_ind.controller_num = dev->usb_core_id; disconnect_ind.controller_num_valid = 1; ret = qmi_send_indication(svc->uaudio_svc_hdl, &svc->client_sq, QMI_UADUIO_STREAM_IND_V01, QMI_UAUDIO_STREAM_IND_MSG_V01_MAX_MSG_LEN, qmi_uaudio_stream_ind_msg_v01_ei, &disconnect_ind); if (ret < 0) uaudio_err("qmi send failed with err: %d\n", ret); ret = wait_event_interruptible_timeout(dev->disconnect_wq, !atomic_read(&dev->in_use), msecs_to_jiffies(DEV_RELEASE_WAIT_TIMEOUT)); if (!ret) { uaudio_err("timeout while waiting for dev_release\n"); atomic_set(&dev->in_use, 0); } else if (ret < 0) { uaudio_err("failed with ret %d\n", ret); atomic_set(&dev->in_use, 0); } mutex_lock(&chip->dev_lock); } uaudio_dev_cleanup(dev); done: mutex_unlock(&chip->dev_lock); } static void uaudio_dev_release(struct kref *kref) { struct uaudio_dev *dev = container_of(kref, struct uaudio_dev, kref); uaudio_dbg("for dev %pK\n", dev); atomic_set(&dev->in_use, 0); clear_bit(dev->card_num, &uaudio_qdev->card_slot); /* all audio devices are disconnected */ if (!uaudio_qdev->card_slot) { usb_sec_event_ring_cleanup(dev->udev, uaudio_qdev->intr_num); uaudio_iommu_unmap(MEM_EVENT_RING, IOVA_BASE, PAGE_SIZE, PAGE_SIZE); uaudio_dbg("all audio devices disconnected\n"); } wake_up(&dev->disconnect_wq); } /* maps audio format received over QMI to asound.h based pcm format */ static int map_pcm_format(unsigned int fmt_received) { switch (fmt_received) { case USB_QMI_PCM_FORMAT_S8: return SNDRV_PCM_FORMAT_S8; case USB_QMI_PCM_FORMAT_U8: return SNDRV_PCM_FORMAT_U8; case USB_QMI_PCM_FORMAT_S16_LE: return SNDRV_PCM_FORMAT_S16_LE; case USB_QMI_PCM_FORMAT_S16_BE: return SNDRV_PCM_FORMAT_S16_BE; case USB_QMI_PCM_FORMAT_U16_LE: return SNDRV_PCM_FORMAT_U16_LE; case USB_QMI_PCM_FORMAT_U16_BE: return SNDRV_PCM_FORMAT_U16_BE; case USB_QMI_PCM_FORMAT_S24_LE: return SNDRV_PCM_FORMAT_S24_LE; case USB_QMI_PCM_FORMAT_S24_BE: return SNDRV_PCM_FORMAT_S24_BE; case USB_QMI_PCM_FORMAT_U24_LE: return SNDRV_PCM_FORMAT_U24_LE; case USB_QMI_PCM_FORMAT_U24_BE: return SNDRV_PCM_FORMAT_U24_BE; case USB_QMI_PCM_FORMAT_S24_3LE: return SNDRV_PCM_FORMAT_S24_3LE; case USB_QMI_PCM_FORMAT_S24_3BE: return SNDRV_PCM_FORMAT_S24_3BE; case USB_QMI_PCM_FORMAT_U24_3LE: return SNDRV_PCM_FORMAT_U24_3LE; case USB_QMI_PCM_FORMAT_U24_3BE: return SNDRV_PCM_FORMAT_U24_3BE; case USB_QMI_PCM_FORMAT_S32_LE: return SNDRV_PCM_FORMAT_S32_LE; case USB_QMI_PCM_FORMAT_S32_BE: return SNDRV_PCM_FORMAT_S32_BE; case USB_QMI_PCM_FORMAT_U32_LE: return SNDRV_PCM_FORMAT_U32_LE; case USB_QMI_PCM_FORMAT_U32_BE: return SNDRV_PCM_FORMAT_U32_BE; default: return -EINVAL; } } static int info_idx_from_ifnum(int card_num, int intf_num, bool enable) { int i; /* * default index 0 is used when info is allocated upon * first enable audio stream req for a pcm device */ if (enable && !uadev[card_num].info) return 0; for (i = 0; i < uadev[card_num].num_intf; i++) { if (enable && !uadev[card_num].info[i].in_use) return i; else if (!enable && uadev[card_num].info[i].intf_num == intf_num) return i; } return -EINVAL; } static int get_data_interval_from_si(struct snd_usb_substream *subs, u32 service_interval) { unsigned int bus_intval, bus_intval_mult, binterval; if (subs->dev->speed >= USB_SPEED_HIGH) bus_intval = BUS_INTERVAL_HIGHSPEED_AND_ABOVE; else bus_intval = BUS_INTERVAL_FULL_SPEED; if (service_interval % bus_intval) return -EINVAL; bus_intval_mult = service_interval / bus_intval; binterval = ffs(bus_intval_mult); if (!binterval || binterval > MAX_BINTERVAL_ISOC_EP) return -EINVAL; /* check if another bit is set then bail out */ bus_intval_mult = bus_intval_mult >> binterval; if (bus_intval_mult) return -EINVAL; return (binterval - 1); } static void handle_uaudio_stream_req(struct qmi_handle *handle, struct sockaddr_qrtr *sq, struct qmi_txn *txn, const void *decoded_msg) { struct qmi_uaudio_stream_req_msg_v01 *req_msg; struct qmi_uaudio_stream_resp_msg_v01 resp = {{0}, 0}; struct snd_usb_substream *subs; struct snd_usb_audio *chip = NULL; struct uaudio_qmi_svc *svc = uaudio_svc; struct intf_info *info; struct usb_host_endpoint *ep; ktime_t t_request_recvd = ktime_get(); int pcm_format; u8 pcm_card_num, pcm_dev_num, direction; int info_idx = -EINVAL, datainterval = -EINVAL, ret = 0; uaudio_dbg("sq_node:%x sq_port:%x sq_family:%x\n", sq->sq_node, sq->sq_port, sq->sq_family); if (!svc->client_connected) { svc->client_sq = *sq; svc->client_connected = true; } req_msg = (struct qmi_uaudio_stream_req_msg_v01 *)decoded_msg; if (!req_msg->audio_format_valid || !req_msg->bit_rate_valid || !req_msg->number_of_ch_valid || !req_msg->xfer_buff_size_valid) { uaudio_err("invalid request msg\n"); ret = -EINVAL; goto response; } direction = req_msg->usb_token & SND_PCM_STREAM_DIRECTION; pcm_dev_num = (req_msg->usb_token & SND_PCM_DEV_NUM_MASK) >> 8; pcm_card_num = (req_msg->usb_token & SND_PCM_CARD_NUM_MASK) >> 16; uaudio_dbg("card#:%d dev#:%d dir:%d en:%d fmt:%d rate:%d #ch:%d\n", pcm_card_num, pcm_dev_num, direction, req_msg->enable, req_msg->audio_format, req_msg->bit_rate, req_msg->number_of_ch); if (pcm_card_num >= SNDRV_CARDS) { uaudio_err("invalid card # %u", pcm_card_num); ret = -EINVAL; goto response; } pcm_format = map_pcm_format(req_msg->audio_format); if (pcm_format == -EINVAL) { uaudio_err("unsupported pcm format received %d\n", req_msg->audio_format); ret = -EINVAL; goto response; } subs = find_snd_usb_substream(pcm_card_num, pcm_dev_num, direction, &chip, uaudio_disconnect_cb); if (!subs || !chip || atomic_read(&chip->shutdown)) { uaudio_err("can't find substream for card# %u, dev# %u dir%u\n", pcm_card_num, pcm_dev_num, direction); ret = -ENODEV; goto response; } mutex_lock(&chip->dev_lock); info_idx = info_idx_from_ifnum(pcm_card_num, subs->interface, req_msg->enable); if (atomic_read(&chip->shutdown) || !subs->stream || !subs->stream->pcm || !subs->stream->chip) { ret = -ENODEV; mutex_unlock(&chip->dev_lock); goto response; } if (req_msg->enable) { if (info_idx < 0) { uaudio_err("interface# %d already in use card# %d\n", subs->interface, pcm_card_num); ret = -EBUSY; mutex_unlock(&chip->dev_lock); goto response; } } subs->pcm_format = pcm_format; subs->channels = req_msg->number_of_ch; subs->cur_rate = req_msg->bit_rate; if (req_msg->service_interval_valid) { ret = get_data_interval_from_si(subs, req_msg->service_interval); if (ret == -EINVAL) { uaudio_err("invalid service interval %u\n", req_msg->service_interval); mutex_unlock(&chip->dev_lock); goto response; } datainterval = ret; uaudio_dbg("data interval %u\n", ret); } uadev[pcm_card_num].ctrl_intf = chip->ctrl_intf; if (!req_msg->enable) { info = &uadev[pcm_card_num].info[info_idx]; if (info->data_ep_pipe) { ep = usb_pipe_endpoint(uadev[pcm_card_num].udev, info->data_ep_pipe); if (!ep) uaudio_dbg("no data ep\n"); else usb_stop_endpoint(uadev[pcm_card_num].udev, ep); info->data_ep_pipe = 0; } if (info->sync_ep_pipe) { ep = usb_pipe_endpoint(uadev[pcm_card_num].udev, info->sync_ep_pipe); if (!ep) uaudio_dbg("no sync ep\n"); else usb_stop_endpoint(uadev[pcm_card_num].udev, ep); info->sync_ep_pipe = 0; } } ret = snd_usb_enable_audio_stream(subs, datainterval, req_msg->enable); if (!ret && req_msg->enable) ret = prepare_qmi_response(subs, req_msg, &resp, info_idx); mutex_unlock(&chip->dev_lock); response: if (!req_msg->enable && ret != -EINVAL && ret != -ENODEV) { mutex_lock(&chip->dev_lock); if (info_idx >= 0) { info = &uadev[pcm_card_num].info[info_idx]; uaudio_dev_intf_cleanup( uadev[pcm_card_num].udev, info); uaudio_dbg("release resources: intf# %d card# %d\n", subs->interface, pcm_card_num); } if (atomic_read(&uadev[pcm_card_num].in_use)) kref_put(&uadev[pcm_card_num].kref, uaudio_dev_release); mutex_unlock(&chip->dev_lock); } resp.usb_token = req_msg->usb_token; resp.usb_token_valid = 1; resp.internal_status = ret; resp.internal_status_valid = 1; resp.status = ret ? USB_AUDIO_STREAM_REQ_FAILURE_V01 : ret; resp.status_valid = 1; ret = qmi_send_response(svc->uaudio_svc_hdl, sq, txn, QMI_UAUDIO_STREAM_RESP_V01, QMI_UAUDIO_STREAM_RESP_MSG_V01_MAX_MSG_LEN, qmi_uaudio_stream_resp_msg_v01_ei, &resp); uaudio_dbg("ret %d: qmi response latency %lld ms\n", ret, ktime_to_ms(ktime_sub(ktime_get(), t_request_recvd))); } static void uaudio_qmi_disconnect_work(struct work_struct *w) { struct intf_info *info; int idx, if_idx; struct snd_usb_substream *subs; struct snd_usb_audio *chip = NULL; /* find all active intf for set alt 0 and cleanup usb audio dev */ for (idx = 0; idx < SNDRV_CARDS; idx++) { if (!atomic_read(&uadev[idx].in_use)) continue; for (if_idx = 0; if_idx < uadev[idx].num_intf; if_idx++) { if (!uadev[idx].info || !uadev[idx].info[if_idx].in_use) continue; info = &uadev[idx].info[if_idx]; subs = find_snd_usb_substream(info->pcm_card_num, info->pcm_dev_num, info->direction, &chip, uaudio_disconnect_cb); if (!subs || !chip || atomic_read(&chip->shutdown)) { uaudio_dbg("no subs for c#%u, dev#%u dir%u\n", info->pcm_card_num, info->pcm_dev_num, info->direction); continue; } snd_usb_enable_audio_stream(subs, -EINVAL, 0); } atomic_set(&uadev[idx].in_use, 0); mutex_lock(&chip->dev_lock); uaudio_dev_cleanup(&uadev[idx]); mutex_unlock(&chip->dev_lock); } } static void uaudio_qmi_bye_cb(struct qmi_handle *handle, unsigned int node) { struct uaudio_qmi_svc *svc = uaudio_svc; if (svc->uaudio_svc_hdl != handle) { uaudio_err("handle mismatch\n"); return; } if (svc->client_connected && svc->client_sq.sq_node == node) { uaudio_dbg("node: %d\n", node); queue_work(svc->uaudio_wq, &svc->qmi_disconnect_work); svc->client_sq.sq_node = 0; svc->client_sq.sq_port = 0; svc->client_sq.sq_family = 0; svc->client_connected = false; } } static void uaudio_qmi_svc_disconnect_cb(struct qmi_handle *handle, unsigned int node, unsigned int port) { struct uaudio_qmi_svc *svc = uaudio_svc; if (svc->uaudio_svc_hdl != handle) { uaudio_err("handle mismatch\n"); return; } if (svc->client_connected && svc->client_sq.sq_node == node && svc->client_sq.sq_port == port) { uaudio_dbg("client node:%x port:%x\n", node, port); queue_work(svc->uaudio_wq, &svc->qmi_disconnect_work); svc->client_sq.sq_node = 0; svc->client_sq.sq_port = 0; svc->client_sq.sq_family = 0; svc->client_connected = false; } } static struct qmi_ops uaudio_svc_ops_options = { .bye = uaudio_qmi_bye_cb, .del_client = uaudio_qmi_svc_disconnect_cb, }; static int uaudio_qmi_plat_probe(struct platform_device *pdev) { int ret; struct device_node *node = pdev->dev.of_node; uaudio_qdev = devm_kzalloc(&pdev->dev, sizeof(struct uaudio_qmi_dev), GFP_KERNEL); if (!uaudio_qdev) return -ENOMEM; uaudio_qdev->dev = &pdev->dev; ret = of_property_read_u32(node, "qcom,usb-audio-stream-id", &uaudio_qdev->sid); if (ret) { dev_err(&pdev->dev, "failed to read sid.\n"); return -ENODEV; } ret = of_property_read_u32(node, "qcom,usb-audio-intr-num", &uaudio_qdev->intr_num); if (ret) { dev_err(&pdev->dev, "failed to read intr num.\n"); return -ENODEV; } uaudio_qdev->domain = iommu_domain_alloc(pdev->dev.bus); if (!uaudio_qdev->domain) { dev_err(&pdev->dev, "failed to allocate iommu domain\n"); return -ENODEV; } /* attach to external processor iommu */ ret = iommu_attach_device(uaudio_qdev->domain, &pdev->dev); if (ret) { dev_err(&pdev->dev, "failed to attach device ret = %d\n", ret); goto free_domain; } /* initialize xfer ring and xfer buf iova list */ INIT_LIST_HEAD(&uaudio_qdev->xfer_ring_list); uaudio_qdev->curr_xfer_ring_iova = IOVA_XFER_RING_BASE; uaudio_qdev->xfer_ring_iova_size = IOVA_XFER_RING_MAX - IOVA_XFER_RING_BASE; INIT_LIST_HEAD(&uaudio_qdev->xfer_buf_list); uaudio_qdev->curr_xfer_buf_iova = IOVA_XFER_BUF_BASE; uaudio_qdev->xfer_buf_iova_size = IOVA_XFER_BUF_MAX - IOVA_XFER_BUF_BASE; return 0; free_domain: iommu_domain_free(uaudio_qdev->domain); return ret; } static int uaudio_qmi_plat_remove(struct platform_device *pdev) { iommu_detach_device(uaudio_qdev->domain, &pdev->dev); iommu_domain_free(uaudio_qdev->domain); uaudio_qdev->domain = NULL; return 0; } static const struct of_device_id of_uaudio_matach[] = { { .compatible = "qcom,usb-audio-qmi-dev", }, { }, }; MODULE_DEVICE_TABLE(of, of_uaudio_matach); static struct platform_driver uaudio_qmi_driver = { .probe = uaudio_qmi_plat_probe, .remove = uaudio_qmi_plat_remove, .driver = { .name = "uaudio-qmi", .of_match_table = of_uaudio_matach, }, }; static int uaudio_qmi_svc_init(void) { int ret; struct uaudio_qmi_svc *svc; svc = kzalloc(sizeof(struct uaudio_qmi_svc), GFP_KERNEL); if (!svc) return -ENOMEM; svc->uaudio_wq = create_singlethread_workqueue("uaudio_svc"); if (!svc->uaudio_wq) { ret = -ENOMEM; goto free_svc; } svc->uaudio_svc_hdl = kzalloc(sizeof(struct qmi_handle), GFP_KERNEL); if (!svc->uaudio_svc_hdl) { ret = -ENOMEM; goto destroy_uaudio_wq; } ret = qmi_handle_init(svc->uaudio_svc_hdl, QMI_UAUDIO_STREAM_REQ_MSG_V01_MAX_MSG_LEN, &uaudio_svc_ops_options, &uaudio_stream_req_handlers); if (ret < 0) { pr_err("%s:Error registering uaudio svc %d\n", __func__, ret); goto free_svc_hdl; } ret = qmi_add_server(svc->uaudio_svc_hdl, UAUDIO_STREAM_SERVICE_ID_V01, UAUDIO_STREAM_SERVICE_VERS_V01, 0); if (ret < 0) { pr_err("%s: failed to add uaudio svc server :%d\n", __func__, ret); goto release_uaudio_svs_hdl; } INIT_WORK(&svc->qmi_disconnect_work, uaudio_qmi_disconnect_work); uaudio_svc = svc; svc->uaudio_ipc_log = ipc_log_context_create(NUM_LOG_PAGES, "usb_audio", 0); return 0; release_uaudio_svs_hdl: qmi_handle_release(svc->uaudio_svc_hdl); free_svc_hdl: kfree(svc->uaudio_svc_hdl); destroy_uaudio_wq: destroy_workqueue(svc->uaudio_wq); free_svc: kfree(svc); return ret; } static void uaudio_qmi_svc_exit(void) { struct uaudio_qmi_svc *svc = uaudio_svc; qmi_handle_release(svc->uaudio_svc_hdl); flush_workqueue(svc->uaudio_wq); destroy_workqueue(svc->uaudio_wq); kfree(svc->uaudio_svc_hdl); ipc_log_context_destroy(svc->uaudio_ipc_log); kfree(svc); uaudio_svc = NULL; } static int __init uaudio_qmi_plat_init(void) { int ret; ret = platform_driver_register(&uaudio_qmi_driver); if (ret) return ret; return uaudio_qmi_svc_init(); } static void __exit uaudio_qmi_plat_exit(void) { uaudio_qmi_svc_exit(); platform_driver_unregister(&uaudio_qmi_driver); } module_init(uaudio_qmi_plat_init); module_exit(uaudio_qmi_plat_exit); MODULE_DESCRIPTION("USB AUDIO QMI Service Driver"); MODULE_LICENSE("GPL v2");