Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 25468ac8 authored by Sriharsha Allenki's avatar Sriharsha Allenki Committed by Gerrit - the friendly Code Review server
Browse files

usb: gadget: f_gps: Do not clean up response queue on suspend



The current driver cleans up the response queue on suspend.
This results in data loss. Fix this by not clearing the
responses on suspend and wakeup the host if any response
is available. And the response is also dropped if there
is an enqueuing error on interrupt endpoint which might
lead to data loss. Fix this by not dropping the response
on error.
If the device is suspended and remote wakeup is not
enabled, all the responses will be queued and may lead
to a memory shortage. Fix this by limiting the responses
that can be queued.

CRs-Fixed: 2109762
Change-Id: I4390602875d3e85f333b107b058e299f973c837c
Signed-off-by: default avatarSriharsha Allenki <sallenki@codeaurora.org>
parent 8f09bb72
Loading
Loading
Loading
Loading
+82 −51
Original line number Diff line number Diff line
@@ -22,7 +22,7 @@

#define GPS_NOTIFY_INTERVAL	5
#define GPS_MAX_NOTIFY_SIZE	64

#define GPS_RESP_Q_LENGTH	100

#define ACM_CTRL_DTR	(1 << 0)

@@ -46,11 +46,14 @@ struct f_gps {
	/* control info */
	struct list_head		cpkt_resp_q;
	atomic_t			notify_count;
	atomic_t			resp_q_count;
	unsigned long			cpkts_len;

	/* remote wakeup info */
	bool				is_suspended;
	bool				is_rw_allowed;

	struct delayed_work		wakeup_work;
};

static struct gps_ports {
@@ -146,6 +149,10 @@ static struct usb_gadget_strings *gps_strings[] = {

static void gps_ctrl_response_available(struct f_gps *dev);

static void gps_queue_notify_request(struct f_gps *dev);

static int gps_wakeup_host(struct f_gps *dev);

/* ------- misc functions --------------------*/

static inline struct f_gps *func_to_gps(struct usb_function *f)
@@ -245,9 +252,18 @@ static void gps_unbind(struct usb_configuration *c, struct usb_function *f)

	gps_free_req(dev->notify, dev->notify_req);

	cancel_delayed_work_sync(&dev->wakeup_work);
	kfree(f->name);
}

static void gps_remote_wakeup_work(struct work_struct *data)
{
	struct f_gps *dev = container_of(data, struct f_gps, wakeup_work.work);

	pr_debug("%s: wakeup host\n", __func__);
	gps_wakeup_host(dev);
}

static void gps_purge_responses(struct f_gps *dev)
{
	unsigned long flags;
@@ -265,12 +281,16 @@ static void gps_purge_responses(struct f_gps *dev)
		rmnet_free_ctrl_pkt(cpkt);
	}
	atomic_set(&dev->notify_count, 0);
	atomic_set(&dev->resp_q_count, 0);
	spin_unlock_irqrestore(&dev->lock, flags);
}

#define GPS_WAKEUP_HOST_DELAY msecs_to_jiffies(100)

static void gps_suspend(struct usb_function *f)
{
	struct f_gps *dev = func_to_gps(f);
	unsigned long flags;

	pr_debug("%s: suspending gps function\n", __func__);
	if (f->config->cdev->gadget->speed == USB_SPEED_SUPER)
@@ -278,13 +298,26 @@ static void gps_suspend(struct usb_function *f)
	else
		dev->is_rw_allowed = f->config->cdev->gadget->remote_wakeup;

	gps_purge_responses(dev);
	dev->is_suspended = true;
	spin_lock_irqsave(&dev->lock, flags);
	/*
	 * The notify count is set to zero and the correct count of
	 * notifications for responses received (before suspend + during
	 * suspend) will be handled during resume
	 */
	atomic_set(&dev->notify_count, 0);
	if (dev->is_rw_allowed && (atomic_read(&dev->notify_count) > 0)) {
		pr_debug("%s: queue not empty; wakeup host\n", __func__);
		schedule_delayed_work(&dev->wakeup_work,
					GPS_WAKEUP_HOST_DELAY);
	}
	spin_unlock_irqrestore(&dev->lock, flags);
}

static void gps_resume(struct usb_function *f)
{
	struct f_gps *dev = func_to_gps(f);
	struct list_head *cpkt;

	pr_debug("%s: resume gps function, func_is_supended:%d\n",
			__func__, f->func_is_suspended);
@@ -304,6 +337,8 @@ static void gps_resume(struct usb_function *f)
		return;
	}
	spin_unlock(&dev->lock);

	list_for_each(cpkt, &dev->cpkt_resp_q)
		gps_ctrl_response_available(dev);
}

@@ -381,6 +416,8 @@ static void gps_disable(struct usb_function *f)

	atomic_set(&dev->online, 0);

	cancel_delayed_work(&dev->wakeup_work);

	gps_purge_responses(dev);

	gport_gps_disconnect(dev);
@@ -419,6 +456,7 @@ gps_set_alt(struct usb_function *f, unsigned intf, unsigned alt)

	atomic_set(&dev->online, 1);

	cancel_delayed_work(&dev->wakeup_work);
	/* In case notifications were aborted, but there are pending control
	   packets in the response queue, re-add the notifications */
	list_for_each(cpkt, &dev->cpkt_resp_q)
@@ -427,13 +465,32 @@ gps_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
	return ret;
}

static void gps_queue_notify_request(struct f_gps *dev)
{
	int ret;

	pr_debug("%s: queue new notification[%d]\n",
			__func__, atomic_read(&dev->notify_count));
	ret = usb_ep_queue(dev->notify, dev->notify_req, GFP_ATOMIC);
	if (ret) {
		/*
		 * The modem response thread and the response completion
		 * thread can try to queue the same notification and may
		 * result in EBUSY error
		 */
		if (ret == -EBUSY) {
			pr_debug("%s: notify_count:%u\n",
				__func__, atomic_read(&dev->notify_count));
		}
		pr_debug("ep enqueue error %d\n", ret);
	}
}

static void gps_ctrl_response_available(struct f_gps *dev)
{
	struct usb_request		*req = dev->notify_req;
	struct usb_cdc_notification	*event;
	unsigned long			flags;
	int				ret;
	struct rmnet_ctrl_pkt	*cpkt;

	pr_debug("%s:dev:%pK\n", __func__, dev);

@@ -457,24 +514,8 @@ static void gps_ctrl_response_available(struct f_gps *dev)
	event->wLength = cpu_to_le16(0);
	spin_unlock_irqrestore(&dev->lock, flags);

	ret = usb_ep_queue(dev->notify, dev->notify_req, GFP_ATOMIC);
	if (ret) {
		if (ret == -EBUSY) {
			pr_err("%s: notify_count:%u\n",
				__func__, atomic_read(&dev->notify_count));
			WARN_ON(1);
		}
		spin_lock_irqsave(&dev->lock, flags);
		if (!list_empty(&dev->cpkt_resp_q)) {
			atomic_dec(&dev->notify_count);
			cpkt = list_first_entry(&dev->cpkt_resp_q,
					struct rmnet_ctrl_pkt, list);
			list_del(&cpkt->list);
			gps_free_ctrl_pkt(cpkt);
		}
		spin_unlock_irqrestore(&dev->lock, flags);
		pr_debug("ep enqueue error %d\n", ret);
	}
	gps_queue_notify_request(dev);

}

static void gps_connect(struct grmnet *gr)
@@ -543,19 +584,27 @@ gps_send_cpkt_response(void *gr, void *buf, size_t len)
		return 0;
	}

	if (atomic_read(&dev->resp_q_count) >= GPS_RESP_Q_LENGTH) {
		pr_err_ratelimited("%s: dropping packets as queue is full\n",
					__func__);
		gps_free_ctrl_pkt(cpkt);
		return 0;
	}

	spin_lock_irqsave(&dev->lock, flags);
	list_add_tail(&cpkt->list, &dev->cpkt_resp_q);
	atomic_inc(&dev->resp_q_count);
	spin_unlock_irqrestore(&dev->lock, flags);

	if (dev->is_suspended && dev->is_rw_allowed) {
	if (dev->is_suspended) {
		if (dev->is_rw_allowed) {
			pr_debug("%s: calling gps_wakeup_host\n", __func__);
			gps_wakeup_host(dev);
		goto end;
		}
		return 0;
	}

	gps_ctrl_response_available(dev);

end:
	return 0;
}

@@ -582,8 +631,6 @@ static void gps_notify_complete(struct usb_ep *ep, struct usb_request *req)
{
	struct f_gps *dev = req->context;
	int status = req->status;
	unsigned long		flags;
	struct rmnet_ctrl_pkt	*cpkt;

	pr_debug("%s: dev:%pK port#%d\n", __func__, dev, dev->port_num);

@@ -605,26 +652,7 @@ static void gps_notify_complete(struct usb_ep *ep, struct usb_request *req)
		if (atomic_dec_and_test(&dev->notify_count))
			break;

		status = usb_ep_queue(dev->notify, req, GFP_ATOMIC);
		if (status) {
			if (status == -EBUSY) {
				pr_err("%s: notify_count:%u\n",
					__func__,
					atomic_read(&dev->notify_count));
				WARN_ON(1);
			}

			spin_lock_irqsave(&dev->lock, flags);
			if (!list_empty(&dev->cpkt_resp_q)) {
				atomic_dec(&dev->notify_count);
				cpkt = list_first_entry(&dev->cpkt_resp_q,
						struct rmnet_ctrl_pkt, list);
				list_del(&cpkt->list);
				gps_free_ctrl_pkt(cpkt);
			}
			spin_unlock_irqrestore(&dev->lock, flags);
			pr_debug("ep enqueue error %d\n", status);
		}
		gps_queue_notify_request(dev);
		break;
	}
}
@@ -674,6 +702,7 @@ gps_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
			cpkt = list_first_entry(&dev->cpkt_resp_q,
					struct rmnet_ctrl_pkt, list);
			list_del(&cpkt->list);
			atomic_dec(&dev->resp_q_count);
			spin_unlock(&dev->lock);

			len = min_t(unsigned, w_length, cpkt->len);
@@ -786,6 +815,8 @@ static int gps_bind(struct usb_configuration *c, struct usb_function *f)
			__func__, dev->port_num,
			gadget_is_dualspeed(cdev->gadget) ? "dual" : "full");

	INIT_DELAYED_WORK(&dev->wakeup_work, gps_remote_wakeup_work);

	return 0;

fail: