Skip to contentSkip to conversation

USB: cdc-acm: remove unneeded spin_lock_irqsave/restore on write path

PATCH Superseded Greg Kroah-Hartman 2 participants 4 messages Jun 7, 2013

Subsystems (2)
lore
Share & tools
lore

Greg Kroah-Hartman

author maintainer
#0 lore raw

PATCH 1/1 USB: cdc-acm: remove unneeded spin_lock_irqsave/restore on write path

From: Greg Kroah-Hartman <[email protected]>

When writing data we were:
lock
do some work
unlock
call function
lock
do some work
unlock
return
return

It turns out, that "function" was only ever called in the one place, so instead of locking/unlocking for no good reason, just inline the function and only grab the lock once.

This has sped up the pathological case of sending 1 byte packets to a loop-back cdc-acm device from 49600 bytes per second to 50100 bytes a second on my workstation. A tiny increase yes, but noticable, and now the spinlock isn't the hottest thing on the perf graph anymore. Yes, we are still waiting for the hardware for the most part, but getting rid of a spin_lock_irqsave() call for every packet is still a good thing.

And we end up deleting lines of code, always a win overall.

This was found by using a Teensy 3.0 device and the test program and firmware located at:
http://www.pjrc.com/teensy/benchmark_usb_serial_receive.html

---
 drivers/usb/class/cdc-acm.c |   51 +++++++++++++++-----------------------------
 1 file changed, 18 insertions(+), 33 deletions(-)
drivers/usb/class/cdc-acm.c +18 -33
@@ -216,38 +216,6 @@ static int acm_start_wb(struct acm *acm,
 	return rc;
 }
 
-static int acm_write_start(struct acm *acm, int wbn)
-{
-	unsigned long flags;
-	struct acm_wb *wb = &acm->wb[wbn];
-	int rc;
-
-	spin_lock_irqsave(&acm->write_lock, flags);
-	if (!acm->dev) {
-		wb->use = 0;
-		spin_unlock_irqrestore(&acm->write_lock, flags);
-		return -ENODEV;
-	}
-
-	dev_vdbg(&acm->data->dev, "%s - susp_count %d\n", __func__,
-							acm->susp_count);
-	usb_autopm_get_interface_async(acm->control);
-	if (acm->susp_count) {
-		if (!acm->delayed_wb)
-			acm->delayed_wb = wb;
-		else
-			usb_autopm_put_interface_async(acm->control);
-		spin_unlock_irqrestore(&acm->write_lock, flags);
-		return 0;	/* A white lie */
-	}
-	usb_mark_last_busy(acm->dev);
-
-	rc = acm_start_wb(acm, wb);
-	spin_unlock_irqrestore(&acm->write_lock, flags);
-
-	return rc;
-
-}
 /*
  * attributes exported through sysfs
  */
@@ -653,13 +621,30 @@ static int acm_tty_write(struct tty_stru
 	}
 	wb = &acm->wb[wbn];
 
+	if (!acm->dev) {
+		wb->use = 0;
+		spin_unlock_irqrestore(&acm->write_lock, flags);
+		return -ENODEV;
+	}
+
 	count = (count > acm->writesize) ? acm->writesize : count;
 	dev_vdbg(&acm->data->dev, "%s - write %d\n", __func__, count);
 	memcpy(wb->buf, buf, count);
 	wb->len = count;
 	spin_unlock_irqrestore(&acm->write_lock, flags);
 
-	stat = acm_write_start(acm, wbn);
+	usb_autopm_get_interface_async(acm->control);
+	if (acm->susp_count) {
+		if (!acm->delayed_wb)
+			acm->delayed_wb = wb;
+		else
+			usb_autopm_put_interface_async(acm->control);
+		spin_unlock_irqrestore(&acm->write_lock, flags);
+		return count;	/* A white lie */
+	}
+	usb_mark_last_busy(acm->dev);
+
+	stat = acm_start_wb(acm, wb);
 	if (stat < 0)
 		return stat;
 	return count;
#1 lore raw

Hello.

Quoted 39 lines · Greg Kroah-Hartman

From: Greg Kroah-Hartman <[email protected]>

When writing data we were:
lock
do some work
unlock
call function
lock
do some work
unlock
return
return

It turns out, that "function" was only ever called in the one place, so instead of locking/unlocking for no good reason, just inline the function and only grab the lock once.

This has sped up the pathological case of sending 1 byte packets to a loop-back cdc-acm device from 49600 bytes per second to 50100 bytes a second on my workstation. A tiny increase yes, but noticable, and now the spinlock isn't the hottest thing on the perf graph anymore. Yes, we are still waiting for the hardware for the most part, but getting rid of a spin_lock_irqsave() call for every packet is still a good thing.

And we end up deleting lines of code, always a win overall.

This was found by using a Teensy 3.0 device and the test program and firmware located at:
http://www.pjrc.com/teensy/benchmark_usb_serial_receive.html

---
  drivers/usb/class/cdc-acm.c |   51 +++++++++++++++-----------------------------
  1 file changed, 18 insertions(+), 33 deletions(-)
drivers/usb/class/cdc-acm.c +0 -0

[...]

Quoted 14 lines
drivers/usb/class/cdc-acm.c +6 -0
@@ -653,13 +621,30 @@ static int acm_tty_write(struct tty_stru
  	}
  	wb = &acm->wb[wbn];
 
+	if (!acm->dev) {
+		wb->use = 0;
+		spin_unlock_irqrestore(&acm->write_lock, flags);
+		return -ENODEV;
+	}
+
  	count = (count > acm->writesize) ? acm->writesize : count;
  	dev_vdbg(&acm->data->dev, "%s - write %d\n", __func__, count);
  	memcpy(wb->buf, buf, count);
  	wb->len = count;

drivers/usb/class/cdc-acm.c static int acm_tty_write(struct tty_stru · line 642

Quoted 10 lines
drivers/usb/class/cdc-acm.c +7 -1
  	spin_unlock_irqrestore(&acm->write_lock, flags);
 
-	stat = acm_write_start(acm, wbn);
+	usb_autopm_get_interface_async(acm->control);
+	if (acm->susp_count) {
+		if (!acm->delayed_wb)
+			acm->delayed_wb = wb;
+		else
+			usb_autopm_put_interface_async(acm->control);
+		spin_unlock_irqrestore(&acm->write_lock, flags);

But you've already dropped it above the *if* and not taken again?

drivers/usb/class/cdc-acm.c static int acm_tty_write(struct tty_stru · line 650

Quoted 9 lines
drivers/usb/class/cdc-acm.c +5 -0
+		return count;	/* A white lie */
+	}
+	usb_mark_last_busy(acm->dev);
+
+	stat = acm_start_wb(acm, wb);
  	if (stat < 0)
  		return stat;
  	return count;

WBR, Sergei

author maintainer
#2 lore raw
Quoted 60 lines · Sergei Shtylyov

Hello.

Quoted 39 lines · Greg Kroah-Hartman

From: Greg Kroah-Hartman <[email protected]>

When writing data we were:
lock
do some work
unlock
call function
lock
do some work
unlock
return
return

It turns out, that "function" was only ever called in the one place, so instead of locking/unlocking for no good reason, just inline the function and only grab the lock once.

This has sped up the pathological case of sending 1 byte packets to a loop-back cdc-acm device from 49600 bytes per second to 50100 bytes a second on my workstation. A tiny increase yes, but noticable, and now the spinlock isn't the hottest thing on the perf graph anymore. Yes, we are still waiting for the hardware for the most part, but getting rid of a spin_lock_irqsave() call for every packet is still a good thing.

And we end up deleting lines of code, always a win overall.

This was found by using a Teensy 3.0 device and the test program and firmware located at:
http://www.pjrc.com/teensy/benchmark_usb_serial_receive.html

---
 drivers/usb/class/cdc-acm.c |   51 +++++++++++++++-----------------------------
 1 file changed, 18 insertions(+), 33 deletions(-)
drivers/usb/class/cdc-acm.c +0 -0

[...]

Quoted 16 lines
drivers/usb/class/cdc-acm.c +7 -1
@@ -653,13 +621,30 @@ static int acm_tty_write(struct tty_stru
 	}
 	wb = &acm->wb[wbn];
+	if (!acm->dev) {
+		wb->use = 0;
+		spin_unlock_irqrestore(&acm->write_lock, flags);
+		return -ENODEV;
+	}
+
 	count = (count > acm->writesize) ? acm->writesize : count;
 	dev_vdbg(&acm->data->dev, "%s - write %d\n", __func__, count);
 	memcpy(wb->buf, buf, count);
 	wb->len = count;
 	spin_unlock_irqrestore(&acm->write_lock, flags);
-	stat = acm_write_start(acm, wbn);
+	usb_autopm_get_interface_async(acm->control);
Quoted 8 lines

drivers/usb/class/cdc-acm.c static int acm_tty_write(struct tty_stru · line 642

Quoted 6 lines
drivers/usb/class/cdc-acm.c +6 -0
+	if (acm->susp_count) {
+		if (!acm->delayed_wb)
+			acm->delayed_wb = wb;
+		else
+			usb_autopm_put_interface_async(acm->control);
+		spin_unlock_irqrestore(&acm->write_lock, flags);

But you've already dropped it above the *if* and not taken again?

Oops, it needs to move down a bit further, let me respin this...

greg k-h

author maintainer
#3 lore raw

PATCH v2 1/1 USB: cdc-acm: remove unneeded spin_lock_irqsave/restore on write path

In reply to Greg Kroah-Hartman

From: Greg Kroah-Hartman <[email protected]>

When writing data we were:
lock
do some work
unlock
call function
lock
do some work
unlock
return
return

It turns out, that "function" was only ever called in the one place, so instead of locking/unlocking for no good reason, just inline the function and only grab the lock once.

This has sped up the pathological case of sending 1 byte packets to a loop-back cdc-acm device from 49600 bytes per second to 50100 bytes a second on my workstation. A tiny increase yes, but noticable, and now the spinlock isn't the hottest thing on the perf graph anymore. Yes, we are still waiting for the hardware for the most part, but getting rid of a spinlock_irq_save() call for every packet is still a good thing.

And we end up deleting lines of code, always a win overall.

This was found by using a Teensy 3.0 device and the test program and firmware located at:
http://www.pjrc.com/teensy/benchmark_usb_serial_receive.html

---
Changes v1 - v2:
- move unlock farther down the call to where it needed to be.

 drivers/usb/class/cdc-acm.c |   52 ++++++++++++++++----------------------------
 1 file changed, 19 insertions(+), 33 deletions(-)
drivers/usb/class/cdc-acm.c +19 -33
@@ -216,38 +216,6 @@ static int acm_start_wb(struct acm *acm,
 	return rc;
 }
 
-static int acm_write_start(struct acm *acm, int wbn)
-{
-	unsigned long flags;
-	struct acm_wb *wb = &acm->wb[wbn];
-	int rc;
-
-	spin_lock_irqsave(&acm->write_lock, flags);
-	if (!acm->dev) {
-		wb->use = 0;
-		spin_unlock_irqrestore(&acm->write_lock, flags);
-		return -ENODEV;
-	}
-
-	dev_vdbg(&acm->data->dev, "%s - susp_count %d\n", __func__,
-							acm->susp_count);
-	usb_autopm_get_interface_async(acm->control);
-	if (acm->susp_count) {
-		if (!acm->delayed_wb)
-			acm->delayed_wb = wb;
-		else
-			usb_autopm_put_interface_async(acm->control);
-		spin_unlock_irqrestore(&acm->write_lock, flags);
-		return 0;	/* A white lie */
-	}
-	usb_mark_last_busy(acm->dev);
-
-	rc = acm_start_wb(acm, wb);
-	spin_unlock_irqrestore(&acm->write_lock, flags);
-
-	return rc;
-
-}
 /*
  * attributes exported through sysfs
  */
@@ -653,13 +621,31 @@ static int acm_tty_write(struct tty_stru
 	}
 	wb = &acm->wb[wbn];
 
+	if (!acm->dev) {
+		wb->use = 0;
+		spin_unlock_irqrestore(&acm->write_lock, flags);
+		return -ENODEV;
+	}
+
 	count = (count > acm->writesize) ? acm->writesize : count;
 	dev_vdbg(&acm->data->dev, "%s - write %d\n", __func__, count);
 	memcpy(wb->buf, buf, count);
 	wb->len = count;
+
+	usb_autopm_get_interface_async(acm->control);
+	if (acm->susp_count) {
+		if (!acm->delayed_wb)
+			acm->delayed_wb = wb;
+		else
+			usb_autopm_put_interface_async(acm->control);
+		spin_unlock_irqrestore(&acm->write_lock, flags);
+		return count;	/* A white lie */
+	}
+	usb_mark_last_busy(acm->dev);
+
+	stat = acm_start_wb(acm, wb);
 	spin_unlock_irqrestore(&acm->write_lock, flags);
 
-	stat = acm_write_start(acm, wbn);
 	if (stat < 0)
 		return stat;
 	return count;