On Mon, Jul 6, 2026 at 2:41 PM Philippe Mathieu-Daudé
<[email protected]> wrote:
>
> On 6/7/26 20:13, James Hilliard wrote:
> > On Mon, Jul 6, 2026 at 10:57 AM Philippe Mathieu-Daudé
> > <[email protected]> wrote:
> >>
> >> Hi James,
> >>
> >> On 8/6/26 20:59, James Hilliard wrote:
> >>> Add helper support for the Octeon HSH hash selectors. This includes the
> >>> base HSH data/IV windows, MD5, SHA1, SHA256, and SHA512 transform paths,
> >>> and the shared HSH/SHA512 register-window readback and write operations.
> >>>
> >>> The SHA512 path shares the wide HSH register bank with SHA3, SNOW3G, and
> >>> ZUC. Keep the aliased readback and write paths centralized so selector
> >>> decode can route register accesses through these helpers when side
> >>> effects are required.
> >>>
> >>> Signed-off-by: James Hilliard <[email protected]>
> >>> ---
> >>> Changes v13 -> v14:
> >>> - Keep HSH narrow DAT/IV state updates in the low 32-bit halves of the
> >>> architectural HSH register bank so paired selector transfers
> >>> preserve
> >>> the wide shared-window state.
> >>>
> >>> Changes v10 -> v13:
> >>> - Keep the 0x0057 SDK compatibility path as an explicit STARTSHA1
> >>> compatibility helper instead of a generic STARTSHA name.
> >>>
> >>> Changes v9 -> v10:
> >>> - Remove references to shared-mode tracking; aliased state is kept in
> >>> the
> >>> architectural HSH register banks.
> >>>
> >>> Changes v8 -> v9:
> >>> - Split HSH/SHA helpers into their own COP2 helper patch.
> >>> - Replace generic selector dispatch with per-operation HSH helpers.
> >>> - Keep shared-window readback and write helpers grouped with HSH.
> >>> - Add matching helper.h declarations with the helper implementation.
> >>> ---
> >>> target/mips/helper.h | 5 +
> >>> target/mips/tcg/octeon_crypto.c | 375
> >>> ++++++++++++++++++++++++++++++++++++++++
> >>> 2 files changed, 380 insertions(+)
> >>>
> >>> diff --git a/target/mips/helper.h b/target/mips/helper.h
> >>> index 71b7a2b30e..834f2d0769 100644
> >>> --- a/target/mips/helper.h
> >>> +++ b/target/mips/helper.h
> >>> @@ -69,6 +69,11 @@ DEF_HELPER_2(octeon_cp2_mt_des3_dec, void, env, i64)
> >>> DEF_HELPER_2(octeon_cp2_mt_camellia_fl, void, env, i64)
> >>> DEF_HELPER_2(octeon_cp2_mt_camellia_flinv, void, env, i64)
> >>> DEF_HELPER_2(octeon_cp2_mt_camellia_round, void, env, i64)
> >>> +DEF_HELPER_2(octeon_cp2_mt_hsh_startsha1_compat, void, env, i64)
> >>> +DEF_HELPER_2(octeon_cp2_mt_hsh_startmd5, void, env, i64)
> >>> +DEF_HELPER_2(octeon_cp2_mt_hsh_startsha256, void, env, i64)
> >>> +DEF_HELPER_2(octeon_cp2_mt_hsh_startsha, void, env, i64)
> >>> +DEF_HELPER_2(octeon_cp2_mt_hsh_startsha512, void, env, i64)
> >>>
> >>> /* microMIPS functions */
> >>> DEF_HELPER_4(lwm, void, env, tl, tl, i32)
> >>> diff --git a/target/mips/tcg/octeon_crypto.c
> >>> b/target/mips/tcg/octeon_crypto.c
> >>> index 67959618cc..9523c84667 100644
> >>> --- a/target/mips/tcg/octeon_crypto.c
> >>> +++ b/target/mips/tcg/octeon_crypto.c
> >>> @@ -153,6 +153,348 @@ static void octeon_gfm_mul64_uia2(const uint64_t
> >>> x[2], const uint64_t y[2],
> >>> out[1] = revbit64(res);
> >>> }
> >>>
> >>> +static inline uint32_t octeon_hsh_get32(const uint64_t *regs,
> >>> + unsigned int index)
> >>> +{
> >>> + return regs[index];
> >>> +}
> >>> +
> >>> +static inline void octeon_hsh_set32(uint64_t *regs, unsigned int index,
> >>> + uint32_t value)
> >>> +{
> >>> + regs[index] = (regs[index] & ~(uint64_t)UINT32_MAX) | value;
> >>> +}
> >>> +
> >>> +static inline void octeon_hsh_set_pair(uint64_t *regs, unsigned int
> >>> index,
> >>> + uint64_t value)
> >>> +{
> >>> + octeon_hsh_set32(regs, index * 2, value >> 32);
> >>> + octeon_hsh_set32(regs, index * 2 + 1, value);
> >>> +}
> >>> +
> >>> +static void octeon_md5_transform(MIPSOcteonCryptoState *crypto)
> >>> +{
> >>> + static const uint32_t k[64] = {
> >>> + 0xd76aa478U, 0xe8c7b756U, 0x242070dbU, 0xc1bdceeeU,
> >>> + 0xf57c0fafU, 0x4787c62aU, 0xa8304613U, 0xfd469501U,
> >>> + 0x698098d8U, 0x8b44f7afU, 0xffff5bb1U, 0x895cd7beU,
> >>> + 0x6b901122U, 0xfd987193U, 0xa679438eU, 0x49b40821U,
> >>> + 0xf61e2562U, 0xc040b340U, 0x265e5a51U, 0xe9b6c7aaU,
> >>> + 0xd62f105dU, 0x02441453U, 0xd8a1e681U, 0xe7d3fbc8U,
> >>> + 0x21e1cde6U, 0xc33707d6U, 0xf4d50d87U, 0x455a14edU,
> >>> + 0xa9e3e905U, 0xfcefa3f8U, 0x676f02d9U, 0x8d2a4c8aU,
> >>> + 0xfffa3942U, 0x8771f681U, 0x6d9d6122U, 0xfde5380cU,
> >>> + 0xa4beea44U, 0x4bdecfa9U, 0xf6bb4b60U, 0xbebfbc70U,
> >>> + 0x289b7ec6U, 0xeaa127faU, 0xd4ef3085U, 0x04881d05U,
> >>> + 0xd9d4d039U, 0xe6db99e5U, 0x1fa27cf8U, 0xc4ac5665U,
> >>> + 0xf4292244U, 0x432aff97U, 0xab9423a7U, 0xfc93a039U,
> >>> + 0x655b59c3U, 0x8f0ccc92U, 0xffeff47dU, 0x85845dd1U,
> >>> + 0x6fa87e4fU, 0xfe2ce6e0U, 0xa3014314U, 0x4e0811a1U,
> >>> + 0xf7537e82U, 0xbd3af235U, 0x2ad7d2bbU, 0xeb86d391U,
> >>> + };
> >>> + static const uint8_t s[64] = {
> >>> + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
> >>> + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
> >>> + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
> >>> + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
> >>> + };
> >>> + uint32_t m[16];
> >>> + uint32_t a, b, c, d;
> >>> + uint32_t aa, bb, cc, dd;
> >>> + int i;
> >>> +
> >>> + for (i = 0; i < 16; i++) {
> >>> + m[i] = bswap32(octeon_hsh_get32(crypto->hsh_dat, i));
> >>> + }
> >>> +
> >>> + a = bswap32(octeon_hsh_get32(crypto->hsh_iv, 0));
> >>> + b = bswap32(octeon_hsh_get32(crypto->hsh_iv, 1));
> >>> + c = bswap32(octeon_hsh_get32(crypto->hsh_iv, 2));
> >>> + d = bswap32(octeon_hsh_get32(crypto->hsh_iv, 3));
> >>
> >> The MD5 algorithm is little-endian, so I think for these we want
> >> cpu_to_le32(), ...
> >
> > Looking at the CN71XX manual, I think the current handling matches the
> > architectural HSH register view. The HSH DAT/IV narrow selectors build
> > HASHDAT/HASHIV words from GPR halves, and the SHA-512 wide selectors move
> > whole GPR values into HASHDAT/HASHIV. These are COP2 register values,
> > not target-memory byte arrays.
> >
> > The manual calls out MD5 specially: HASHDAT/HASHIV are in the big-endian
> > HSH register-word order, while MD5 is little-endian, so the MD5 operation
> > byte-swaps HASHDAT/HASHIV on entry and exit. That is what the current
> > bswap32() calls model.
>
> This call works on little endian hosts, because the target CPU is only
> built for big-endian in user-mode. I don't expect your tests to pass on
> a big-endian host, because we don't want to swap in this case.
I agree those helpers are host-endian conversions, but that is why I
don't think they apply here.
The HSH state is modeled as architectural COP2 register values, not as a
host byte array. The narrow HSH DAT/IV selectors split GPR[rt] into
HASHDAT/HASHIV low-32 register words, and the translator already handles
host layout when placing those low 32 bits in the uint64_t state. After
that, octeon_hsh_get32() is reading an architectural register word.
For MD5, the CN71XX manual describes a byte swap between the HSH
big-endian register-word view and MD5's little-endian word order. That
swap is part of the Octeon operation, so it must be unconditional with
respect to the QEMU host. Using cpu_to_le32() would make it host-dependent:
identity on little-endian hosts and swapped on big-endian hosts.
Likewise, the SHA paths consume the HSH register words directly in the
manual pseudo-code, with no analogous MD5 byte-swap step. Using
cpu_to_be32()/be32_to_cpu() there would also introduce host-dependent
behavior that the modeled hardware operation does not have.
> See in include/qemu/bswap.h:
>
> 60 * Endianness conversion functions between host cpu and specified
> endianness.
> 63 *
> 64 * uint16_t le16_to_cpu(uint16_t v);
> 65 * uint32_t le32_to_cpu(uint32_t v);
> 66 * uint64_t le64_to_cpu(uint64_t v);
> 67 * uint16_t be16_to_cpu(uint16_t v);
> 68 * uint32_t be32_to_cpu(uint32_t v);
> 69 * uint64_t be64_to_cpu(uint64_t v);
> 70 *
> 71 * Convert the value @v from the specified format to the native
> 72 * endianness of the host CPU by byteswapping if necessary, and
> 73 * return the converted value.
> 74 *
> 75 * uint16_t cpu_to_le16(uint16_t v);
> 76 * uint32_t cpu_to_le32(uint32_t v);
> 77 * uint64_t cpu_to_le64(uint64_t v);
> 78 * uint16_t cpu_to_be16(uint16_t v);
> 79 * uint32_t cpu_to_be32(uint32_t v);
> 80 * uint64_t cpu_to_be64(uint64_t v);
> 81 *
> 82 * Convert the value @v from the native endianness of the host CPU
> 83 * to the specified format by byteswapping if necessary, and return
> 84 * the converted value.
>
> >
> >>> + aa = a;
> >>> + bb = b;
> >>> + cc = c;
> >>> + dd = d;
> >>> +
> >>> + for (i = 0; i < 64; i++) {
> >>> + uint32_t f, g, tmp;
> >>> +
> >>> + if (i < 16) {
> >>> + f = (b & c) | ((~b) & d);
> >>> + g = i;
> >>> + } else if (i < 32) {
> >>> + f = (d & b) | ((~d) & c);
> >>> + g = (5 * i + 1) & 0xf;
> >>> + } else if (i < 48) {
> >>> + f = b ^ c ^ d;
> >>> + g = (3 * i + 5) & 0xf;
> >>> + } else {
> >>> + f = c ^ (b | (~d));
> >>> + g = (7 * i) & 0xf;
> >>> + }
> >>> +
> >>> + tmp = d;
> >>> + d = c;
> >>> + c = b;
> >>> + b = b + rol32(a + f + k[i] + m[g], s[i]);
> >>> + a = tmp;
> >>> + }
> >>> +
> >>> + a += aa;
> >>> + b += bb;
> >>> + c += cc;
> >>> + d += dd;
> >>> + octeon_hsh_set32(crypto->hsh_iv, 0, bswap32(a));
> >>> + octeon_hsh_set32(crypto->hsh_iv, 1, bswap32(b));
> >>> + octeon_hsh_set32(crypto->hsh_iv, 2, bswap32(c));
> >>> + octeon_hsh_set32(crypto->hsh_iv, 3, bswap32(d));
> >>
> >> ... and le32_to_cpu() here.
> >>
> >>> +}
> >>> +
> >>> +static void octeon_sha1_transform(MIPSOcteonCryptoState *crypto)
> >>> +{
> >>> + uint32_t w[80];
> >>> + uint32_t a, b, c, d, e;
> >>> + uint32_t orig[5];
> >>> + int i;
> >>> +
> >>> + for (i = 0; i < 16; i++) {
> >>> + w[i] = octeon_hsh_get32(crypto->hsh_dat, i);
> >>> + }
> >>> + for (i = 16; i < 80; i++) {
> >>> + w[i] = rol32(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
> >>> + }
> >>> +
> >>> + for (i = 0; i < 5; i++) {
> >>> + orig[i] = octeon_hsh_get32(crypto->hsh_iv, i);
> >>> + }
> >>
> >> SHA is big-endian so we are good, but watch out this CPU could switch
> >> endianness.
> >>
> >> Maybe safer to use cpu_to_be32() and be32_to_cpu()?
> >
> > For SHA-1, SHA-256, and SHA-512, the manual pseudo-code feeds the HSH
> > register words directly into the SHA operation and does not describe a
> > similar byte-swap. So using cpu_to_be32()/be32_to_cpu() here would
> > byte-swap based on host endianness, which is not the architectural HSH
> > operation.
> >
> >>> + a = orig[0];
> >>> + b = orig[1];
> >>> + c = orig[2];
> >>> + d = orig[3];
> >>> + e = orig[4];
> >>> +
> >>> + for (i = 0; i < 80; i++) {
> >>> + uint32_t f, k, temp;
> >>> +
> >>> + if (i < 20) {
> >>> + f = (b & c) | ((~b) & d);
> >>> + k = 0x5a827999;
> >>> + } else if (i < 40) {
> >>> + f = b ^ c ^ d;
> >>> + k = 0x6ed9eba1;
> >>> + } else if (i < 60) {
> >>> + f = (b & c) | (b & d) | (c & d);
> >>> + k = 0x8f1bbcdc;
> >>> + } else {
> >>> + f = b ^ c ^ d;
> >>> + k = 0xca62c1d6;
> >>> + }
> >>> +
> >>> + temp = rol32(a, 5) + f + e + k + w[i];
> >>> + e = d;
> >>> + d = c;
> >>> + c = rol32(b, 30);
> >>> + b = a;
> >>> + a = temp;
> >>> + }
> >>> +
> >>> + orig[0] += a;
> >>> + orig[1] += b;
> >>> + orig[2] += c;
> >>> + orig[3] += d;
> >>> + orig[4] += e;
> >>> + for (i = 0; i < 5; i++) {
> >>> + octeon_hsh_set32(crypto->hsh_iv, i, orig[i]);
> >>> + }
> >>> +}
> >>> +
> >>> +static void octeon_sha256_transform(MIPSOcteonCryptoState *crypto)
> >>> +{
> >>
> >> (ditto)
> >>
> >>> +}
> >>> +
> >>> +static void octeon_sha512_transform(MIPSOcteonCryptoState *crypto)
> >>> +{
> >>
> >> (ditto)
> >>
> >>> +}
> >
>