OXU210 host controller problems

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi,

I'm trying to have this host controller up and running on an ARM
system (S3C2440). The driver in the vanilla kernel (checked up to
2.6.37) seems broken (I tried USB mass storage, an usbnet dongle and a
composite HID keybord/mouse). I had a look and somehow managed to have
the USB mass storage running (it transfered some hundreds of megabytes
in both directions correctly). I'm attaching the patch. I'm really not
satisfied with it because it just goes in an infinite loop if I attach
a HID/usbnet device and cannot report failures in the lower level to
the upper ones. With usbnet dongle I see too many URBs being queued to
the device ( __oxu_urb_enqueue returns -ENOMEM at one point because
there are to many transactions not being finished: I don't know yet if
this is a problem with transactions not being correctly executed (and
not being cleared for timeout either) or it's by design of the usbnet
driver). I tried to throttle this number by testing the pending and
pendingl number I introduces and returning -ENOMEM if they are filling
the OXU210 on-chip memory. It somehow works for a while (I can even do
pings). Unfortunately the CPU load is very high because usbnet is
constantly submitting new URBs and after a while everything blows up.
This second problem is similar to what I see with a HID device:
interrupt transactions fail with a ENOSPC error. I really don't have
any idea why this happens.

Anyway I'm going to investigate the problem now that I have the
data-sheet available. Any idea, advice or "Read That Fine Mailing List
Post" is really appreciated, thanks!

Just another question: I saw in this mailing list's archive that
another driver "ehci-oxu210hp.c" is mentioned in many places. Should I
have a look at this one or Rodolfo's (which lives in mainline) is the
right one to work with?

Thanks!

-- 
Christian Pellegrin, see http://www.evolware.org/chri/
"Real Programmers don't play tennis, or any other sport which requires
you to change clothes. Mountain climbing is OK, and Real Programmers
wear their climbing boots to work in case a mountain should suddenly
spring up in the middle of the computer room."
diff --git a/drivers/usb/host/oxu210hp-hcd.c b/drivers/usb/host/oxu210hp-hcd.c
index 50f57f4..c9719b2 100644
--- a/drivers/usb/host/oxu210hp-hcd.c
+++ b/drivers/usb/host/oxu210hp-hcd.c
@@ -2858,46 +2858,72 @@ static int __oxu_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
 	}
 }
 
-/* This function is responsible for breaking URBs with big data size
+/* These functions are responsible for breaking URBs with big data size
  * into smaller size and processing small urbs in sequence.
  */
-static int oxu_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
-				gfp_t mem_flags)
-{
-	struct oxu_hcd *oxu = hcd_to_oxu(hcd);
+
+#define LOOP_START()				\
+	do {					\
+	unsigned long start = jiffies;		\
+	do {					\
+	while (0)
+
+#define LOOP_END(COND)							\
+	if (COND) schedule();						\
+	if (jiffies > (start + HZ)) {					\
+		printk("%s %d %p/%p stuck %d %d %d\n", __FUNCTION__, __LINE__, urb, murb, COND, atomic_read(&oxu->pending), atomic_read(&oxu->pendingl)); \
+		start = jiffies;					\
+	}								\
+	} while(COND);							\
+	} while(0)
+
+static void oxu_splitter(struct work_struct *w)
+{
+	struct oxu_splitter_work_s *ws = container_of(w, struct oxu_splitter_work_s, w);
+	struct urb *murb = NULL;
+	int i, ret;
 	int num, rem;
+	struct urb *urb = ws->urb;
+	struct usb_hcd *hcd = ws->hcd;
+	struct oxu_hcd *oxu = hcd_to_oxu(hcd);
 	int transfer_buffer_length;
 	void *transfer_buffer;
-	struct urb *murb;
-	int i, ret;
-
-	/* If not bulk pipe just enqueue the URB */
-	if (!usb_pipebulk(urb->pipe))
-		return __oxu_urb_enqueue(hcd, urb, mem_flags);
+	gfp_t mem_flags = ws->memflags;
 
-	/* Otherwise we should verify the USB transfer buffer size! */
 	transfer_buffer = urb->transfer_buffer;
 	transfer_buffer_length = urb->transfer_buffer_length;
 
+	atomic_sub(1, &oxu->pending) ;
+	atomic_sub(transfer_buffer_length, &oxu->pendingl);
+
+	/* If not bulk pipe just enqueue the URB */
+	if (!usb_pipebulk(urb->pipe)) {
+		LOOP_START();
+		ret = __oxu_urb_enqueue(hcd, urb, mem_flags);
+		LOOP_END(ret);
+		return;
+	}
+
 	num = urb->transfer_buffer_length / 4096;
 	rem = urb->transfer_buffer_length % 4096;
 	if (rem != 0)
 		num++;
 
 	/* If URB is smaller than 4096 bytes just enqueue it! */
-	if (num == 1)
-		return __oxu_urb_enqueue(hcd, urb, mem_flags);
-
-	/* Ok, we have more job to do! :) */
+	if (num == 1) {
+		LOOP_START();
+		ret = __oxu_urb_enqueue(hcd, urb, mem_flags);
+		LOOP_END(ret);
+		return;
+	}
 
+	/* Ok, we have more work to do! :) */
 	for (i = 0; i < num - 1; i++) {
 		/* Get free micro URB poll till a free urb is recieved */
 
-		do {
-			murb = (struct urb *) oxu_murb_alloc(oxu);
-			if (!murb)
-				schedule();
-		} while (!murb);
+		LOOP_START();
+		murb = (struct urb *) oxu_murb_alloc(oxu);
+		LOOP_END(!murb);
 
 		/* Coping the urb */
 		memcpy(murb, urb, sizeof(struct urb));
@@ -2914,21 +2940,18 @@ static int oxu_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
 		/* This loop is to guarantee urb to be processed when there's
 		 * not enough resources at a particular time by retrying.
 		 */
-		do {
-			ret  = __oxu_urb_enqueue(hcd, murb, mem_flags);
-			if (ret)
-				schedule();
-		} while (ret);
+		LOOP_START();
+		ret  = __oxu_urb_enqueue(hcd, murb, mem_flags);
+		LOOP_END(ret);
+		murb = NULL;
 	}
 
 	/* Last urb requires special handling  */
 
 	/* Get free micro URB poll till a free urb is recieved */
-	do {
-		murb = (struct urb *) oxu_murb_alloc(oxu);
-		if (!murb)
-			schedule();
-	} while (!murb);
+	LOOP_START();
+	murb = (struct urb *) oxu_murb_alloc(oxu);
+	LOOP_END(!murb);
 
 	/* Coping the urb */
 	memcpy(murb, urb, sizeof(struct urb));
@@ -2942,13 +2965,31 @@ static int oxu_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
 	((struct oxu_murb *) murb)->main = urb;
 	((struct oxu_murb *) murb)->last = 1;
 
-	do {
-		ret = __oxu_urb_enqueue(hcd, murb, mem_flags);
-		if (ret)
-			schedule();
-	} while (ret);
+	LOOP_START();
+	ret = __oxu_urb_enqueue(hcd, murb, mem_flags);
+	LOOP_END(ret);
 
-	return ret;
+	kfree(ws);
+}
+
+static int oxu_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
+				gfp_t mem_flags)
+{
+	struct oxu_hcd *oxu = hcd_to_oxu(hcd);
+	struct oxu_splitter_work_s *ws;
+
+	ws = kmalloc(sizeof(*ws), GFP_ATOMIC);
+	if (!ws)
+		return -ENOMEM;
+	ws->urb = urb;
+	ws->hcd = hcd;
+	ws->memflags = mem_flags;
+	atomic_add(1, &oxu->pending);
+	atomic_add(urb->transfer_buffer_length, &oxu->pendingl);
+	INIT_WORK(&ws->w, oxu_splitter);
+	queue_work(oxu->splitter_wq, &ws->w);
+
+	return 0;
 }
 
 /* Remove from hardware lists.
@@ -3751,6 +3792,9 @@ static struct usb_hcd *oxu_create(struct platform_device *pdev,
 
 	oxu = hcd_to_oxu(hcd);
 	oxu->is_otg = otg;
+	oxu->splitter_wq = create_workqueue(hcd->product_desc);
+	atomic_set(&oxu->pending, 0);
+	atomic_set(&oxu->pendingl, 0);
 
 	ret = usb_add_hcd(hcd, irq, IRQF_SHARED);
 	if (ret < 0)
@@ -3898,6 +3942,11 @@ error_ioremap:
 
 static void oxu_remove(struct platform_device *pdev, struct usb_hcd *hcd)
 {
+	struct oxu_hcd *oxu;
+
+	oxu = hcd_to_oxu(hcd);
+	flush_workqueue(oxu->splitter_wq);
+	destroy_workqueue(oxu->splitter_wq);
 	usb_remove_hcd(hcd);
 	usb_put_hcd(hcd);
 }
diff --git a/drivers/usb/host/oxu210hp.h b/drivers/usb/host/oxu210hp.h
index 1c216ad..8d45b7e 100644
--- a/drivers/usb/host/oxu210hp.h
+++ b/drivers/usb/host/oxu210hp.h
@@ -369,6 +369,15 @@ struct oxu_murb {
 	u8			last;
 };
 
+struct oxu_splitter_work_s {
+	struct work_struct      w;
+	struct urb              *urb;
+	struct usb_hcd          *hcd;
+	gfp_t                   memflags;
+};
+
+static void oxu_splitter(struct work_struct *w);
+
 struct oxu_hcd {				/* one per controller */
 	unsigned int		is_otg:1;
 
@@ -428,6 +437,9 @@ struct oxu_hcd {				/* one per controller */
 						 */
 	struct oxu_murb		*murb_pool;	/* murb per split big urb */
 	unsigned urb_len;
+	struct workqueue_struct *splitter_wq;   /* split urbs in murbs */
+	atomic_t pending;
+	atomic_t pendingl;
 
 	u8			sbrn;		/* packed release number */
 };

[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux