| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Accelerated CRC32 implementation with Zbc extension. | |
| * | |
| * Copyright (C) 2024 Intel Corporation | |
| */ | |
| #include <asm/hwcap.h> | |
| #include <asm/alternative-macros.h> | |
| #include <asm/byteorder.h> | |
| #include <linux/types.h> | |
| #include <linux/minmax.h> | |
| #include <linux/crc32poly.h> | |
| #include <linux/crc32.h> | |
| #include <linux/byteorder/generic.h> | |
| /* | |
| * Refer to https://www.corsix.org/content/barrett-reduction-polynomials for | |
| * better understanding of how this math works. | |
| * | |
| * let "+" denotes polynomial add (XOR) | |
| * let "-" denotes polynomial sub (XOR) | |
| * let "*" denotes polynomial multiplication | |
| * let "/" denotes polynomial floor division | |
| * let "S" denotes source data, XLEN bit wide | |
| * let "P" denotes CRC32 polynomial | |
| * let "T" denotes 2^(XLEN+32) | |
| * let "QT" denotes quotient of T/P, with the bit for 2^XLEN being implicit | |
| * | |
| * crc32(S, P) | |
| * => S * (2^32) - S * (2^32) / P * P | |
| * => lowest 32 bits of: S * (2^32) / P * P | |
| * => lowest 32 bits of: S * (2^32) * (T / P) / T * P | |
| * => lowest 32 bits of: S * (2^32) * quotient / T * P | |
| * => lowest 32 bits of: S * quotient / 2^XLEN * P | |
| * => lowest 32 bits of: (clmul_high_part(S, QT) + S) * P | |
| * => clmul_low_part(clmul_high_part(S, QT) + S, P) | |
| * | |
| * In terms of below implementations, the BE case is more intuitive, since the | |
| * higher order bit sits at more significant position. | |
| */ | |
| #if __riscv_xlen == 64 | |
| /* Slide by XLEN bits per iteration */ | |
| # define STEP_ORDER 3 | |
| /* Each below polynomial quotient has an implicit bit for 2^XLEN */ | |
| /* Polynomial quotient of (2^(XLEN+32))/CRC32_POLY, in LE format */ | |
| # define CRC32_POLY_QT_LE 0x5a72d812fb808b20 | |
| /* Polynomial quotient of (2^(XLEN+32))/CRC32C_POLY, in LE format */ | |
| # define CRC32C_POLY_QT_LE 0xa434f61c6f5389f8 | |
| /* Polynomial quotient of (2^(XLEN+32))/CRC32_POLY, in BE format, it should be | |
| * the same as the bit-reversed version of CRC32_POLY_QT_LE | |
| */ | |
| # define CRC32_POLY_QT_BE 0x04d101df481b4e5a | |
| static inline u64 crc32_le_prep(u32 crc, unsigned long const *ptr) | |
| { | |
| return (u64)crc ^ (__force u64)__cpu_to_le64(*ptr); | |
| } | |
| static inline u32 crc32_le_zbc(unsigned long s, u32 poly, unsigned long poly_qt) | |
| { | |
| u32 crc; | |
| /* We don't have a "clmulrh" insn, so use clmul + slli instead. */ | |
| asm volatile (".option push\n" | |
| ".option arch,+zbc\n" | |
| "clmul %0, %1, %2\n" | |
| "slli %0, %0, 1\n" | |
| "xor %0, %0, %1\n" | |
| "clmulr %0, %0, %3\n" | |
| "srli %0, %0, 32\n" | |
| ".option pop\n" | |
| : "=&r" (crc) | |
| : "r" (s), | |
| "r" (poly_qt), | |
| "r" ((u64)poly << 32) | |
| :); | |
| return crc; | |
| } | |
| static inline u64 crc32_be_prep(u32 crc, unsigned long const *ptr) | |
| { | |
| return ((u64)crc << 32) ^ (__force u64)__cpu_to_be64(*ptr); | |
| } | |
| #elif __riscv_xlen == 32 | |
| # define STEP_ORDER 2 | |
| /* Each quotient should match the upper half of its analog in RV64 */ | |
| # define CRC32_POLY_QT_LE 0xfb808b20 | |
| # define CRC32C_POLY_QT_LE 0x6f5389f8 | |
| # define CRC32_POLY_QT_BE 0x04d101df | |
| static inline u32 crc32_le_prep(u32 crc, unsigned long const *ptr) | |
| { | |
| return crc ^ (__force u32)__cpu_to_le32(*ptr); | |
| } | |
| static inline u32 crc32_le_zbc(unsigned long s, u32 poly, unsigned long poly_qt) | |
| { | |
| u32 crc; | |
| /* We don't have a "clmulrh" insn, so use clmul + slli instead. */ | |
| asm volatile (".option push\n" | |
| ".option arch,+zbc\n" | |
| "clmul %0, %1, %2\n" | |
| "slli %0, %0, 1\n" | |
| "xor %0, %0, %1\n" | |
| "clmulr %0, %0, %3\n" | |
| ".option pop\n" | |
| : "=&r" (crc) | |
| : "r" (s), | |
| "r" (poly_qt), | |
| "r" (poly) | |
| :); | |
| return crc; | |
| } | |
| static inline u32 crc32_be_prep(u32 crc, unsigned long const *ptr) | |
| { | |
| return crc ^ (__force u32)__cpu_to_be32(*ptr); | |
| } | |
| #else | |
| # error "Unexpected __riscv_xlen" | |
| #endif | |
| static inline u32 crc32_be_zbc(unsigned long s) | |
| { | |
| u32 crc; | |
| asm volatile (".option push\n" | |
| ".option arch,+zbc\n" | |
| "clmulh %0, %1, %2\n" | |
| "xor %0, %0, %1\n" | |
| "clmul %0, %0, %3\n" | |
| ".option pop\n" | |
| : "=&r" (crc) | |
| : "r" (s), | |
| "r" (CRC32_POLY_QT_BE), | |
| "r" (CRC32_POLY_BE) | |
| :); | |
| return crc; | |
| } | |
| #define STEP (1 << STEP_ORDER) | |
| #define OFFSET_MASK (STEP - 1) | |
| typedef u32 (*fallback)(u32 crc, unsigned char const *p, size_t len); | |
| static inline u32 crc32_le_unaligned(u32 crc, unsigned char const *p, | |
| size_t len, u32 poly, | |
| unsigned long poly_qt) | |
| { | |
| size_t bits = len * 8; | |
| unsigned long s = 0; | |
| u32 crc_low = 0; | |
| for (int i = 0; i < len; i++) | |
| s = ((unsigned long)*p++ << (__riscv_xlen - 8)) | (s >> 8); | |
| s ^= (unsigned long)crc << (__riscv_xlen - bits); | |
| if (__riscv_xlen == 32 || len < sizeof(u32)) | |
| crc_low = crc >> bits; | |
| crc = crc32_le_zbc(s, poly, poly_qt); | |
| crc ^= crc_low; | |
| return crc; | |
| } | |
| static inline u32 __pure crc32_le_generic(u32 crc, unsigned char const *p, | |
| size_t len, u32 poly, | |
| unsigned long poly_qt, | |
| fallback crc_fb) | |
| { | |
| size_t offset, head_len, tail_len; | |
| unsigned long const *p_ul; | |
| unsigned long s; | |
| asm goto(ALTERNATIVE("j %l[legacy]", "nop", 0, | |
| RISCV_ISA_EXT_ZBC, 1) | |
| : : : : legacy); | |
| /* Handle the unaligned head. */ | |
| offset = (unsigned long)p & OFFSET_MASK; | |
| if (offset && len) { | |
| head_len = min(STEP - offset, len); | |
| crc = crc32_le_unaligned(crc, p, head_len, poly, poly_qt); | |
| p += head_len; | |
| len -= head_len; | |
| } | |
| tail_len = len & OFFSET_MASK; | |
| len = len >> STEP_ORDER; | |
| p_ul = (unsigned long const *)p; | |
| for (int i = 0; i < len; i++) { | |
| s = crc32_le_prep(crc, p_ul); | |
| crc = crc32_le_zbc(s, poly, poly_qt); | |
| p_ul++; | |
| } | |
| /* Handle the tail bytes. */ | |
| p = (unsigned char const *)p_ul; | |
| if (tail_len) | |
| crc = crc32_le_unaligned(crc, p, tail_len, poly, poly_qt); | |
| return crc; | |
| legacy: | |
| return crc_fb(crc, p, len); | |
| } | |
| u32 __pure crc32_le(u32 crc, unsigned char const *p, size_t len) | |
| { | |
| return crc32_le_generic(crc, p, len, CRC32_POLY_LE, CRC32_POLY_QT_LE, | |
| crc32_le_base); | |
| } | |
| u32 __pure __crc32c_le(u32 crc, unsigned char const *p, size_t len) | |
| { | |
| return crc32_le_generic(crc, p, len, CRC32C_POLY_LE, | |
| CRC32C_POLY_QT_LE, __crc32c_le_base); | |
| } | |
| static inline u32 crc32_be_unaligned(u32 crc, unsigned char const *p, | |
| size_t len) | |
| { | |
| size_t bits = len * 8; | |
| unsigned long s = 0; | |
| u32 crc_low = 0; | |
| s = 0; | |
| for (int i = 0; i < len; i++) | |
| s = *p++ | (s << 8); | |
| if (__riscv_xlen == 32 || len < sizeof(u32)) { | |
| s ^= crc >> (32 - bits); | |
| crc_low = crc << bits; | |
| } else { | |
| s ^= (unsigned long)crc << (bits - 32); | |
| } | |
| crc = crc32_be_zbc(s); | |
| crc ^= crc_low; | |
| return crc; | |
| } | |
| u32 __pure crc32_be(u32 crc, unsigned char const *p, size_t len) | |
| { | |
| size_t offset, head_len, tail_len; | |
| unsigned long const *p_ul; | |
| unsigned long s; | |
| asm goto(ALTERNATIVE("j %l[legacy]", "nop", 0, | |
| RISCV_ISA_EXT_ZBC, 1) | |
| : : : : legacy); | |
| /* Handle the unaligned head. */ | |
| offset = (unsigned long)p & OFFSET_MASK; | |
| if (offset && len) { | |
| head_len = min(STEP - offset, len); | |
| crc = crc32_be_unaligned(crc, p, head_len); | |
| p += head_len; | |
| len -= head_len; | |
| } | |
| tail_len = len & OFFSET_MASK; | |
| len = len >> STEP_ORDER; | |
| p_ul = (unsigned long const *)p; | |
| for (int i = 0; i < len; i++) { | |
| s = crc32_be_prep(crc, p_ul); | |
| crc = crc32_be_zbc(s); | |
| p_ul++; | |
| } | |
| /* Handle the tail bytes. */ | |
| p = (unsigned char const *)p_ul; | |
| if (tail_len) | |
| crc = crc32_be_unaligned(crc, p, tail_len); | |
| return crc; | |
| legacy: | |
| return crc32_be_base(crc, p, len); | |
| } |