Re: [PATCH v2] qcom: apr: Make apr callbacks in non-atomic context
From: Bjorn Andersson
Date: Tue May 28 2019 - 21:07:38 EST
On Fri 08 Feb 09:55 PST 2019, Srinivas Kandagatla wrote:
> APR communication with DSP is not atomic in nature.
> Its request-response type. Trying to pretend that these are atomic
> and invoking apr client callbacks directly under atomic/irq context has
> endless issues with soundcard. It makes more sense to convert these
> to nonatomic calls. This also coverts all the dais to be nonatomic.
>
> All the callbacks are now invoked as part of rx work queue.
>
> Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@xxxxxxxxxx>
> Reviewed-by: Bjorn Andersson <bjorn.andersson@xxxxxxxxxx>
Picked up
Thanks,
Bjorn
> ---
> Changes since v1:
> - flush and destroy work queue after removing the device
> to avoid active communication from device. suggested by Bjorn.
>
> drivers/soc/qcom/apr.c | 74 +++++++++++++++++++++++++++++++++++++++---
> 1 file changed, 69 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/soc/qcom/apr.c b/drivers/soc/qcom/apr.c
> index 74f8b9607daa..039e3aa6f5e0 100644
> --- a/drivers/soc/qcom/apr.c
> +++ b/drivers/soc/qcom/apr.c
> @@ -8,6 +8,7 @@
> #include <linux/spinlock.h>
> #include <linux/idr.h>
> #include <linux/slab.h>
> +#include <linux/workqueue.h>
> #include <linux/of_device.h>
> #include <linux/soc/qcom/apr.h>
> #include <linux/rpmsg.h>
> @@ -17,8 +18,18 @@ struct apr {
> struct rpmsg_endpoint *ch;
> struct device *dev;
> spinlock_t svcs_lock;
> + spinlock_t rx_lock;
> struct idr svcs_idr;
> int dest_domain_id;
> + struct workqueue_struct *rxwq;
> + struct work_struct rx_work;
> + struct list_head rx_list;
> +};
> +
> +struct apr_rx_buf {
> + struct list_head node;
> + int len;
> + uint8_t buf[];
> };
>
> /**
> @@ -62,11 +73,7 @@ static int apr_callback(struct rpmsg_device *rpdev, void *buf,
> int len, void *priv, u32 addr)
> {
> struct apr *apr = dev_get_drvdata(&rpdev->dev);
> - uint16_t hdr_size, msg_type, ver, svc_id;
> - struct apr_device *svc = NULL;
> - struct apr_driver *adrv = NULL;
> - struct apr_resp_pkt resp;
> - struct apr_hdr *hdr;
> + struct apr_rx_buf *abuf;
> unsigned long flags;
>
> if (len <= APR_HDR_SIZE) {
> @@ -75,6 +82,34 @@ static int apr_callback(struct rpmsg_device *rpdev, void *buf,
> return -EINVAL;
> }
>
> + abuf = kzalloc(sizeof(*abuf) + len, GFP_ATOMIC);
> + if (!abuf)
> + return -ENOMEM;
> +
> + abuf->len = len;
> + memcpy(abuf->buf, buf, len);
> +
> + spin_lock_irqsave(&apr->rx_lock, flags);
> + list_add_tail(&abuf->node, &apr->rx_list);
> + spin_unlock_irqrestore(&apr->rx_lock, flags);
> +
> + queue_work(apr->rxwq, &apr->rx_work);
> +
> + return 0;
> +}
> +
> +
> +static int apr_do_rx_callback(struct apr *apr, struct apr_rx_buf *abuf)
> +{
> + uint16_t hdr_size, msg_type, ver, svc_id;
> + struct apr_device *svc = NULL;
> + struct apr_driver *adrv = NULL;
> + struct apr_resp_pkt resp;
> + struct apr_hdr *hdr;
> + unsigned long flags;
> + void *buf = abuf->buf;
> + int len = abuf->len;
> +
> hdr = buf;
> ver = APR_HDR_FIELD_VER(hdr->hdr_field);
> if (ver > APR_PKT_VER + 1)
> @@ -132,6 +167,23 @@ static int apr_callback(struct rpmsg_device *rpdev, void *buf,
> return 0;
> }
>
> +static void apr_rxwq(struct work_struct *work)
> +{
> + struct apr *apr = container_of(work, struct apr, rx_work);
> + struct apr_rx_buf *abuf, *b;
> + unsigned long flags;
> +
> + if (!list_empty(&apr->rx_list)) {
> + list_for_each_entry_safe(abuf, b, &apr->rx_list, node) {
> + apr_do_rx_callback(apr, abuf);
> + spin_lock_irqsave(&apr->rx_lock, flags);
> + list_del(&abuf->node);
> + spin_unlock_irqrestore(&apr->rx_lock, flags);
> + kfree(abuf);
> + }
> + }
> +}
> +
> static int apr_device_match(struct device *dev, struct device_driver *drv)
> {
> struct apr_device *adev = to_apr_device(dev);
> @@ -285,6 +337,14 @@ static int apr_probe(struct rpmsg_device *rpdev)
> dev_set_drvdata(dev, apr);
> apr->ch = rpdev->ept;
> apr->dev = dev;
> + apr->rxwq = create_singlethread_workqueue("qcom_apr_rx");
> + if (!apr->rxwq) {
> + dev_err(apr->dev, "Failed to start Rx WQ\n");
> + return -ENOMEM;
> + }
> + INIT_WORK(&apr->rx_work, apr_rxwq);
> + INIT_LIST_HEAD(&apr->rx_list);
> + spin_lock_init(&apr->rx_lock);
> spin_lock_init(&apr->svcs_lock);
> idr_init(&apr->svcs_idr);
> of_register_apr_devices(dev);
> @@ -303,7 +363,11 @@ static int apr_remove_device(struct device *dev, void *null)
>
> static void apr_remove(struct rpmsg_device *rpdev)
> {
> + struct apr *apr = dev_get_drvdata(&rpdev->dev);
> +
> device_for_each_child(&rpdev->dev, NULL, apr_remove_device);
> + flush_workqueue(apr->rxwq);
> + destroy_workqueue(apr->rxwq);
> }
>
> /*
> --
> 2.20.1
>