/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #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 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_DCBA_BASE 0x2000 #define IOVA_XFER_RING_BASE (IOVA_DCBA_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; 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; atomic_t in_use; struct kref kref; unsigned long dcba_iova; size_t dcba_size; 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 dcba_list; size_t dcba_iova_size; unsigned long curr_dcba_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; /* cache event ring phys addr */ u64 er_phys_addr; }; static struct uaudio_qmi_dev *uaudio_qdev; struct uaudio_qmi_svc { struct qmi_handle *uaudio_svc_hdl; void *curr_conn; struct work_struct recv_msg_work; struct work_struct qmi_disconnect_work; struct workqueue_struct *uaudio_wq; ktime_t t_request_recvd; ktime_t t_resp_sent; }; static struct uaudio_qmi_svc *uaudio_svc; static struct msg_desc uaudio_stream_req_desc = { .max_msg_len = QMI_UAUDIO_STREAM_REQ_MSG_V01_MAX_MSG_LEN, .msg_id = QMI_UAUDIO_STREAM_REQ_V01, .ei_array = qmi_uaudio_stream_req_msg_v01_ei, }; static struct msg_desc uaudio_stream_resp_desc = { .max_msg_len = QMI_UAUDIO_STREAM_RESP_MSG_V01_MAX_MSG_LEN, .msg_id = QMI_UAUDIO_STREAM_RESP_V01, .ei_array = qmi_uaudio_stream_resp_msg_v01_ei, }; static struct msg_desc uaudio_stream_ind_desc = { .max_msg_len = QMI_UAUDIO_STREAM_IND_MSG_V01_MAX_MSG_LEN, .msg_id = QMI_UADUIO_STREAM_IND_V01, .ei_array = qmi_uaudio_stream_ind_msg_v01_ei, }; enum mem_type { MEM_EVENT_RING, MEM_DCBA, 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, }; 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) { pr_err("%s: size %zu is not page size multiple\n", __func__, size); goto done; } if (size > *curr_iova_size) { pr_err("%s: size %zu > curr size %zu\n", __func__, size, *curr_iova_size); goto done; } if (*curr_iova_size == 0) { pr_err("%s: iova mapping is full\n", __func__); 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; pr_debug("%s: exact size :%zu found\n", __func__, size); goto done; } else if (!info->in_use && tmp_size >= info->size) { if (!new_info) new_info = info; pr_debug("%s: partial size: %zu found\n", __func__, 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) pr_err("%s: unable to find %zu size iova\n", __func__, size); else pr_debug("%s: va:%lu curr_iova:%lu curr_iova_size:%zu\n", __func__, 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) { unsigned long va = 0; bool map = true; int ret; switch (mtype) { case MEM_EVENT_RING: va = IOVA_BASE; /* er already mapped */ if (uaudio_qdev->er_phys_addr == pa) map = false; break; case MEM_DCBA: va = uaudio_get_iova(&uaudio_qdev->curr_dcba_iova, &uaudio_qdev->dcba_iova_size, &uaudio_qdev->dcba_list, size); 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: pr_err("%s: unknown mem type %d\n", __func__, mtype); } if (!va) map = false; if (!map) goto done; pr_debug("%s: map pa %pa to iova %lu for memtype %d\n", __func__, &pa, va, mtype); ret = iommu_map(uaudio_qdev->domain, va, pa, size, IOMMU_READ | IOMMU_WRITE | IOMMU_DEVICE); if (ret) pr_err("%s:failed to map pa:%pa iova:%lu memtype:%d ret:%d\n", __func__, &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) { pr_err("%s: va %lu is not in use\n", __func__, 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) { pr_err("%s: unable to find the va %lu\n", __func__, va); return; } done: *curr_iova_size += size; pr_debug("%s: curr_iova_size %zu\n", __func__, *curr_iova_size); } static void uaudio_iommu_unmap(enum mem_type mtype, unsigned long va, size_t size) { size_t umap_size; bool unmap = true; if (!va || !size) return; switch (mtype) { case MEM_EVENT_RING: if (uaudio_qdev->er_phys_addr) uaudio_qdev->er_phys_addr = 0; else unmap = false; break; case MEM_DCBA: uaudio_put_iova(va, size, &uaudio_qdev->dcba_list, &uaudio_qdev->dcba_iova_size); break; case MEM_XFER_RING: uaudio_put_iova(va, size, &uaudio_qdev->xfer_ring_list, &uaudio_qdev->xfer_ring_iova_size); break; case MEM_XFER_BUF: uaudio_put_iova(va, size, &uaudio_qdev->xfer_buf_list, &uaudio_qdev->xfer_buf_iova_size); break; default: pr_err("%s: unknown mem type %d\n", __func__, mtype); unmap = false; } if (!unmap) return; pr_debug("%s: unmap iova %lu for memtype %d\n", __func__, va, mtype); umap_size = iommu_unmap(uaudio_qdev->domain, va, size); if (umap_size != size) pr_err("%s: unmapped size %zu for iova %lu\n", __func__, umap_size, va); } 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_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 = -ENODEV; int protocol, card_num, pcm_dev_num; void *hdr_ptr; u8 *xfer_buf; u32 len, mult, remainder, xfer_buf_len; unsigned long va, tr_data_va = 0, tr_sync_va = 0, dcba_va = 0, xfer_buf_va = 0; phys_addr_t xhci_pa, xfer_buf_pa; iface = usb_ifnum_to_if(subs->dev, subs->interface); if (!iface) { pr_err("%s: interface # %d does not exist\n", __func__, subs->interface); goto err; } 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) { pr_err("%s: %u:%d : no UAC_FORMAT_TYPE desc\n", __func__, subs->interface, subs->altset_idx); goto err; } } if (!uadev[card_num].ctrl_intf) { pr_err("%s: audio ctrl intf info not cached\n", __func__); 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) { pr_err("%s: no UAC_HEADER desc\n", __func__); goto err; } } if (protocol == UAC_VERSION_1) { as = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL); if (!as) { pr_err("%s: %u:%d : no UAC_AS_GENERAL desc\n", __func__, subs->interface, subs->altset_idx); 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) { switch (le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize)) { case BADD_MAXPSIZE_SYNC_MONO_16: case BADD_MAXPSIZE_SYNC_STEREO_16: case BADD_MAXPSIZE_ASYNC_MONO_16: case BADD_MAXPSIZE_ASYNC_STEREO_16: { resp->usb_audio_subslot_size = SUBSLOTSIZE_16_BIT; break; } case BADD_MAXPSIZE_SYNC_MONO_24: case BADD_MAXPSIZE_SYNC_STEREO_24: case BADD_MAXPSIZE_ASYNC_MONO_24: case BADD_MAXPSIZE_ASYNC_STEREO_24: { resp->usb_audio_subslot_size = SUBSLOTSIZE_24_BIT; break; } default: pr_err("%d: %u: Invalid wMaxPacketSize\n", subs->interface, subs->altset_idx); ret = -EINVAL; goto err; } resp->usb_audio_subslot_size_valid = 1; } else { pr_err("%s: unknown protocol version %x\n", __func__, protocol); 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) { pr_err("%s: data ep # %d context is null\n", __func__, subs->data_endpoint->ep_num); goto err; } memcpy(&resp->std_as_data_ep_desc, &ep->desc, sizeof(ep->desc)); resp->std_as_data_ep_desc_valid = 1; xhci_pa = usb_get_xfer_ring_dma_addr(subs->dev, ep); if (!xhci_pa) { pr_err("%s:failed to get data ep ring dma address\n", __func__); goto err; } resp->xhci_mem_info.tr_data.pa = xhci_pa; if (subs->sync_endpoint) { ep = usb_pipe_endpoint(subs->dev, subs->sync_endpoint->pipe); if (!ep) { pr_debug("%s: implicit fb on data ep\n", __func__); goto skip_sync_ep; } memcpy(&resp->std_as_sync_ep_desc, &ep->desc, sizeof(ep->desc)); resp->std_as_sync_ep_desc_valid = 1; xhci_pa = usb_get_xfer_ring_dma_addr(subs->dev, ep); if (!xhci_pa) { pr_err("%s:failed to get sync ep ring dma address\n", __func__); goto err; } resp->xhci_mem_info.tr_sync.pa = xhci_pa; } skip_sync_ep: resp->interrupter_num = uaudio_qdev->intr_num; resp->interrupter_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) { pr_err("%s: failed to setup sec event ring ret %d\n", __func__, ret); goto err; } xhci_pa = usb_get_sec_event_ring_dma_addr(subs->dev, resp->interrupter_num); if (!xhci_pa) { pr_err("%s: failed to get sec event ring dma address\n", __func__); goto err; } va = uaudio_iommu_map(MEM_EVENT_RING, xhci_pa, PAGE_SIZE); if (!va) goto err; resp->xhci_mem_info.evt_ring.va = PREPEND_SID_TO_IOVA(va, uaudio_qdev->sid); resp->xhci_mem_info.evt_ring.pa = xhci_pa; resp->xhci_mem_info.evt_ring.size = PAGE_SIZE; uaudio_qdev->er_phys_addr = xhci_pa; /* dcba */ xhci_pa = usb_get_dcba_dma_addr(subs->dev); if (!xhci_pa) { pr_err("%s:failed to get dcba dma address\n", __func__); goto unmap_er; } if (!uadev[card_num].dcba_iova) { /* mappped per usb device */ va = uaudio_iommu_map(MEM_DCBA, xhci_pa, PAGE_SIZE); if (!va) goto unmap_er; uadev[card_num].dcba_iova = va; uadev[card_num].dcba_size = PAGE_SIZE; } dcba_va = uadev[card_num].dcba_iova; resp->xhci_mem_info.dcba.va = PREPEND_SID_TO_IOVA(dcba_va, uaudio_qdev->sid); resp->xhci_mem_info.dcba.pa = xhci_pa; resp->xhci_mem_info.dcba.size = PAGE_SIZE; /* data transfer ring */ xhci_pa = resp->xhci_mem_info.tr_data.pa; va = uaudio_iommu_map(MEM_XFER_RING, xhci_pa, PAGE_SIZE); if (!va) goto unmap_dcba; 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, xhci_pa, PAGE_SIZE); if (!va) 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) { pr_err("%s: req buf len %d > max buf len %lu, setting %lu\n", __func__, 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) goto unmap_sync; resp->xhci_mem_info.xfer_buff.pa = xfer_buf_pa; resp->xhci_mem_info.xfer_buff.size = len; va = uaudio_iommu_map(MEM_XFER_BUF, xfer_buf_pa, len); if (!va) goto unmap_sync; xfer_buf_va = va; resp->xhci_mem_info.xfer_buff.va = PREPEND_SID_TO_IOVA(va, uaudio_qdev->sid); resp->xhci_mem_info_valid = 1; 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 = kzalloc(sizeof(struct intf_info) * uadev[card_num].num_intf, GFP_KERNEL); if (!uadev[card_num].info) { ret = -ENOMEM; goto unmap_xfer_buf; } 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; /* 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 = xfer_buf_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].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_xfer_buf: uaudio_iommu_unmap(MEM_XFER_BUF, xfer_buf_va, len); unmap_sync: usb_free_coherent(subs->dev, len, xfer_buf, xfer_buf_pa); uaudio_iommu_unmap(MEM_XFER_RING, tr_sync_va, PAGE_SIZE); unmap_data: uaudio_iommu_unmap(MEM_XFER_RING, tr_data_va, PAGE_SIZE); unmap_dcba: uaudio_iommu_unmap(MEM_DCBA, dcba_va, PAGE_SIZE); unmap_er: uaudio_iommu_unmap(MEM_EVENT_RING, IOVA_BASE, 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_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_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_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]); pr_debug("%s: release resources: intf# %d card# %d\n", __func__, dev->info[if_idx].intf_num, dev->card_num); } /* iommu_unmap dcba iova for a usb device */ uaudio_iommu_unmap(MEM_DCBA, dev->dcba_iova, dev->dcba_size); dev->dcba_iova = 0; dev->dcba_size = 0; 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); usb_sec_event_ring_cleanup(dev->udev, uaudio_qdev->intr_num); pr_debug("%s: all audio devices disconnected\n", __func__); } 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}; pr_debug("%s: for card# %d\n", __func__, card_num); if (card_num >= SNDRV_CARDS) { pr_err("%s: invalid card number\n", __func__); return; } mutex_lock(&chip->dev_lock); dev = &uadev[card_num]; /* clean up */ if (!dev->udev) { pr_debug("%s: no clean up required\n", __func__); goto done; } if (atomic_read(&dev->in_use)) { mutex_unlock(&chip->dev_lock); pr_debug("%s: sending qmi indication disconnect\n", __func__); disconnect_ind.dev_event = USB_AUDIO_DEV_DISCONNECT_V01; disconnect_ind.slot_id = dev->udev->slot_id; ret = qmi_send_ind(svc->uaudio_svc_hdl, svc->curr_conn, &uaudio_stream_ind_desc, &disconnect_ind, sizeof(disconnect_ind)); if (ret < 0) { pr_err("%s: qmi send failed wiht err: %d\n", __func__, ret); return; } ret = wait_event_interruptible(dev->disconnect_wq, !atomic_read(&dev->in_use)); if (ret < 0) { pr_debug("%s: failed with ret %d\n", __func__, ret); return; } 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); pr_debug("%s for dev %pK\n", __func__, 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); pr_debug("%s: all audio devices disconnected\n", __func__); } 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 handle_uaudio_stream_req(void *req_h, void *req) { 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; int pcm_format; u8 pcm_card_num, pcm_dev_num, direction; int info_idx = -EINVAL, ret = 0; req_msg = (struct qmi_uaudio_stream_req_msg_v01 *)req; if (!req_msg->audio_format_valid || !req_msg->bit_rate_valid || !req_msg->number_of_ch_valid || !req_msg->xfer_buff_size_valid) { pr_err("%s: invalid request msg\n", __func__); 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; pr_debug("%s:card#:%d dev#:%d dir:%d en:%d fmt:%d rate:%d #ch:%d\n", __func__, 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) { pr_err("%s: invalid card # %u", __func__, pcm_card_num); ret = -EINVAL; goto response; } pcm_format = map_pcm_format(req_msg->audio_format); if (pcm_format == -EINVAL) { pr_err("%s: unsupported pcm format received %d\n", __func__, 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)) { pr_err("%s: can't find substream for card# %u, dev# %u dir%u\n", __func__, 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) { pr_err("%s interface# %d already in use card# %d\n", __func__, 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; uadev[pcm_card_num].ctrl_intf = chip->ctrl_intf; ret = snd_usb_enable_audio_stream(subs, 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) { if (info_idx >= 0) { mutex_lock(&chip->dev_lock); info = &uadev[pcm_card_num].info[info_idx]; uaudio_dev_intf_cleanup(uadev[pcm_card_num].udev, info); pr_debug("%s:release resources: intf# %d card# %d\n", __func__, subs->interface, pcm_card_num); mutex_unlock(&chip->dev_lock); } if (atomic_read(&uadev[pcm_card_num].in_use)) kref_put(&uadev[pcm_card_num].kref, uaudio_dev_release); } 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_resp_from_cb(svc->uaudio_svc_hdl, svc->curr_conn, req_h, &uaudio_stream_resp_desc, &resp, sizeof(resp)); svc->t_resp_sent = ktime_get(); pr_debug("%s: t_resp sent - t_req recvd (in ms) %lld\n", __func__, ktime_to_ms(ktime_sub(svc->t_resp_sent, svc->t_request_recvd))); return ret; } static int uaudio_qmi_svc_connect_cb(struct qmi_handle *handle, void *conn_h) { struct uaudio_qmi_svc *svc = uaudio_svc; if (svc->uaudio_svc_hdl != handle || !conn_h) { pr_err("%s: handle mismatch\n", __func__); return -EINVAL; } if (svc->curr_conn) { pr_err("%s: Service is busy\n", __func__); return -ECONNREFUSED; } svc->curr_conn = conn_h; return 0; } 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)) { pr_debug("%s:no subs for c#%u, dev#%u dir%u\n", __func__, info->pcm_card_num, info->pcm_dev_num, info->direction); continue; } snd_usb_enable_audio_stream(subs, 0); } atomic_set(&uadev[idx].in_use, 0); mutex_lock(&chip->dev_lock); uaudio_dev_cleanup(&uadev[idx]); mutex_unlock(&chip->dev_lock); } } static int uaudio_qmi_svc_disconnect_cb(struct qmi_handle *handle, void *conn_h) { struct uaudio_qmi_svc *svc = uaudio_svc; if (svc->uaudio_svc_hdl != handle || svc->curr_conn != conn_h) { pr_err("%s: handle mismatch\n", __func__); return -EINVAL; } svc->curr_conn = NULL; queue_work(svc->uaudio_wq, &svc->qmi_disconnect_work); return 0; } static int uaudio_qmi_svc_req_cb(struct qmi_handle *handle, void *conn_h, void *req_h, unsigned int msg_id, void *req) { int ret; struct uaudio_qmi_svc *svc = uaudio_svc; if (svc->uaudio_svc_hdl != handle || svc->curr_conn != conn_h) { pr_err("%s: handle mismatch\n", __func__); return -EINVAL; } switch (msg_id) { case QMI_UAUDIO_STREAM_REQ_V01: ret = handle_uaudio_stream_req(req_h, req); break; default: ret = -ENOTSUPP; break; } return ret; } static int uaudio_qmi_svc_req_desc_cb(unsigned int msg_id, struct msg_desc **req_desc) { int ret; pr_debug("%s: msg_id %d\n", __func__, msg_id); switch (msg_id) { case QMI_UAUDIO_STREAM_REQ_V01: *req_desc = &uaudio_stream_req_desc; ret = sizeof(struct qmi_uaudio_stream_req_msg_v01); break; default: ret = -ENOTSUPP; break; } return ret; } static void uaudio_qmi_svc_recv_msg(struct work_struct *w) { int ret; struct uaudio_qmi_svc *svc = container_of(w, struct uaudio_qmi_svc, recv_msg_work); do { pr_debug("%s: Notified about a Receive Event", __func__); } while ((ret = qmi_recv_msg(svc->uaudio_svc_hdl)) == 0); if (ret != -ENOMSG) pr_err("%s: Error receiving message\n", __func__); } static void uaudio_qmi_svc_ntfy(struct qmi_handle *handle, enum qmi_event_type event, void *priv) { struct uaudio_qmi_svc *svc = uaudio_svc; pr_debug("%s: event %d", __func__, event); svc->t_request_recvd = ktime_get(); switch (event) { case QMI_RECV_MSG: queue_work(svc->uaudio_wq, &svc->recv_msg_work); break; default: break; } } static struct qmi_svc_ops_options uaudio_svc_ops_options = { .version = 1, .service_id = UAUDIO_STREAM_SERVICE_ID_V01, .service_vers = UAUDIO_STREAM_SERVICE_VERS_V01, .connect_cb = uaudio_qmi_svc_connect_cb, .disconnect_cb = uaudio_qmi_svc_disconnect_cb, .req_desc_cb = uaudio_qmi_svc_req_desc_cb, .req_cb = uaudio_qmi_svc_req_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(msm_iommu_get_bus(&pdev->dev)); if (!uaudio_qdev->domain) { dev_err(&pdev->dev, "failed to callocate 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 dcba, xfer ring and xfer buf iova list */ INIT_LIST_HEAD(&uaudio_qdev->dcba_list); uaudio_qdev->curr_dcba_iova = IOVA_DCBA_BASE; uaudio_qdev->dcba_iova_size = SNDRV_CARDS * PAGE_SIZE; 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 = qmi_handle_create(uaudio_qmi_svc_ntfy, NULL); if (!svc->uaudio_svc_hdl) { pr_err("%s: Error creating svc_hdl\n", __func__); ret = -EFAULT; goto destroy_uaudio_wq; } ret = qmi_svc_register(svc->uaudio_svc_hdl, &uaudio_svc_ops_options); if (ret < 0) { pr_err("%s:Error registering uaudio svc %d\n", __func__, ret); goto destroy_svc_handle; } INIT_WORK(&svc->recv_msg_work, uaudio_qmi_svc_recv_msg); INIT_WORK(&svc->qmi_disconnect_work, uaudio_qmi_disconnect_work); uaudio_svc = svc; return 0; destroy_svc_handle: qmi_handle_destroy(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_svc_unregister(svc->uaudio_svc_hdl); flush_workqueue(svc->uaudio_wq); qmi_handle_destroy(svc->uaudio_svc_hdl); destroy_workqueue(svc->uaudio_wq); 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");