Skip to contentSkip to conversation

arm64: optimize memcpy_{from,to}io() and memset_io()

PATCH New Joonwoo Park 3 participants 11 messages Jul 30, 2014 – Oct 20, 2014 arm64

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 arm64: optimize memcpy_{from,to}io() and memset_io()

Optimize memcpy_{from,to}io() and memset_io() by transferring in 64 bit as much as possible with minimized barrier usage. This simplest optimization brings faster throughput compare to current byte-by-byte read and write with barrier in the loop. Code's skeleton is taken from the powerpc.

---
 arch/arm64/kernel/io.c | 72 +++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 62 insertions(+), 10 deletions(-)
arch/arm64/kernel/io.c +62 -10
index 7d37ead..c0e3ab1 100644
@@ -20,18 +20,34 @@
 #include <linux/types.h>
 #include <linux/io.h>
 
+#define IO_CHECK_ALIGN(v, a) ((((unsigned long)(v)) & ((a) - 1)) == 0)
+
 /*
  * Copy data from IO memory space to "real" memory space.
  */
 void __memcpy_fromio(void *to, const volatile void __iomem *from, size_t count)
 {
-	unsigned char *t = to;
-	while (count) {
+	while (count && (!IO_CHECK_ALIGN(from, 8) || !IO_CHECK_ALIGN(to, 8))) {
+		*(u8 *)to = readb_relaxed(from);
+		from++;
+		to++;
 		count--;
-		*t = readb(from);
-		t++;
+	}
+
+	while (count >= 8) {
+		*(u64 *)to = readq_relaxed(from);
+		from += 8;
+		to += 8;
+		count -= 8;
+	}
+
+	while (count) {
+		*(u8 *)to = readb_relaxed(from);
 		from++;
+		to++;
+		count--;
 	}
+	__iormb();
 }
 EXPORT_SYMBOL(__memcpy_fromio);
 
@@ -40,12 +56,28 @@ EXPORT_SYMBOL(__memcpy_fromio);
  */
 void __memcpy_toio(volatile void __iomem *to, const void *from, size_t count)
 {
-	const unsigned char *f = from;
+	void *p = (void __force *)from;
+
+	__iowmb();
+	while (count && (!IO_CHECK_ALIGN(p, 8) || !IO_CHECK_ALIGN(from, 8))) {
+		writeb_relaxed(*(volatile u8 *)from, p);
+		from++;
+		p++;
+		count--;
+	}
+
+	while (count >= 8) {
+		writeq_relaxed(*(volatile u64 *)from, p);
+		from += 8;
+		p += 8;
+		count -= 8;
+	}
+
 	while (count) {
+		writeb_relaxed(*(volatile u8 *)from, p);
+		from++;
+		p++;
 		count--;
-		writeb(*f, to);
-		f++;
-		to++;
 	}
 }
 EXPORT_SYMBOL(__memcpy_toio);
@@ -55,10 +87,30 @@ EXPORT_SYMBOL(__memcpy_toio);
  */
 void __memset_io(volatile void __iomem *dst, int c, size_t count)
 {
+	void *p = (void __force *)dst;
+	u64 qc = c;
+
+	qc |= qc << 8;
+	qc |= qc << 16;
+	qc |= qc << 32;
+
+	__iowmb();
+	while (count && !IO_CHECK_ALIGN(p, 8)) {
+		writeb_relaxed(c, p);
+		p++;
+		count--;
+	}
+
+	while (count >= 8) {
+		writeq_relaxed(c, p);
+		p += 8;
+		count -= 8;
+	}
+
 	while (count) {
+		writeb_relaxed(c, p);
+		p++;
 		count--;
-		writeb(c, dst);
-		dst++;
 	}
 }
 EXPORT_SYMBOL(__memset_io);
Signature · 2 lines
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
hosted by The Linux Foundation
#1 lore raw

PATCH 1/1 arm64: optimize memcpy_{from,to}io() and memset_io()

+ Catalin, Will

Thanks,
Joonwoo

Quoted 124 lines · Joonwoo Park

Optimize memcpy_{from,to}io() and memset_io() by transferring in 64 bit as much as possible with minimized barrier usage. This simplest optimization brings faster throughput compare to current byte-by-byte read and write with barrier in the loop. Code's skeleton is taken from the powerpc.

---
 arch/arm64/kernel/io.c | 72 +++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 62 insertions(+), 10 deletions(-)
arch/arm64/kernel/io.c +62 -10
index 7d37ead..c0e3ab1 100644
@@ -20,18 +20,34 @@
 #include <linux/types.h>
 #include <linux/io.h>
 
+#define IO_CHECK_ALIGN(v, a) ((((unsigned long)(v)) & ((a) - 1)) == 0)
+
 /*
  * Copy data from IO memory space to "real" memory space.
  */
 void __memcpy_fromio(void *to, const volatile void __iomem *from, size_t count)
 {
-	unsigned char *t = to;
-	while (count) {
+	while (count && (!IO_CHECK_ALIGN(from, 8) || !IO_CHECK_ALIGN(to, 8))) {
+		*(u8 *)to = readb_relaxed(from);
+		from++;
+		to++;
 		count--;
-		*t = readb(from);
-		t++;
+	}
+
+	while (count >= 8) {
+		*(u64 *)to = readq_relaxed(from);
+		from += 8;
+		to += 8;
+		count -= 8;
+	}
+
+	while (count) {
+		*(u8 *)to = readb_relaxed(from);
 		from++;
+		to++;
+		count--;
 	}
+	__iormb();
 }
 EXPORT_SYMBOL(__memcpy_fromio);
 
@@ -40,12 +56,28 @@ EXPORT_SYMBOL(__memcpy_fromio);
  */
 void __memcpy_toio(volatile void __iomem *to, const void *from, size_t count)
 {
-	const unsigned char *f = from;
+	void *p = (void __force *)from;
+
+	__iowmb();
+	while (count && (!IO_CHECK_ALIGN(p, 8) || !IO_CHECK_ALIGN(from, 8))) {
+		writeb_relaxed(*(volatile u8 *)from, p);
+		from++;
+		p++;
+		count--;
+	}
+
+	while (count >= 8) {
+		writeq_relaxed(*(volatile u64 *)from, p);
+		from += 8;
+		p += 8;
+		count -= 8;
+	}
+
 	while (count) {
+		writeb_relaxed(*(volatile u8 *)from, p);
+		from++;
+		p++;
 		count--;
-		writeb(*f, to);
-		f++;
-		to++;
 	}
 }
 EXPORT_SYMBOL(__memcpy_toio);
@@ -55,10 +87,30 @@ EXPORT_SYMBOL(__memcpy_toio);
  */
 void __memset_io(volatile void __iomem *dst, int c, size_t count)
 {
+	void *p = (void __force *)dst;
+	u64 qc = c;
+
+	qc |= qc << 8;
+	qc |= qc << 16;
+	qc |= qc << 32;
+
+	__iowmb();
+	while (count && !IO_CHECK_ALIGN(p, 8)) {
+		writeb_relaxed(c, p);
+		p++;
+		count--;
+	}
+
+	while (count >= 8) {
+		writeq_relaxed(c, p);
+		p += 8;
+		count -= 8;
+	}
+
 	while (count) {
+		writeb_relaxed(c, p);
+		p++;
 		count--;
-		writeb(c, dst);
-		dst++;
 	}
 }
 EXPORT_SYMBOL(__memset_io);
Signature · 2 lines
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
hosted by The Linux Foundation
Signature · 2 lines
 Sent by an employee of the Qualcomm Innovation Center, Inc.
 The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
#2 lore raw

PATCH 1/1 arm64: optimize memcpy_{from,to}io() and memset_io()

Quoted 9 lines · Joonwoo Park

+ Catalin, Will

Thanks,
Joonwoo

Quoted 4 lines · Joonwoo Park

Optimize memcpy_{from,to}io() and memset_io() by transferring in 64 bit as much as possible with minimized barrier usage. This simplest optimization brings faster throughput compare to current byte-by-byte read and write with barrier in the loop. Code's skeleton is taken from the powerpc.

Hmm, I've never really understood the use-case for memcpy_{to,from}io on ARM, so getting to the bottom of that would help in reviewing this patch.

Can you point me at the drivers which are using this for ARM please? Doing a blind byte-by-byte copy could easily cause problems with some peripherals, so there must be an underlying assumption somewhere about how this is supposed to work.

Will

#3 lore raw

PATCH 1/1 arm64: optimize memcpy_{from,to}io() and memset_io()

Quoted 11 lines · Will Deacon
Quoted 9 lines · Joonwoo Park

+ Catalin, Will

Thanks,
Joonwoo

Quoted 4 lines · Joonwoo Park

Optimize memcpy_{from,to}io() and memset_io() by transferring in 64 bit as much as possible with minimized barrier usage. This simplest optimization brings faster throughput compare to current byte-by-byte read and write with barrier in the loop. Code's skeleton is taken from the powerpc.

Quoted 4 lines

Hmm, I've never really understood the use-case for memcpy_{to,from}io on ARM, so getting to the bottom of that would help in reviewing this patch.

Can you point me at the drivers which are using this for ARM please? Doing a

Sure. This peripheral-loader.c driver now moved under drivers/soc/ so it can be used for ARM and ARM64.
https://android.googlesource.com/kernel/msm.git/+/db34f44bcba24345d26b8a4b8137cf94d70afa73/arch/arm/mach-msm/peripheral-loader.c
static int load_segment(const struct elf32_phdr *phdr, unsigned num, struct pil_device *pil) {
<snip>

	while (count > 0) {
		int size;
		u8 __iomem *buf;
		size = min_t(size_t, IOMAP_SIZE, count);
		buf = ioremap(paddr, size);
	}
	<snip>
	memset(buf, 0, size);

<snip>
}
As you can see the function load_segment() does ioremap() followed by memset() and memcpy() which can cause unaligned multi-byte (maybe ARM64 traps 8byte unaligned access?) write to device memory. Because of this I was fixing the driver to use memset_io() and memcpy_io() but the existing implementations were too slow compare to the one I'm proposing.

Quoted 3 lines

blind byte-by-byte copy could easily cause problems with some peripherals, so there must be an underlying assumption somewhere about how this is supposed to work.

Would you mind to explain more about the problem with byte-by-byte copying you're worried about?
I thought byte-by-byte copy always safe with regard to aligned access and that's the reason existing implementation does byte-by-byte copy. I can imagine there are some peripherals don't allow per-byte access. But if that is the case, should they not use memset_io() and memcpy_{from,to}io() anyway?

Also I found this. http://lists.infradead.org/pipermail/linux-arm-kernel/2014-February/234729.html
Looks like Catalin also had a similar idea with mine.

Thanks,
Joonwoo

Quoted 2 lines

Will

Signature · 2 lines
The Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
hosted by The Linux Foundation
#4 lore raw

PATCH 1/1 arm64: optimize memcpy_{from,to}io() and memset_io()

Quoted 26 lines · Joonwoo Park
Quoted 11 lines · Will Deacon
Quoted 5 lines · Joonwoo Park
Quoted 4 lines · Joonwoo Park

Optimize memcpy_{from,to}io() and memset_io() by transferring in 64 bit as much as possible with minimized barrier usage. This simplest optimization brings faster throughput compare to current byte-by-byte read and write with barrier in the loop. Code's skeleton is taken from the powerpc.

Hmm, I've never really understood the use-case for memcpy_{to,from}io on ARM, so getting to the bottom of that would help in reviewing this patch.

Can you point me at the drivers which are using this for ARM please? Doing a

Sure. This peripheral-loader.c driver now moved under drivers/soc/ so it
can be used for ARM and ARM64.
https://android.googlesource.com/kernel/msm.git/+/db34f44bcba24345d26b8a4b8137cf94d70afa73/arch/arm/mach-msm/peripheral-loader.c
static int load_segment(const struct elf32_phdr *phdr, unsigned num, struct pil_device *pil) {
<snip>

	while (count > 0) {
		int size;
		u8 __iomem *buf;
		size = min_t(size_t, IOMAP_SIZE, count);
		buf = ioremap(paddr, size);
	}
	<snip>
	memset(buf, 0, size);
Quoted 1 line

<snip>

Right, but that code doesn't exist in mainline afaict.

Quoted 10 lines

As you can see the function load_segment() does ioremap() followed by memset() and memcpy() which can cause unaligned multi-byte (maybe ARM64 traps 8byte unaligned access?) write to device memory.
Because of this I was fixing the driver to use memset_io() and memcpy_io() but the existing implementations were too slow compare to the one I'm proposing.

Quoted 3 lines

blind byte-by-byte copy could easily cause problems with some peripherals, so there must be an underlying assumption somewhere about how this is supposed to work.

Quoted 7 lines

Would you mind to explain more about the problem with byte-by-byte copying you're worried about?
I thought byte-by-byte copy always safe with regard to aligned access and that's the reason existing implementation does byte-by-byte copy. I can imagine there are some peripherals don't allow per-byte access. But if that is the case, should they not use memset_io() and memcpy_{from,to}io() anyway?

Yes, if somebody tried to use memset_io to zero a bunch of control registers, for example, you'd likely get a bunch of aborts because the endpoint would give you a SLVERR for a byte-access to a word register.

It just seems like the expected usage of this function should be documented somewhere to avoid it becoming highly dependent on the architecture.

Will

maintainer
#5 lore raw

PATCH 1/1 arm64: optimize memcpy_{from,to}io() and memset_io()

In reply to Joonwoo Park
Quoted 7 lines · Joonwoo Park

Optimize memcpy_{from,to}io() and memset_io() by transferring in 64 bit as much as possible with minimized barrier usage. This simplest optimization brings faster throughput compare to current byte-by-byte read and write with barrier in the loop. Code's skeleton is taken from the powerpc.

I thought about merging this but there are some issues. Comments below.

arch/arm64/kernel/io.c line 23

Quoted 9 lines
arch/arm64/kernel/io.c +1 -0
index 7d37ead..c0e3ab1 100644
@@ -20,18 +20,34 @@
 #include <linux/types.h>
 #include <linux/io.h>
 
+#define IO_CHECK_ALIGN(v, a) ((((unsigned long)(v)) & ((a) - 1)) == 0)

Can you not use just IS_ALIGNED?

arch/arm64/kernel/io.c line 31

Quoted 9 lines
arch/arm64/kernel/io.c +2 -2
 /*
  * Copy data from IO memory space to "real" memory space.
  */
 void __memcpy_fromio(void *to, const volatile void __iomem *from, size_t count)
 {
-	unsigned char *t = to;
-	while (count) {
+	while (count && (!IO_CHECK_ALIGN(from, 8) || !IO_CHECK_ALIGN(to, 8))) {
+		*(u8 *)to = readb_relaxed(from);

We should not use the relaxed accessors here as we don't really care about endianness conversion. We just copy data from one place to another without interpreting it, so __raw_read*() is sufficient.

Quoted 11 lines
arch/arm64/kernel/io.c +8 -2
+		from++;
+		to++;
 		count--;
-		*t = readb(from);
-		t++;
+	}
+
+	while (count >= 8) {
+		*(u64 *)to = readq_relaxed(from);
+		from += 8;
+		to += 8;

arch/arm64/kernel/io.c line 50

Quoted 10 lines
arch/arm64/kernel/io.c +8 -0
+		count -= 8;
+	}
+
+	while (count) {
+		*(u8 *)to = readb_relaxed(from);
 		from++;
+		to++;
+		count--;
 	}
+	__iormb();

We don't need this barrier here. In the readl() implementation, it's use is for ordering between I/O polling and DMA buffer access.

arch/arm64/kernel/io.c EXPORT_SYMBOL(__memcpy_fromio); · line 59

Quoted 9 lines
arch/arm64/kernel/io.c +1 -1
 }
 EXPORT_SYMBOL(__memcpy_fromio);
 
@@ -40,12 +56,28 @@ EXPORT_SYMBOL(__memcpy_fromio);
  */
 void __memcpy_toio(volatile void __iomem *to, const void *from, size_t count)
 {
-	const unsigned char *f = from;
+	void *p = (void __force *)from;

Why do you need this?

arch/arm64/kernel/io.c EXPORT_SYMBOL(__memcpy_fromio); · line 61

Quoted 2 lines
arch/arm64/kernel/io.c +2 -0
+
+	__iowmb();

Not needed here either.

arch/arm64/kernel/io.c EXPORT_SYMBOL(__memcpy_fromio); · line 63

Quoted 2 lines
arch/arm64/kernel/io.c +2 -0
+	while (count && (!IO_CHECK_ALIGN(p, 8) || !IO_CHECK_ALIGN(from, 8))) {
+		writeb_relaxed(*(volatile u8 *)from, p);

Oh, so you copy from "from" to "from". Have you really tested this?

arch/arm64/kernel/io.c EXPORT_SYMBOL(__memcpy_toio); · line 90

Quoted 5 lines
arch/arm64/kernel/io.c +1 -0
@@ -55,10 +87,30 @@ EXPORT_SYMBOL(__memcpy_toio);
  */
 void __memset_io(volatile void __iomem *dst, int c, size_t count)
 {
+	void *p = (void __force *)dst;

Do you need this?

arch/arm64/kernel/io.c EXPORT_SYMBOL(__memcpy_toio); · line 97

Quoted 7 lines
arch/arm64/kernel/io.c +7 -0
+	u64 qc = c;
+
+	qc |= qc << 8;
+	qc |= qc << 16;
+	qc |= qc << 32;
+
+	__iowmb();

What's this for?

arch/arm64/kernel/io.c EXPORT_SYMBOL(__memcpy_toio); · line 99

Quoted 2 lines
arch/arm64/kernel/io.c +2 -0
+	while (count && !IO_CHECK_ALIGN(p, 8)) {
+		writeb_relaxed(c, p);

Using dst here directly here should work (__raw_writeb takes the same type as the second argument).

Signature · 1 line
Catalin
#6 lore raw

PATCH 1/1 arm64: optimize memcpy_{from,to}io() and memset_io()

Quoted 10 lines · Catalin Marinas
Quoted 7 lines · Joonwoo Park

Optimize memcpy_{from,to}io() and memset_io() by transferring in 64 bit as much as possible with minimized barrier usage. This simplest optimization brings faster throughput compare to current byte-by-byte read and write with barrier in the loop. Code's skeleton is taken from the powerpc.

I thought about merging this but there are some issues. Comments below.

Thanks for reviewing.

Quoted 9 lines
Quoted 9 lines
arch/arm64/kernel/io.c +0 -0
index 7d37ead..c0e3ab1 100644
@@ -20,18 +20,34 @@
 #include <linux/types.h>
 #include <linux/io.h>
 
Quoted 4 lines

arch/arm64/kernel/io.c line 23

Quoted 1 line
arch/arm64/kernel/io.c +1 -0
+#define IO_CHECK_ALIGN(v, a) ((((unsigned long)(v)) & ((a) - 1)) == 0)

Can you not use just IS_ALIGNED?

Will do. I would need to cast from/to with unsigned long.

Quoted 5 lines
Quoted 5 lines
arch/arm64/kernel/io.c +0 -0
 /*
  * Copy data from IO memory space to "real" memory space.
  */
 void __memcpy_fromio(void *to, const volatile void __iomem *from, size_t count)
 {
Quoted 9 lines

arch/arm64/kernel/io.c line 31

Quoted 4 lines
arch/arm64/kernel/io.c +2 -2
-	unsigned char *t = to;
-	while (count) {
+	while (count && (!IO_CHECK_ALIGN(from, 8) || !IO_CHECK_ALIGN(to, 8))) {
+		*(u8 *)to = readb_relaxed(from);

We should not use the relaxed accessors here as we don't really care about endianness conversion. We just copy data from one place to another without interpreting it, so __raw_read*() is sufficient.

Will do.

Quoted 16 lines
Quoted 16 lines
arch/arm64/kernel/io.c +13 -2
+		from++;
+		to++;
 		count--;
-		*t = readb(from);
-		t++;
+	}
+
+	while (count >= 8) {
+		*(u64 *)to = readq_relaxed(from);
+		from += 8;
+		to += 8;
+		count -= 8;
+	}
+
+	while (count) {
+		*(u8 *)to = readb_relaxed(from);
Quoted 9 lines

arch/arm64/kernel/io.c line 50

Quoted 5 lines
arch/arm64/kernel/io.c +3 -0
 		from++;
+		to++;
+		count--;
 	}
+	__iormb();

We don't need this barrier here. In the readl() implementation, it's use is for ordering between I/O polling and DMA buffer access.

The barriers here and down below are for accessing different devices in a row. I thought that's what your suggestion too.
http://lists.infradead.org/pipermail/linux-arm-kernel/2012-September/123178.html

Quoted 12 lines

arch/arm64/kernel/io.c EXPORT_SYMBOL(__memcpy_fromio); · line 59

Quoted 9 lines
arch/arm64/kernel/io.c +1 -1
 }
 EXPORT_SYMBOL(__memcpy_fromio);
 
@@ -40,12 +56,28 @@ EXPORT_SYMBOL(__memcpy_fromio);
  */
 void __memcpy_toio(volatile void __iomem *to, const void *from, size_t count)
 {
-	const unsigned char *f = from;
+	void *p = (void __force *)from;

Why do you need this?

Will drop this.

Quoted 10 lines

arch/arm64/kernel/io.c EXPORT_SYMBOL(__memcpy_fromio); · line 61

Quoted 2 lines
arch/arm64/kernel/io.c +2 -0
+
+	__iowmb();

Not needed here either.

arch/arm64/kernel/io.c EXPORT_SYMBOL(__memcpy_fromio); · line 63

Quoted 2 lines
arch/arm64/kernel/io.c +2 -0
+	while (count && (!IO_CHECK_ALIGN(p, 8) || !IO_CHECK_ALIGN(from, 8))) {
+		writeb_relaxed(*(volatile u8 *)from, p);

Oh, so you copy from "from" to "from". Have you really tested this?

I also found this issue with more testing after sending this patch. Will fix.

Quoted 8 lines

arch/arm64/kernel/io.c EXPORT_SYMBOL(__memcpy_toio); · line 90

Quoted 5 lines
arch/arm64/kernel/io.c +1 -0
@@ -55,10 +87,30 @@ EXPORT_SYMBOL(__memcpy_toio);
  */
 void __memset_io(volatile void __iomem *dst, int c, size_t count)
 {
+	void *p = (void __force *)dst;

Do you need this?

Will drop this.

Quoted 10 lines

arch/arm64/kernel/io.c EXPORT_SYMBOL(__memcpy_toio); · line 97

Quoted 7 lines
arch/arm64/kernel/io.c +7 -0
+	u64 qc = c;
+
+	qc |= qc << 8;
+	qc |= qc << 16;
+	qc |= qc << 32;
+
+	__iowmb();

What's this for?

This barrier was for the same reason with one above for writing different devices that doesn't guarantee writing order.

Quoted 6 lines

arch/arm64/kernel/io.c EXPORT_SYMBOL(__memcpy_toio); · line 99

Quoted 2 lines
arch/arm64/kernel/io.c +2 -0
+	while (count && !IO_CHECK_ALIGN(p, 8)) {
+		writeb_relaxed(c, p);

Using dst here directly here should work (__raw_writeb takes the same type as the second argument).

Will update.

I'm quite not sure if barriers are not needed or not indeed.
The situation I'm worried about is like 'memcpy_io(device A); memcpy_io(device B);' which I think memcpy_io() needs to guarantee the order. Please let me know. I'll submit new version then.

Thanks,
Joonwoo

Quoted 3 lines
Signature · 2 lines
-- 
Catalin
Signature · 2 lines
The Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
hosted by The Linux Foundation
maintainer
#7 lore raw

PATCH 1/1 arm64: optimize memcpy_{from,to}io() and memset_io()

Quoted 10 lines · Joonwoo Park
Quoted 9 lines · Catalin Marinas
Quoted 8 lines · Joonwoo Park
arch/arm64/kernel/io.c +0 -0
index 7d37ead..c0e3ab1 100644
@@ -20,18 +20,34 @@
 #include <linux/types.h>
 #include <linux/io.h>
 
Quoted 5 lines
Quoted 3 lines

arch/arm64/kernel/io.c line 23

Quoted 1 line
arch/arm64/kernel/io.c +1 -0
+#define IO_CHECK_ALIGN(v, a) ((((unsigned long)(v)) & ((a) - 1)) == 0)

Can you not use just IS_ALIGNED?

Will do. I would need to cast from/to with unsigned long.

Or define IO_CHECK_ALIGN as IS_ALIGNED((unsigned long)v, a)

Quoted 25 lines
Quoted 16 lines
Quoted 16 lines
arch/arm64/kernel/io.c +13 -2
+		from++;
+		to++;
 		count--;
-		*t = readb(from);
-		t++;
+	}
+
+	while (count >= 8) {
+		*(u64 *)to = readq_relaxed(from);
+		from += 8;
+		to += 8;
+		count -= 8;
+	}
+
+	while (count) {
+		*(u8 *)to = readb_relaxed(from);
Quoted 8 lines

arch/arm64/kernel/io.c line 50

Quoted 5 lines
arch/arm64/kernel/io.c +3 -0
 		from++;
+		to++;
+		count--;
 	}
+	__iormb();

We don't need this barrier here. In the readl() implementation, it's use is for ordering between I/O polling and DMA buffer access.

Quoted 3 lines

The barriers here and down below are for accessing different devices in a row. I thought that's what your suggestion too.
http://lists.infradead.org/pipermail/linux-arm-kernel/2012-September/123178.html

I think we should leave them out until we find a use case. I currently don't see any (writel/readl etc. still have the barriers).

Quoted 6 lines
Quoted 5 lines

arch/arm64/kernel/io.c line 44

Quoted 2 lines
arch/arm64/kernel/io.c +2 -0
+	while (count && !IO_CHECK_ALIGN(p, 8)) {
+		writeb_relaxed(c, p);

Using dst here directly here should work (__raw_writeb takes the same type as the second argument).

Quoted 6 lines

Will update.

I'm quite not sure if barriers are not needed or not indeed. The situation I'm worried about is like 'memcpy_io(device A); memcpy_io(device B);' which I think memcpy_io() needs to guarantee the order.

Without barriers, ordering between device A and B would not be guaranteed. But do you have a scenario where this ordering actually matters? Most case we have something like:

memcpy_io(device A); // mapped as Device or Normal NonCacheable
writel(device B); // or an I/O register of device A

Here writel() has the correct barrier.

Signature · 1 line
Catalin
#8 lore raw

PATCH 1/1 arm64: optimize memcpy_{from,to}io() and memset_io()

Quoted 64 lines · Catalin Marinas
Quoted 10 lines · Joonwoo Park
Quoted 9 lines · Catalin Marinas
Quoted 8 lines · Joonwoo Park
arch/arm64/kernel/io.c +0 -0
index 7d37ead..c0e3ab1 100644
@@ -20,18 +20,34 @@
 #include <linux/types.h>
 #include <linux/io.h>
 
Quoted 5 lines
Quoted 3 lines

arch/arm64/kernel/io.c line 23

Quoted 1 line
arch/arm64/kernel/io.c +1 -0
+#define IO_CHECK_ALIGN(v, a) ((((unsigned long)(v)) & ((a) - 1)) == 0)

Can you not use just IS_ALIGNED?

Will do. I would need to cast from/to with unsigned long.

Or define IO_CHECK_ALIGN as IS_ALIGNED((unsigned long)v, a)

Quoted 25 lines
Quoted 16 lines
Quoted 16 lines
arch/arm64/kernel/io.c +13 -2
+		from++;
+		to++;
 		count--;
-		*t = readb(from);
-		t++;
+	}
+
+	while (count >= 8) {
+		*(u64 *)to = readq_relaxed(from);
+		from += 8;
+		to += 8;
+		count -= 8;
+	}
+
+	while (count) {
+		*(u8 *)to = readb_relaxed(from);
Quoted 8 lines

arch/arm64/kernel/io.c line 50

Quoted 5 lines
arch/arm64/kernel/io.c +3 -0
 		from++;
+		to++;
+		count--;
 	}
+	__iormb();

We don't need this barrier here. In the readl() implementation, it's use is for ordering between I/O polling and DMA buffer access.

Quoted 3 lines

The barriers here and down below are for accessing different devices in a row. I thought that's what your suggestion too.
http://lists.infradead.org/pipermail/linux-arm-kernel/2012-September/123178.html

I think we should leave them out until we find a use case. I currently don't see any (writel/readl etc. still have the barriers).

Quoted 6 lines
Quoted 5 lines

arch/arm64/kernel/io.c line 44

Quoted 2 lines
arch/arm64/kernel/io.c +2 -0
+	while (count && !IO_CHECK_ALIGN(p, 8)) {
+		writeb_relaxed(c, p);

Using dst here directly here should work (__raw_writeb takes the same type as the second argument).

Quoted 6 lines

Will update.

I'm quite not sure if barriers are not needed or not indeed. The situation I'm worried about is like 'memcpy_io(device A); memcpy_io(device B);' which I think memcpy_io() needs to guarantee the order.

Quoted 8 lines

Without barriers, ordering between device A and B would not be guaranteed. But do you have a scenario where this ordering actually matters? Most case we have something like:

memcpy_io(device A); // mapped as Device or Normal NonCacheable
writel(device B); // or an I/O register of device A

Here writel() has the correct barrier.

I don't have such use case that ordering matters either. I agree that we should leave until it's needed.

Thanks,
Joonwoo

Quoted 3 lines
Signature · 2 lines
-- 
Catalin
Signature · 2 lines
The Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
hosted by The Linux Foundation
#9 lore raw

PATCH 1/1 arm64: optimize memcpy_{from,to}io() and memset_io()

Quoted 1 line

From 1cb0bb81e0d399255e679337929a8438946a4b9a Mon Sep 17 00:00:00 2001

From: Joonwoo Park <[email protected]>
Date: Mon, 28 Jul 2014 17:32:06 -0700
Subject: [PATCH v2] arm64: optimize memcpy_{from,to}io() and memset_io()

Optimize memcpy_{from,to}io() and memset_io() by transferring in 64 bit as much as possible with minimized barrier usage. This simplest optimization brings faster throughput compare to current byte-by-byte read and write with barrier in the loop. Code's skeleton is taken from the powerpc.

---
Changes in v2:
* dropped barriers.
* Use IS_ALIGNED() macro.
* use __raw_read{b,q} and __raw_write{b,q}.
* fix bug in __memcpy_toio()

 arch/arm64/kernel/io.c | 66 ++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 56 insertions(+), 10 deletions(-)
arch/arm64/kernel/io.c +56 -10
index 7d37ead..a47cd65 100644
@@ -25,12 +25,26 @@
  */
 void __memcpy_fromio(void *to, const volatile void __iomem *from, size_t count)
 {
-	unsigned char *t = to;
-	while (count) {
+	while (count && (!IS_ALIGNED((unsigned long)from, 8) ||
+			 !IS_ALIGNED((unsigned long)to, 8))) {
+		*(u8 *)to = __raw_readb(from);
+		from++;
+		to++;
 		count--;
-		*t = readb(from);
-		t++;
+	}
+
+	while (count >= 8) {
+		*(u64 *)to = __raw_readq(from);
+		from += 8;
+		to += 8;
+		count -= 8;
+	}
+
+	while (count) {
+		*(u8 *)to = __raw_readb(from);
 		from++;
+		to++;
+		count--;
 	}
 }
 EXPORT_SYMBOL(__memcpy_fromio);
@@ -40,12 +54,26 @@ EXPORT_SYMBOL(__memcpy_fromio);
  */
 void __memcpy_toio(volatile void __iomem *to, const void *from, size_t count)
 {
-	const unsigned char *f = from;
-	while (count) {
+	while (count && (!IS_ALIGNED((unsigned long)to, 8) ||
+			 !IS_ALIGNED((unsigned long)from, 8))) {
+		__raw_writeb(*(volatile u8 *)from, to);
+		from++;
+		to++;
 		count--;
-		writeb(*f, to);
-		f++;
+	}
+
+	while (count >= 8) {
+		__raw_writeq(*(volatile u64 *)from, to);
+		from += 8;
+		to += 8;
+		count -= 8;
+	}
+
+	while (count) {
+		__raw_writeb(*(volatile u8 *)from, to);
+		from++;
 		to++;
+		count--;
 	}
 }
 EXPORT_SYMBOL(__memcpy_toio);
@@ -55,10 +83,28 @@ EXPORT_SYMBOL(__memcpy_toio);
  */
 void __memset_io(volatile void __iomem *dst, int c, size_t count)
 {
-	while (count) {
+	u64 qc = c;
+
+	qc |= qc << 8;
+	qc |= qc << 16;
+	qc |= qc << 32;
+
+	while (count && !IS_ALIGNED((unsigned long)dst, 8)) {
+		__raw_writeb(c, dst);
+		dst++;
 		count--;
-		writeb(c, dst);
+	}
+
+	while (count >= 8) {
+		__raw_writeq(qc, dst);
+		dst += 8;
+		count -= 8;
+	}
+
+	while (count) {
+		__raw_writeb(c, dst);
 		dst++;
+		count--;
 	}
 }
 EXPORT_SYMBOL(__memset_io);
Signature · 2 lines
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
hosted by The Linux Foundation
maintainer
#10 lore raw

PATCH 1/1 arm64: optimize memcpy_{from,to}io() and memset_io()

Quoted 13 lines · Joonwoo Park

From 1cb0bb81e0d399255e679337929a8438946a4b9a Mon Sep 17 00:00:00 2001
From: Joonwoo Park <[email protected]>
Date: Mon, 28 Jul 2014 17:32:06 -0700
Subject: [PATCH v2] arm64: optimize memcpy_{from,to}io() and memset_io()

Optimize memcpy_{from,to}io() and memset_io() by transferring in 64 bit as much as possible with minimized barrier usage. This simplest optimization brings faster throughput compare to current byte-by-byte read and write with barrier in the loop. Code's skeleton is taken from the powerpc.

[...]

arch/arm64/kernel/io.c EXPORT_SYMBOL(__memcpy_toio); · line 86

Quoted 6 lines
arch/arm64/kernel/io.c +1 -1
@@ -55,10 +83,28 @@ EXPORT_SYMBOL(__memcpy_toio);
  */
 void __memset_io(volatile void __iomem *dst, int c, size_t count)
 {
-	while (count) {
+	u64 qc = c;

I think you should use a (u8) cast here (I'm not sure there is a requirement for memset to only pass u8 values).

	u64 qc = (u8)c;

Otherwise the patch looks fine. Since Will is handling the next merging window, you can add: