| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| * Copyright 2021 ASPEED Technology Inc. | |
| */ | |
| #include <config.h> | |
| #include <dm.h> | |
| #include <clk.h> | |
| #include <log.h> | |
| #include <asm/io.h> | |
| #include <malloc.h> | |
| #include <watchdog.h> | |
| #include <u-boot/hash.h> | |
| #include <linux/bitops.h> | |
| #include <linux/delay.h> | |
| #include <linux/kernel.h> | |
| #include <linux/iopoll.h> | |
| /* register offsets*/ | |
| #define HACE_STS 0x1C | |
| #define HACE_HASH_DATA_OVF BIT(23) | |
| #define HACE_HASH_INT BIT(9) | |
| #define HACE_HASH_BUSY BIT(0) | |
| #define HACE_HASH_DATA 0x20 | |
| #define HACE_HASH_DIGEST 0x24 | |
| #define HACE_HASH_HMAC_KEY 0x28 | |
| #define HACE_HASH_DATA_LEN 0x2C | |
| #define HACE_HASH_CMD 0x30 | |
| #define HACE_HASH_MODE_ACCUM BIT(8) | |
| #define HACE_HASH_ALGO_SHA1 BIT(5) | |
| #define HACE_HASH_ALGO_SHA256 (BIT(6) | BIT(4)) | |
| #define HACE_HASH_ALGO_SHA384 (BIT(10) | BIT(6) | BIT(5)) | |
| #define HACE_HASH_ALGO_SHA512 (BIT(6) | BIT(5)) | |
| #define HACE_HASH_SHA_BE_EN BIT(3) | |
| /* buffer size based on SHA-512 need*/ | |
| #define HASH_BLOCK_BUFSZ 128 | |
| #define HASH_DIGEST_BUFSZ 64 | |
| struct aspeed_hace_ctx { | |
| uint8_t digest[HASH_DIGEST_BUFSZ]; | |
| uint32_t cmd; | |
| enum HASH_ALGO algo; | |
| uint32_t blk_size; | |
| uint32_t pad_size; | |
| uint64_t total[2]; | |
| uint8_t buf[HASH_BLOCK_BUFSZ]; | |
| uint32_t buf_cnt; | |
| } __aligned((8)); | |
| struct aspeed_hace { | |
| phys_addr_t base; | |
| struct clk clk; | |
| }; | |
| static const uint32_t iv_sha1[8] = { | |
| 0x01234567, 0x89abcdef, 0xfedcba98, 0x76543210, | |
| 0xf0e1d2c3, 0, 0, 0 | |
| }; | |
| static const uint32_t iv_sha256[8] = { | |
| 0x67e6096a, 0x85ae67bb, 0x72f36e3c, 0x3af54fa5, | |
| 0x7f520e51, 0x8c68059b, 0xabd9831f, 0x19cde05bUL | |
| }; | |
| static const uint32_t iv_sha384[16] = { | |
| 0x5d9dbbcb, 0xd89e05c1, 0x2a299a62, 0x07d57c36, | |
| 0x5a015991, 0x17dd7030, 0xd8ec2f15, 0x39590ef7, | |
| 0x67263367, 0x310bc0ff, 0x874ab48e, 0x11155868, | |
| 0x0d2e0cdb, 0xa78ff964, 0x1d48b547, 0xa44ffabeUL | |
| }; | |
| static const uint32_t iv_sha512[16] = { | |
| 0x67e6096a, 0x08c9bcf3, 0x85ae67bb, 0x3ba7ca84, | |
| 0x72f36e3c, 0x2bf894fe, 0x3af54fa5, 0xf1361d5f, | |
| 0x7f520e51, 0xd182e6ad, 0x8c68059b, 0x1f6c3e2b, | |
| 0xabd9831f, 0x6bbd41fb, 0x19cde05b, 0x79217e13UL | |
| }; | |
| static int aspeed_hace_wait_completion(uint32_t reg, uint32_t flag, int timeout_us) | |
| { | |
| uint32_t val; | |
| return readl_poll_timeout(reg, val, (val & flag) == flag, timeout_us); | |
| } | |
| static int aspeed_hace_process(struct udevice *dev, void *ctx, const void *ibuf, uint32_t ilen) | |
| { | |
| struct aspeed_hace *hace = dev_get_priv(dev); | |
| struct aspeed_hace_ctx *hace_ctx = (struct aspeed_hace_ctx *)ctx; | |
| uint32_t sts = readl(hace->base + HACE_STS); | |
| if (sts & HACE_HASH_BUSY) { | |
| debug("HACE engine busy\n"); | |
| return -EBUSY; | |
| } | |
| writel(HACE_HASH_INT, hace->base + HACE_STS); | |
| writel((uint32_t)ibuf, hace->base + HACE_HASH_DATA); | |
| writel((uint32_t)hace_ctx->digest, hace->base + HACE_HASH_DIGEST); | |
| writel((uint32_t)hace_ctx->digest, hace->base + HACE_HASH_HMAC_KEY); | |
| writel(ilen, hace->base + HACE_HASH_DATA_LEN); | |
| writel(hace_ctx->cmd, hace->base + HACE_HASH_CMD); | |
| return aspeed_hace_wait_completion(hace->base + HACE_STS, | |
| HACE_HASH_INT, | |
| 1000 + (ilen >> 3)); | |
| } | |
| static int aspeed_hace_init(struct udevice *dev, enum HASH_ALGO algo, void **ctxp) | |
| { | |
| struct aspeed_hace_ctx *hace_ctx; | |
| hace_ctx = memalign(8, sizeof(struct aspeed_hace_ctx)); | |
| if (!hace_ctx) | |
| return -ENOMEM; | |
| memset(hace_ctx, 0, sizeof(struct aspeed_hace_ctx)); | |
| hace_ctx->algo = algo; | |
| hace_ctx->cmd = HACE_HASH_MODE_ACCUM | HACE_HASH_SHA_BE_EN; | |
| switch (algo) { | |
| case HASH_ALGO_SHA1: | |
| hace_ctx->blk_size = 64; | |
| hace_ctx->pad_size = 8; | |
| hace_ctx->cmd |= HACE_HASH_ALGO_SHA1; | |
| memcpy(hace_ctx->digest, iv_sha1, sizeof(iv_sha1)); | |
| break; | |
| case HASH_ALGO_SHA256: | |
| hace_ctx->blk_size = 64; | |
| hace_ctx->pad_size = 8; | |
| hace_ctx->cmd |= HACE_HASH_ALGO_SHA256; | |
| memcpy(hace_ctx->digest, iv_sha256, sizeof(iv_sha256)); | |
| break; | |
| case HASH_ALGO_SHA384: | |
| hace_ctx->blk_size = 128; | |
| hace_ctx->pad_size = 16; | |
| hace_ctx->cmd |= HACE_HASH_ALGO_SHA384; | |
| memcpy(hace_ctx->digest, iv_sha384, sizeof(iv_sha384)); | |
| break; | |
| case HASH_ALGO_SHA512: | |
| hace_ctx->blk_size = 128; | |
| hace_ctx->pad_size = 16; | |
| hace_ctx->cmd |= HACE_HASH_ALGO_SHA512; | |
| memcpy(hace_ctx->digest, iv_sha512, sizeof(iv_sha512)); | |
| break; | |
| default: | |
| debug("Unsupported hash algorithm '%s'\n", hash_algo_name(algo)); | |
| goto free_n_out; | |
| }; | |
| *ctxp = hace_ctx; | |
| return 0; | |
| free_n_out: | |
| free(hace_ctx); | |
| return -EINVAL; | |
| } | |
| static int aspeed_hace_update(struct udevice *dev, void *ctx, const void *ibuf, uint32_t ilen) | |
| { | |
| int rc; | |
| uint32_t left, fill; | |
| struct aspeed_hace_ctx *hace_ctx = ctx; | |
| left = hace_ctx->total[0] & (hace_ctx->blk_size - 1); | |
| fill = hace_ctx->blk_size - left; | |
| hace_ctx->total[0] += ilen; | |
| if (hace_ctx->total[0] < ilen) | |
| hace_ctx->total[1]++; | |
| if (left && ilen >= fill) { | |
| memcpy(hace_ctx->buf + left, ibuf, fill); | |
| rc = aspeed_hace_process(dev, ctx, hace_ctx->buf, hace_ctx->blk_size); | |
| if (rc) { | |
| debug("failed to process hash, rc=%d\n", rc); | |
| return rc; | |
| } | |
| ilen -= fill; | |
| ibuf += fill; | |
| left = 0; | |
| } | |
| while (ilen >= hace_ctx->blk_size) { | |
| rc = aspeed_hace_process(dev, ctx, ibuf, hace_ctx->blk_size); | |
| if (rc) { | |
| debug("failed to process hash, rc=%d\n", rc); | |
| return rc; | |
| } | |
| ibuf += hace_ctx->blk_size; | |
| ilen -= hace_ctx->blk_size; | |
| } | |
| if (ilen) | |
| memcpy(hace_ctx->buf + left, ibuf, ilen); | |
| return 0; | |
| } | |
| static int aspeed_hace_finish(struct udevice *dev, void *ctx, void *obuf) | |
| { | |
| int rc = 0; | |
| uint8_t pad[HASH_BLOCK_BUFSZ * 2]; | |
| uint32_t last, padn; | |
| uint64_t ibits_h, ibits_l; | |
| uint64_t ibits_be_h, ibits_be_l; | |
| struct aspeed_hace_ctx *hace_ctx = ctx; | |
| memset(pad, 0, sizeof(pad)); | |
| pad[0] = 0x80; | |
| ibits_h = (hace_ctx->total[0] >> 61) | (hace_ctx->total[1] << 3); | |
| ibits_be_h = cpu_to_be64(ibits_h); | |
| ibits_l = (hace_ctx->total[0] << 3); | |
| ibits_be_l = cpu_to_be64(ibits_l); | |
| last = hace_ctx->total[0] & (hace_ctx->blk_size - 1); | |
| switch (hace_ctx->algo) { | |
| case HASH_ALGO_SHA1: | |
| case HASH_ALGO_SHA256: | |
| padn = (last < 56) ? (56 - last) : (120 - last); | |
| rc = aspeed_hace_update(dev, ctx, pad, padn); | |
| if (rc) { | |
| debug("failed to append padding, rc=%d\n", rc); | |
| goto free_n_out; | |
| } | |
| rc = aspeed_hace_update(dev, ctx, &ibits_be_l, sizeof(ibits_be_l)); | |
| if (rc) { | |
| debug("failed to append message bits length, rc=%d\n", rc); | |
| goto free_n_out; | |
| } | |
| break; | |
| case HASH_ALGO_SHA384: | |
| case HASH_ALGO_SHA512: | |
| padn = (last < 112) ? (112 - last) : (240 - last); | |
| rc = aspeed_hace_update(dev, ctx, pad, padn); | |
| if (rc) { | |
| debug("failed to append padding, rc=%d\n", rc); | |
| goto free_n_out; | |
| } | |
| rc = aspeed_hace_update(dev, ctx, &ibits_be_h, sizeof(ibits_be_h)) | | |
| aspeed_hace_update(dev, ctx, &ibits_be_l, sizeof(ibits_be_l)); | |
| if (rc) { | |
| debug("failed to append message bits length, rc=%d\n", rc); | |
| goto free_n_out; | |
| } | |
| break; | |
| default: | |
| rc = -EINVAL; | |
| break; | |
| } | |
| memcpy(obuf, hace_ctx->digest, hash_algo_digest_size(hace_ctx->algo)); | |
| free_n_out: | |
| free(ctx); | |
| return rc; | |
| } | |
| static int aspeed_hace_digest_wd(struct udevice *dev, enum HASH_ALGO algo, | |
| const void *ibuf, const uint32_t ilen, | |
| void *obuf, uint32_t chunk_sz) | |
| { | |
| int rc; | |
| void *ctx; | |
| const void *cur, *end; | |
| uint32_t chunk; | |
| rc = aspeed_hace_init(dev, algo, &ctx); | |
| if (rc) | |
| return rc; | |
| if (IS_ENABLED(CONFIG_HW_WATCHDOG) || CONFIG_IS_ENABLED(WATCHDOG)) { | |
| cur = ibuf; | |
| end = ibuf + ilen; | |
| while (cur < end) { | |
| chunk = end - cur; | |
| if (chunk > chunk_sz) | |
| chunk = chunk_sz; | |
| rc = aspeed_hace_update(dev, ctx, cur, chunk); | |
| if (rc) | |
| return rc; | |
| cur += chunk; | |
| schedule(); | |
| } | |
| } else { | |
| rc = aspeed_hace_update(dev, ctx, ibuf, ilen); | |
| if (rc) | |
| return rc; | |
| } | |
| rc = aspeed_hace_finish(dev, ctx, obuf); | |
| if (rc) | |
| return rc; | |
| return 0; | |
| } | |
| static int aspeed_hace_digest(struct udevice *dev, enum HASH_ALGO algo, | |
| const void *ibuf, const uint32_t ilen, | |
| void *obuf) | |
| { | |
| /* re-use the watchdog version with input length as the chunk_sz */ | |
| return aspeed_hace_digest_wd(dev, algo, ibuf, ilen, obuf, ilen); | |
| } | |
| static int aspeed_hace_probe(struct udevice *dev) | |
| { | |
| int rc; | |
| struct aspeed_hace *hace = dev_get_priv(dev); | |
| rc = clk_get_by_index(dev, 0, &hace->clk); | |
| if (rc < 0) { | |
| debug("cannot get clock for %s: %d\n", dev->name, rc); | |
| return rc; | |
| } | |
| rc = clk_enable(&hace->clk); | |
| if (rc) { | |
| debug("cannot enable clock for %s: %d\n", dev->name, rc); | |
| return rc; | |
| } | |
| hace->base = devfdt_get_addr(dev); | |
| return rc; | |
| } | |
| static int aspeed_hace_remove(struct udevice *dev) | |
| { | |
| struct aspeed_hace *hace = dev_get_priv(dev); | |
| clk_disable(&hace->clk); | |
| return 0; | |
| } | |
| static const struct hash_ops aspeed_hace_ops = { | |
| .hash_init = aspeed_hace_init, | |
| .hash_update = aspeed_hace_update, | |
| .hash_finish = aspeed_hace_finish, | |
| .hash_digest_wd = aspeed_hace_digest_wd, | |
| .hash_digest = aspeed_hace_digest, | |
| }; | |
| static const struct udevice_id aspeed_hace_ids[] = { | |
| { .compatible = "aspeed,ast2600-hace" }, | |
| { } | |
| }; | |
| U_BOOT_DRIVER(aspeed_hace) = { | |
| .name = "aspeed_hace", | |
| .id = UCLASS_HASH, | |
| .of_match = aspeed_hace_ids, | |
| .ops = &aspeed_hace_ops, | |
| .probe = aspeed_hace_probe, | |
| .remove = aspeed_hace_remove, | |
| .priv_auto = sizeof(struct aspeed_hace), | |
| .flags = DM_FLAG_PRE_RELOC, | |
| }; |