+config SND_USB_AUDIO_QMI
- tristate "Qualcomm Audio Offload driver"
- depends on QCOM_QMI_HELPERS && SND_USB_AUDIO && USB_XHCI_SIDEBAND
- select SND_PCM
This select is not needed:
config SND_USB_AUDIO tristate "USB Audio/MIDI driver" select SND_HWDEP select SND_RAWMIDI select SND_PCM
+#include <linux/ctype.h> +#include <linux/moduleparam.h> +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/init.h>
alphabetical order?
+#include <linux/usb/hcd.h> +#include <linux/usb/xhci-sideband.h> +#include <linux/usb/quirks.h> +#include <linux/usb/audio.h> +#include <linux/usb/audio-v2.h> +#include <linux/usb/audio-v3.h> +#include <linux/soc/qcom/qmi.h> +#include <linux/iommu.h> +#include <linux/dma-mapping.h> +#include <linux/dma-map-ops.h> +#include <sound/q6usboffload.h>
+#include <sound/control.h> +#include <sound/core.h> +#include <sound/info.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h>
+#include <sound/soc.h> +#include <sound/soc-usb.h> +#include "../usbaudio.h" +#include "../card.h" +#include "../endpoint.h" +#include "../helper.h" +#include "../pcm.h" +#include "../format.h" +#include "../power.h" +#include "usb_audio_qmi_v01.h"
+/* Stream disable request timeout during USB device disconnect */ +#define DEV_RELEASE_WAIT_TIMEOUT 10000 /* in ms */
DEV_RELEASE_WAIT_TIMEOUT_MS?
why 10s btw?
+/* Data interval calculation parameters */ +#define BUS_INTERVAL_FULL_SPEED 1000 /* in us */ +#define BUS_INTERVAL_HIGHSPEED_AND_ABOVE 125 /* in us */ +#define MAX_BINTERVAL_ISOC_EP 16
+#define QMI_STREAM_REQ_CARD_NUM_MASK 0xffff0000 +#define QMI_STREAM_REQ_DEV_NUM_MASK 0xff00 +#define QMI_STREAM_REQ_DIRECTION 0xff
+/* iommu resource parameters and management */ +#define PREPEND_SID_TO_IOVA(iova, sid) ((u64)(((u64)(iova)) | \
(((u64)sid) << 32)))
+#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_qmi_dev {
- struct device *dev;
- u32 sid;
- u32 intr_num;
- struct xhci_ring *sec_ring;
- 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;
- /* reference count to number of possible consumers */
- atomic_t qdev_in_use;
- /* idx to last udev card number plugged in */
- unsigned int last_card_num;
+};
+struct uaudio_dev {
- struct usb_device *udev;
- /* audio control interface */
- struct usb_host_interface *ctrl_intf;
- 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;
- struct snd_usb_audio *chip;
- /* xhci sideband */
- struct xhci_sideband *sb;
- /* SoC USB device */
- struct snd_soc_usb_device *sdev;
+};
these structures feel like a set of kitchen sinks... Or a possible copy-paste, I don't know how one would add all these pointers on their own?
Do you really need all this? Is there not a way to use existing substructures?
+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);
+}
This also feels like a generic helper. I don't see what's Qualcomm specific here?
+static unsigned long uaudio_iommu_map(enum mem_type mtype, bool dma_coherent,
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;
- int prot = IOMMU_READ | IOMMU_WRITE;
reverse x-mas tree style?
- if (dma_coherent)
prot |= IOMMU_CACHE;
- 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:
dev_err(uaudio_qdev->dev, "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,
prot, GFP_KERNEL);
if (ret) {
dev_err(uaudio_qdev->dev, "mapping failed ret%d\n", ret);
dev_err(uaudio_qdev->dev,
"type:%d, pa:%pa iova:0x%08lx sg_len:%zu\n",
mtype, &pa_sg, va_sg, sg_len);
uaudio_iommu_unmap(MEM_XFER_BUF, va, size, total_len);
va = 0;
so it's an error but the function returns 0?
goto done;
}
dev_dbg(uaudio_qdev->dev,
"type:%d map pa:%pa to iova:0x%08lx len:%zu offset:%u\n",
mtype, &pa_sg, va_sg, sg_len, sg->offset);
va_sg += sg_len;
total_len += sg_len;
- }
- if (size != total_len) {
dev_err(uaudio_qdev->dev, "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:
- dev_dbg(uaudio_qdev->dev, "type:%d map pa:%pa to iova:0x%08lx size:%zu\n",
mtype, &pa, va, size);
- ret = iommu_map(uaudio_qdev->domain, va, pa, size, prot, GFP_KERNEL);
- if (ret)
dev_err(uaudio_qdev->dev,
"failed to map pa:%pa iova:0x%lx type:%d ret:%d\n",
&pa, va, mtype, ret);
+done:
- return va;
+}
+/* looks up alias, if any, for controller DT node and returns the index */ +static int usb_get_controller_id(struct usb_device *udev) +{
- if (udev->bus->sysdev && udev->bus->sysdev->of_node)
return of_alias_get_id(udev->bus->sysdev->of_node, "usb");
- return -ENODEV;
+}
+/**
- uaudio_dev_intf_cleanup() - cleanup transfer resources
- @udev: usb device
- @info: usb offloading interface
- Cleans up the transfer ring related resources which are assigned per
- endpoint from XHCI. This is invoked when the USB endpoints are no
- longer in use by the adsp.
- */
+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;
+}
+/**
- uaudio_event_ring_cleanup_free() - cleanup secondary event ring
- @dev: usb offload device
- Cleans up the secondary event ring that was requested. This will
- occur when the adsp is no longer transferring data on the USB bus
- across all endpoints.
- */
+static void uaudio_event_ring_cleanup_free(struct uaudio_dev *dev) +{
- clear_bit(dev->chip->card->number, &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);
xhci_sideband_remove_interrupter(uadev[dev->chip->card->number].sb);
- }
+}
+static void uaudio_dev_cleanup(struct uaudio_dev *dev)
there should be a comment that this assumes a mutex is locked in the caller.
+{
- int if_idx;
- if (!dev->udev)
return;
- /* 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]);
dev_dbg(uaudio_qdev->dev, "release resources: intf# %d card# %d\n",
dev->info[if_idx].intf_num, dev->chip->card->number);
- }
- dev->num_intf = 0;
- /* free interface info */
- kfree(dev->info);
- dev->info = NULL;
- uaudio_event_ring_cleanup_free(dev);
- dev->udev = NULL;
+}
+/**
- disable_audio_stream() - disable usb snd endpoints
- @subs: usb substream
- Closes the USB SND endpoints associated with the current audio stream
- used. This will decrement the USB SND endpoint opened reference count.
- */
+static void disable_audio_stream(struct snd_usb_substream *subs) +{
- struct snd_usb_audio *chip = subs->stream->chip;
- snd_usb_hw_free(subs);
- snd_usb_autosuspend(chip);
+}
+/* QMI service disconnect handlers */ +static void 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;
- mutex_lock(&qdev_mutex);
- /* 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;
chip = uadev[idx].chip;
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_substream(info->pcm_card_num,
info->pcm_dev_num,
info->direction);
if (!subs || !chip || atomic_read(&chip->shutdown)) {
dev_err(&subs->dev->dev,
"no sub for c#%u dev#%u dir%u\n",
info->pcm_card_num,
info->pcm_dev_num,
info->direction);
continue;
}
disable_audio_stream(subs);
}
atomic_set(&uadev[idx].in_use, 0);
mutex_lock(&chip->mutex);
uaudio_dev_cleanup(&uadev[idx]);
mutex_unlock(&chip->mutex);
- }
- mutex_unlock(&qdev_mutex);
+}
+/**
- qmi_bye_cb() - qmi bye message callback
- @handle: QMI handle
- @node: id of the dying node
- This callback is invoked when the QMI bye control message is received
- from the QMI client. Handle the message accordingly by ensuring that
- the USB offload path is disabled and cleaned up. At this point, ADSP
- is not utilizing the USB bus.
- */
+static void qmi_bye_cb(struct qmi_handle *handle, unsigned int node) +{
- struct uaudio_qmi_svc *svc = uaudio_svc;
- if (svc->uaudio_svc_hdl != handle)
return;
- if (svc->client_connected && svc->client_sq.sq_node == 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;
- }
+}
+/**
- qmi_svc_disconnect_cb() - qmi client disconnected
- @handle: QMI handle
- @node: id of the dying node
- @port: port of the dying client
- Invoked when the remote QMI client is disconnected. Handle this event
- the same way as when the QMI bye message is received. This will ensure
- the USB offloading path is disabled and cleaned up.
- */
+static void qmi_svc_disconnect_cb(struct qmi_handle *handle,
unsigned int node, unsigned int port)
+{
- struct uaudio_qmi_svc *svc;
- if (uaudio_svc == NULL)
return;
- svc = uaudio_svc;
- if (svc->uaudio_svc_hdl != handle)
return;
- if (svc->client_connected && svc->client_sq.sq_node == node &&
svc->client_sq.sq_port == 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;
this feels racy, shouldn't all these reset values be set in the work function?
- }
+}
+/* QMI client callback handlers from QMI interface */ +static struct qmi_ops uaudio_svc_ops_options = {
- .bye = qmi_bye_cb,
- .del_client = qmi_svc_disconnect_cb,
+};
+/* kref release callback when all streams are disabled */ +static void uaudio_dev_release(struct kref *kref) +{
- struct uaudio_dev *dev = container_of(kref, struct uaudio_dev, kref);
- uaudio_event_ring_cleanup_free(dev);
- atomic_set(&dev->in_use, 0);
- wake_up(&dev->disconnect_wq);
+}
+/**
- enable_audio_stream() - enable usb snd endpoints
- @subs: usb substream
- @pcm_format: pcm format requested
- @channels: number of channels
- @cur_rate: sample rate
- @datainterval: interval
- Opens all USB SND endpoints used for the data interface. This will increment
- the USB SND endpoint's opened count. Requests to keep the interface resumed
- until the audio stream is stopped. Will issue the USB set interface control
- message to enable the data interface.
- */
+static int enable_audio_stream(struct snd_usb_substream *subs,
snd_pcm_format_t pcm_format,
unsigned int channels, unsigned int cur_rate,
int datainterval)
+{
- struct snd_usb_audio *chip = subs->stream->chip;
- struct snd_pcm_hw_params params;
- struct snd_mask *m;
- struct snd_interval *i;
- int ret;
- _snd_pcm_hw_params_any(¶ms);
- m = hw_param_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT);
- snd_mask_leave(m, pcm_format);
- i = hw_param_interval(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS);
- snd_interval_setinteger(i);
- i->min = i->max = channels;
- i = hw_param_interval(¶ms, SNDRV_PCM_HW_PARAM_RATE);
- snd_interval_setinteger(i);
- i->min = i->max = cur_rate;
- pm_runtime_barrier(&chip->intf[0]->dev);
- snd_usb_autoresume(chip);
- ret = snd_usb_hw_params(subs, ¶ms);
- if (ret < 0)
goto put_suspend;
- if (!atomic_read(&chip->shutdown)) {
ret = snd_usb_lock_shutdown(chip);
if (ret < 0)
goto detach_ep;
if (subs->sync_endpoint) {
ret = snd_usb_endpoint_prepare(chip, subs->sync_endpoint);
if (ret < 0)
goto unlock;
}
ret = snd_usb_endpoint_prepare(chip, subs->data_endpoint);
if (ret < 0)
goto unlock;
snd_usb_unlock_shutdown(chip);
dev_dbg(uaudio_qdev->dev,
"selected %s iface:%d altsetting:%d datainterval:%dus\n",
subs->direction ? "capture" : "playback",
subs->cur_audiofmt->iface, subs->cur_audiofmt->altsetting,
(1 << subs->cur_audiofmt->datainterval) *
(subs->dev->speed >= USB_SPEED_HIGH ?
BUS_INTERVAL_HIGHSPEED_AND_ABOVE :
BUS_INTERVAL_FULL_SPEED));
- }
- return 0;
+unlock:
- snd_usb_unlock_shutdown(chip);
+detach_ep:
- snd_usb_hw_free(subs);
+put_suspend:
- snd_usb_autosuspend(chip);
- return ret;
+}
+/* returns usb hcd sysdev */ +static struct device *usb_get_usb_backend(struct usb_device *udev) +{
- if (udev->bus->sysdev && udev->bus->sysdev->of_node)
return udev->bus->sysdev;
- return NULL;
+}
+/**
- prepare_qmi_response() - prepare stream enable response
- @subs: usb substream
- @req_msg: QMI request message
- @resp: QMI response buffer
- @info_idx: usb interface array index
- Prepares the QMI response for a USB QMI stream enable request. Will parse
- out the parameters within the stream enable request, in order to match
- requested audio profile to the ones exposed by the USB device connected.
- In addition, will fetch the XHCI transfer resources needed for the handoff to
- happen. This includes, transfer ring and buffer addresses and secondary event
- ring address. These parameters will be communicated as part of the USB QMI
- stream enable response.
- */
+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;
- struct q6usb_offload *data;
- 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;
- struct sg_table *sgt;
- struct sg_table xfer_buf_sgt;
- struct page *pg;
- bool dma_coherent;
consider simplifying or splitting in different functions? you have 20 lines and probably 30-odd variables. This is a bit beyond what reviewers can handle...
- iface = usb_ifnum_to_if(subs->dev, subs->cur_audiofmt->iface);
- if (!iface) {
dev_err(uaudio_qdev->dev, "interface # %d does not exist\n",
subs->cur_audiofmt->iface);
ret = -ENODEV;
goto err;
- }
- assoc = iface->intf_assoc;
- pcm_dev_num = (req_msg->usb_token & QMI_STREAM_REQ_DEV_NUM_MASK) >> 8;
- xfer_buf_len = req_msg->xfer_buff_size;
- card_num = uaudio_qdev->last_card_num;
- alts = &iface->altsetting[subs->cur_audiofmt->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) {
dev_err(uaudio_qdev->dev,
"%u:%d : no UAC_FORMAT_TYPE desc\n",
subs->cur_audiofmt->iface,
subs->cur_audiofmt->altset_idx);
ret = -ENODEV;
goto err;
}
- }
- if (!uadev[card_num].ctrl_intf) {
dev_err(uaudio_qdev->dev, "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) {
dev_err(uaudio_qdev->dev, "no UAC_HEADER desc\n");
ret = -ENODEV;
goto err;
}
- }
- if (protocol == UAC_VERSION_1) {
struct uac1_ac_header_descriptor *uac1_hdr = hdr_ptr;
as = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL,
UAC_AS_GENERAL);
if (!as) {
dev_err(uaudio_qdev->dev,
"%u:%d : no UAC_AS_GENERAL desc\n",
subs->cur_audiofmt->iface,
subs->cur_audiofmt->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 = le16_to_cpu(uac1_hdr->bcdADC);
resp->usb_audio_spec_revision_valid = 1;
- } else if (protocol == UAC_VERSION_2) {
struct uac2_ac_header_descriptor *uac2_hdr = hdr_ptr;
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 = le16_to_cpu(uac2_hdr->bcdADC);
resp->usb_audio_spec_revision_valid = 1;
- } else if (protocol == UAC_VERSION_3) {
if (assoc->bFunctionSubClass ==
UAC3_FUNCTION_SUBCLASS_FULL_ADC_3_0) {
dev_err(uaudio_qdev->dev, "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:
dev_err(uaudio_qdev->dev,
"%d: %u: Invalid wMaxPacketSize\n",
subs->cur_audiofmt->iface,
subs->cur_audiofmt->altset_idx);
ret = -EINVAL;
goto err;
}
resp->usb_audio_subslot_size_valid = 1;
- } else {
dev_err(uaudio_qdev->dev, "unknown protocol version %x\n",
protocol);
ret = -ENODEV;
goto err;
- }
these 100-odd lines look like duplicated code. Why would we redo the parsing of UAC3 stuff in a QCOM-specific driver?
- 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) {
dev_err(uaudio_qdev->dev, "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;
- ret = xhci_sideband_add_endpoint(uadev[card_num].sb, ep);
- if (ret < 0) {
dev_err(uaudio_qdev->dev, "failed to add data ep to sideband\n");
ret = -ENODEV;
goto err;
- }
- sgt = xhci_sideband_get_endpoint_buffer(uadev[card_num].sb, ep);
- if (!sgt) {
dev_err(uaudio_qdev->dev, "failed to get data ep ring address\n");
ret = -ENODEV;
goto drop_data_ep;
- }
- pg = sg_page(sgt->sgl);
- tr_data_pa = page_to_phys(pg);
- resp->xhci_mem_info.tr_data.pa = sg_dma_address(sgt->sgl);
- sg_free_table(sgt);
- if (subs->sync_endpoint) {
ep = usb_pipe_endpoint(subs->dev, subs->sync_endpoint->pipe);
if (!ep) {
dev_err(uaudio_qdev->dev, "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;
ret = xhci_sideband_add_endpoint(uadev[card_num].sb, ep);
if (ret < 0) {
dev_err(uaudio_qdev->dev,
"failed to add sync ep to sideband\n");
ret = -ENODEV;
goto drop_data_ep;
}
sgt = xhci_sideband_get_endpoint_buffer(uadev[card_num].sb, ep);
if (!sgt) {
dev_err(uaudio_qdev->dev, "failed to get sync ep ring address\n");
ret = -ENODEV;
goto drop_sync_ep;
}
pg = sg_page(sgt->sgl);
tr_sync_pa = page_to_phys(pg);
resp->xhci_mem_info.tr_sync.pa = sg_dma_address(sgt->sgl);
sg_free_table(sgt);
- }
+skip_sync_ep:
- data = snd_soc_usb_find_priv_data(usb_get_usb_backend(subs->dev));
- if (!data)
goto drop_sync_ep;
- uaudio_qdev->domain = data->domain;
- uaudio_qdev->sid = data->sid;
- uaudio_qdev->intr_num = data->intr_num;
- uaudio_qdev->dev = data->dev;
- resp->interrupter_num_valid = 1;
- resp->controller_num_valid = 0;
- ret = usb_get_controller_id(subs->dev);
- if (ret >= 0) {
resp->controller_num = ret;
resp->controller_num_valid = 1;
- }
- /* map xhci data structures PA memory to iova */
- dma_coherent = dev_is_dma_coherent(subs->dev->bus->sysdev);
- /* event ring */
- ret = xhci_sideband_create_interrupter(uadev[card_num].sb, uaudio_qdev->intr_num);
- if (ret < 0) {
dev_err(uaudio_qdev->dev, "failed to fetch interrupter\n");
ret = -ENODEV;
goto drop_sync_ep;
- }
- sgt = xhci_sideband_get_event_buffer(uadev[card_num].sb);
- if (!sgt) {
dev_err(uaudio_qdev->dev, "failed to get event ring address\n");
ret = -ENODEV;
goto free_sec_ring;
- }
- xhci_pa = page_to_phys(sg_page(sgt->sgl));
- resp->xhci_mem_info.evt_ring.pa = sg_dma_address(sgt->sgl);
- sg_free_table(sgt);
- if (!xhci_pa) {
dev_err(uaudio_qdev->dev,
"failed to get sec event ring address\n");
ret = -ENODEV;
goto free_sec_ring;
- }
- resp->interrupter_num = xhci_sideband_interrupter_id(uadev[card_num].sb);
- va = uaudio_iommu_map(MEM_EVENT_RING, dma_coherent, xhci_pa, PAGE_SIZE,
NULL);
- if (!va) {
ret = -ENOMEM;
goto free_sec_ring;
- }
- resp->xhci_mem_info.evt_ring.va = PREPEND_SID_TO_IOVA(va,
uaudio_qdev->sid);
- 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_QMI_DEVICE_SPEED_INVALID_V01) {
ret = -ENODEV;
goto unmap_er;
- }
- resp->speed_info_valid = 1;
- /* data transfer ring */
- va = uaudio_iommu_map(MEM_XFER_RING, dma_coherent, 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, dma_coherent, 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) {
dev_err(uaudio_qdev->dev,
"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, &xfer_buf_sgt, xfer_buf, xfer_buf_pa,
len);
- va = uaudio_iommu_map(MEM_XFER_BUF, dma_coherent, xfer_buf_pa, len,
&xfer_buf_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(&xfer_buf_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].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->cur_audiofmt->iface;
- 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);
+free_sec_ring:
- xhci_sideband_remove_interrupter(uadev[card_num].sb);
+drop_sync_ep:
- if (subs->sync_endpoint)
xhci_sideband_remove_endpoint(uadev[card_num].sb,
usb_pipe_endpoint(subs->dev, subs->sync_endpoint->pipe));
+drop_data_ep:
- xhci_sideband_remove_endpoint(uadev[card_num].sb,
usb_pipe_endpoint(subs->dev, subs->data_endpoint->pipe));
+err:
- return ret;
+}
this is really the largest function I've seen in a while... Can this use helpers or be more modular?
+/**
- handle_uaudio_stream_req() - handle stream enable/disable request
- @handle: QMI client handle
- @sq: qrtr socket
- @txn: QMI transaction context
- @decoded_msg: decoded QMI message
- Main handler for the QMI stream enable/disable requests. This executes the
- corresponding enable/disable stream apis, respectively.
- */
+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;
- u8 pcm_card_num, pcm_dev_num, direction;
- int info_idx = -EINVAL, datainterval = -EINVAL, ret = 0;
- if (!svc->client_connected) {
svc->client_sq = *sq;
svc->client_connected = true;
- }
- mutex_lock(&qdev_mutex);
- 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) {
ret = -EINVAL;
this looks like copy pasted code, this function return void so all uses of 'ret' are not so useful, are they?
goto response;
- }
- if (!uaudio_qdev) {
ret = -EINVAL;
goto response;
- }
- direction = (req_msg->usb_token & QMI_STREAM_REQ_DIRECTION);
- pcm_dev_num = (req_msg->usb_token & QMI_STREAM_REQ_DEV_NUM_MASK) >> 8;
- pcm_card_num = req_msg->enable ? uaudio_qdev->last_card_num :
ffs(uaudio_qdev->card_slot) - 1;
- if (pcm_card_num >= SNDRV_CARDS) {
ret = -EINVAL;
goto response;
- }
- if (req_msg->audio_format > USB_QMI_PCM_FORMAT_U32_BE) {
ret = -EINVAL;
goto response;
- }
- subs = find_substream(pcm_card_num, pcm_dev_num, direction);
- chip = uadev[pcm_card_num].chip;
- if (!subs || !chip || atomic_read(&chip->shutdown)) {
ret = -ENODEV;
goto response;
- }
- info_idx = info_idx_from_ifnum(pcm_card_num, subs->cur_audiofmt ?
subs->cur_audiofmt->iface : -1, req_msg->enable);
- if (atomic_read(&chip->shutdown) || !subs->stream || !subs->stream->pcm
|| !subs->stream->chip) {
ret = -ENODEV;
goto response;
- }
- if (req_msg->enable) {
if (info_idx < 0 || chip->system_suspend) {
ret = -EBUSY;
goto response;
}
- }
- if (req_msg->service_interval_valid) {
ret = get_data_interval_from_si(subs,
req_msg->service_interval);
if (ret == -EINVAL)
goto response;
datainterval = ret;
- }
- uadev[pcm_card_num].ctrl_intf = chip->ctrl_intf;
- if (req_msg->enable) {
ret = enable_audio_stream(subs,
map_pcm_format(req_msg->audio_format),
req_msg->number_of_ch, req_msg->bit_rate,
datainterval);
if (!ret)
ret = prepare_qmi_response(subs, req_msg, &resp,
info_idx);
- } else {
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)
xhci_sideband_stop_endpoint(uadev[pcm_card_num].sb,
ep);
xhci_sideband_remove_endpoint(uadev[pcm_card_num].sb, 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)
xhci_sideband_stop_endpoint(uadev[pcm_card_num].sb,
ep);
xhci_sideband_remove_endpoint(uadev[pcm_card_num].sb, ep);
info->sync_ep_pipe = 0;
}
disable_audio_stream(subs);
- }
+response:
- if (!req_msg->enable && ret != -EINVAL && ret != -ENODEV) {
mutex_lock(&chip->mutex);
if (info_idx >= 0) {
info = &uadev[pcm_card_num].info[info_idx];
uaudio_dev_intf_cleanup(
uadev[pcm_card_num].udev,
info);
}
if (atomic_read(&uadev[pcm_card_num].in_use))
kref_put(&uadev[pcm_card_num].kref,
uaudio_dev_release);
mutex_unlock(&chip->mutex);
- }
- mutex_unlock(&qdev_mutex);
- 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_QMI_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);
ret is not used?
+}
I stopped here...