![Will Deacon](/assets/img/avatar_default.png)
The asm-generic, big-endian version of zero_bytemask creates a mask of bytes preceding the first zero-byte by left shifting ~0ul based on the position of the first zero byte. Unfortunately, if the first (top) byte is zero, the output of prep_zero_mask has only the top bit set, resulting in undefined C behaviour as we shift left by an amount equal to the width of the type. As it happens, GCC doesn't manage to spot this through the call to fls(), but the issue remains if architectures choose to implement their shift instructions differently. An example would be arch/arm/ (AArch32), where LSL Rd, Rn, #32 results in Rd == 0x0, whilst on arch/arm64 (AArch64) LSL Xd, Xn, #64 results in Xd == Xn. Rather than check explicitly for the problematic shift, this patch adds an extra shift by 1, replacing fls with __fls. Since zero_bytemask is never called with a zero argument (has_zero() is used to check the data first), we don't need to worry about calling __fls(0), which is undefined. Cc: <stable@vger.kernel.org> Cc: Victor Kamensky <victor.kamensky@linaro.org> Signed-off-by: Will Deacon <will.deacon@arm.com> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
57 lines
1.2 KiB
C
57 lines
1.2 KiB
C
#ifndef _ASM_WORD_AT_A_TIME_H
|
|
#define _ASM_WORD_AT_A_TIME_H
|
|
|
|
/*
|
|
* This says "generic", but it's actually big-endian only.
|
|
* Little-endian can use more efficient versions of these
|
|
* interfaces, see for example
|
|
* arch/x86/include/asm/word-at-a-time.h
|
|
* for those.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
struct word_at_a_time {
|
|
const unsigned long high_bits, low_bits;
|
|
};
|
|
|
|
#define WORD_AT_A_TIME_CONSTANTS { REPEAT_BYTE(0xfe) + 1, REPEAT_BYTE(0x7f) }
|
|
|
|
/* Bit set in the bytes that have a zero */
|
|
static inline long prep_zero_mask(unsigned long val, unsigned long rhs, const struct word_at_a_time *c)
|
|
{
|
|
unsigned long mask = (val & c->low_bits) + c->low_bits;
|
|
return ~(mask | rhs);
|
|
}
|
|
|
|
#define create_zero_mask(mask) (mask)
|
|
|
|
static inline long find_zero(unsigned long mask)
|
|
{
|
|
long byte = 0;
|
|
#ifdef CONFIG_64BIT
|
|
if (mask >> 32)
|
|
mask >>= 32;
|
|
else
|
|
byte = 4;
|
|
#endif
|
|
if (mask >> 16)
|
|
mask >>= 16;
|
|
else
|
|
byte += 2;
|
|
return (mask >> 8) ? byte : byte + 1;
|
|
}
|
|
|
|
static inline bool has_zero(unsigned long val, unsigned long *data, const struct word_at_a_time *c)
|
|
{
|
|
unsigned long rhs = val | c->low_bits;
|
|
*data = rhs;
|
|
return (val + c->high_bits) & ~rhs;
|
|
}
|
|
|
|
#ifndef zero_bytemask
|
|
#define zero_bytemask(mask) (~0ul << __fls(mask) << 1)
|
|
#endif
|
|
|
|
#endif /* _ASM_WORD_AT_A_TIME_H */
|