riscv: optimized memmove
[platform/kernel/linux-starfive.git] / arch / riscv / lib / string.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * String functions optimized for hardware which doesn't
4  * handle unaligned memory accesses efficiently.
5  *
6  * Copyright (C) 2021 Matteo Croce
7  */
8
9 #define __NO_FORTIFY
10 #include <linux/types.h>
11 #include <linux/module.h>
12
13 /* Minimum size for a word copy to be convenient */
14 #define BYTES_LONG      sizeof(long)
15 #define WORD_MASK       (BYTES_LONG - 1)
16 #define MIN_THRESHOLD   (BYTES_LONG * 2)
17
18 /* convenience union to avoid cast between different pointer types */
19 union types {
20         u8 *as_u8;
21         unsigned long *as_ulong;
22         uintptr_t as_uptr;
23 };
24
25 union const_types {
26         const u8 *as_u8;
27         unsigned long *as_ulong;
28         uintptr_t as_uptr;
29 };
30
31 void *__memcpy(void *dest, const void *src, size_t count)
32 {
33         union const_types s = { .as_u8 = src };
34         union types d = { .as_u8 = dest };
35         int distance = 0;
36
37         if (!IS_ENABLED(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS)) {
38                 if (count < MIN_THRESHOLD)
39                         goto copy_remainder;
40
41                 /* Copy a byte at time until destination is aligned. */
42                 for (; d.as_uptr & WORD_MASK; count--)
43                         *d.as_u8++ = *s.as_u8++;
44
45                 distance = s.as_uptr & WORD_MASK;
46         }
47
48         if (distance) {
49                 unsigned long last, next;
50
51                 /*
52                  * s is distance bytes ahead of d, and d just reached
53                  * the alignment boundary. Move s backward to word align it
54                  * and shift data to compensate for distance, in order to do
55                  * word-by-word copy.
56                  */
57                 s.as_u8 -= distance;
58
59                 next = s.as_ulong[0];
60                 for (; count >= BYTES_LONG; count -= BYTES_LONG) {
61                         last = next;
62                         next = s.as_ulong[1];
63
64                         d.as_ulong[0] = last >> (distance * 8) |
65                                         next << ((BYTES_LONG - distance) * 8);
66
67                         d.as_ulong++;
68                         s.as_ulong++;
69                 }
70
71                 /* Restore s with the original offset. */
72                 s.as_u8 += distance;
73         } else {
74                 /*
75                  * If the source and dest lower bits are the same, do a simple
76                  * 32/64 bit wide copy.
77                  */
78                 for (; count >= BYTES_LONG; count -= BYTES_LONG)
79                         *d.as_ulong++ = *s.as_ulong++;
80         }
81
82 copy_remainder:
83         while (count--)
84                 *d.as_u8++ = *s.as_u8++;
85
86         return dest;
87 }
88 EXPORT_SYMBOL(__memcpy);
89
90 void *memcpy(void *dest, const void *src, size_t count) __weak __alias(__memcpy);
91 EXPORT_SYMBOL(memcpy);
92
93 /*
94  * Simply check if the buffer overlaps an call memcpy() in case,
95  * otherwise do a simple one byte at time backward copy.
96  */
97 void *__memmove(void *dest, const void *src, size_t count)
98 {
99         if (dest < src || src + count <= dest)
100                 return __memcpy(dest, src, count);
101
102         if (dest > src) {
103                 const char *s = src + count;
104                 char *tmp = dest + count;
105
106                 while (count--)
107                         *--tmp = *--s;
108         }
109         return dest;
110 }
111 EXPORT_SYMBOL(__memmove);
112
113 void *memmove(void *dest, const void *src, size_t count) __weak __alias(__memmove);
114 EXPORT_SYMBOL(memmove);