b52f798c52
Commit 2733ec307c
("sound: usb: Clear in_use if wait_event
fails while disconnect") added ENODEV check with EINVAL in the
error path if chip is removed while handling uaudio stream request.
Instead, both the error codes should be checked exclusively in the
return path to avoid NULL pointer access.
Change-Id: Iebf12b6f13fc6a22c679ed3482759c9173004bb8
Signed-off-by: Pratham Pratap <prathampratap@codeaurora.org>
1450 lines
38 KiB
C
1450 lines
38 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2016-2020, The Linux Foundation. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/usb/audio.h>
|
|
#include <linux/usb/audio-v2.h>
|
|
#include <linux/uaccess.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/core.h>
|
|
#include <sound/asound.h>
|
|
#include <linux/usb.h>
|
|
#include <linux/soc/qcom/qmi.h>
|
|
#include <linux/iommu.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/usb/audio-v3.h>
|
|
#include <linux/ipc_logging.h>
|
|
|
|
#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");
|