summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sound/usb/usb_audio_qmi_svc.c211
1 files changed, 124 insertions, 87 deletions
diff --git a/sound/usb/usb_audio_qmi_svc.c b/sound/usb/usb_audio_qmi_svc.c
index 9f7d4e2cb532..22468eee62db 100644
--- a/sound/usb/usb_audio_qmi_svc.c
+++ b/sound/usb/usb_audio_qmi_svc.c
@@ -68,6 +68,9 @@ struct intf_info {
size_t xfer_buf_size;
phys_addr_t xfer_buf_pa;
u8 *xfer_buf;
+ u8 pcm_card_num;
+ u8 pcm_dev_num;
+ u8 direction;
bool in_use;
};
@@ -115,6 +118,7 @@ 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;
@@ -385,7 +389,7 @@ static void uaudio_iommu_unmap(enum mem_type mtype, unsigned long va,
static int prepare_qmi_response(struct snd_usb_substream *subs,
struct qmi_uaudio_stream_resp_msg_v01 *resp, u32 xfer_buf_len,
- int card_num)
+ int card_num, int pcm_dev_num)
{
int ret = -ENODEV;
struct usb_interface *iface;
@@ -411,6 +415,14 @@ static int prepare_qmi_response(struct snd_usb_substream *subs,
goto err;
}
+ if (uadev[card_num].info &&
+ uadev[card_num].info[subs->interface].in_use) {
+ pr_err("%s interface# %d already in use card# %d\n", __func__,
+ subs->interface, card_num);
+ ret = -EBUSY;
+ goto err;
+ }
+
alts = &iface->altsetting[subs->altset_idx];
altsd = get_iface_desc(alts);
protocol = altsd->bInterfaceProtocol;
@@ -627,12 +639,6 @@ skip_sync:
kref_get(&uadev[card_num].kref);
}
- if (uadev[card_num].info[subs->interface].in_use) {
- pr_err("%s interface# %d already in use card# %d\n", __func__,
- subs->interface, card_num);
- goto unmap_xfer_buf;
- }
-
uadev[card_num].card_num = card_num;
/* cache intf specific info to use it for unmap and free xfer buf */
@@ -644,6 +650,9 @@ skip_sync:
uadev[card_num].info[subs->interface].xfer_buf_pa = xfer_buf_pa;
uadev[card_num].info[subs->interface].xfer_buf_size = len;
uadev[card_num].info[subs->interface].xfer_buf = xfer_buf;
+ uadev[card_num].info[subs->interface].pcm_card_num = card_num;
+ uadev[card_num].info[subs->interface].pcm_dev_num = pcm_dev_num;
+ uadev[card_num].info[subs->interface].direction = subs->direction;
uadev[card_num].info[subs->interface].in_use = true;
set_bit(card_num, &uaudio_qdev->card_slot);
@@ -665,9 +674,71 @@ err:
return ret;
}
-void uaudio_disconnect_cb(struct snd_usb_audio *chip)
+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 ret, if_idx;
+ 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__,
+ if_idx, 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;
@@ -713,57 +784,7 @@ void uaudio_disconnect_cb(struct snd_usb_audio *chip)
mutex_lock(&chip->dev_lock);
}
- /* 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;
- usb_free_coherent(dev->udev,
- dev->info[if_idx].xfer_buf_size,
- dev->info[if_idx].xfer_buf,
- dev->info[if_idx].xfer_buf_pa);
-
- uaudio_iommu_unmap(MEM_XFER_RING,
- dev->info[if_idx].data_xfer_ring_va,
- dev->info[if_idx].data_xfer_ring_size);
- dev->info[if_idx].data_xfer_ring_va = 0;
- dev->info[if_idx].data_xfer_ring_size = 0;
-
- uaudio_iommu_unmap(MEM_XFER_RING,
- dev->info[if_idx].sync_xfer_ring_va,
- dev->info[if_idx].sync_xfer_ring_size);
- dev->info[if_idx].sync_xfer_ring_va = 0;
- dev->info[if_idx].sync_xfer_ring_size = 0;
-
- uaudio_iommu_unmap(MEM_XFER_BUF,
- dev->info[if_idx].xfer_buf_va,
- dev->info[if_idx].xfer_buf_size);
- dev->info[if_idx].xfer_buf_va = 0;
- dev->info[if_idx].xfer_buf_size = 0;
- pr_debug("%s: release resources: intf# %d card# %d\n", __func__,
- if_idx, 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(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;
+ uaudio_dev_cleanup(dev);
done:
mutex_unlock(&chip->dev_lock);
}
@@ -789,7 +810,7 @@ static void uaudio_dev_release(struct kref *kref)
}
/* maps audio format received over QMI to asound.h based pcm format */
-int map_pcm_format(unsigned int fmt_received)
+static int map_pcm_format(unsigned int fmt_received)
{
switch (fmt_received) {
case USB_QMI_PCM_FORMAT_S8:
@@ -903,7 +924,7 @@ static int handle_uaudio_stream_req(void *req_h, void *req)
if (!ret && req_msg->enable)
ret = prepare_qmi_response(subs, &resp, req_msg->xfer_buff_size,
- pcm_card_num);
+ pcm_card_num, pcm_dev_num);
mutex_unlock(&chip->dev_lock);
@@ -912,31 +933,7 @@ response:
if (intf_num >= 0) {
mutex_lock(&chip->dev_lock);
info = &uadev[pcm_card_num].info[intf_num];
- 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(uadev[pcm_card_num].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;
+ uaudio_dev_intf_cleanup(uadev[pcm_card_num].udev, info);
pr_debug("%s:release resources: intf# %d card# %d\n",
__func__, intf_num, pcm_card_num);
mutex_unlock(&chip->dev_lock);
@@ -980,6 +977,43 @@ static int uaudio_qmi_svc_connect_cb(struct qmi_handle *handle,
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)
{
@@ -991,6 +1025,8 @@ static int uaudio_qmi_svc_disconnect_cb(struct qmi_handle *handle,
}
svc->curr_conn = NULL;
+ queue_work(svc->uaudio_wq, &svc->qmi_disconnect_work);
+
return 0;
}
@@ -1195,6 +1231,7 @@ static int uaudio_qmi_svc_init(void)
}
INIT_WORK(&svc->recv_msg_work, uaudio_qmi_svc_recv_msg);
+ INIT_WORK(&svc->qmi_disconnect_work, uaudio_qmi_disconnect_work);
uaudio_svc = svc;