Edit online

设计要点

5 Nov 2024
Read time: 5 minute(s)

在设计实现 SPI_ENC 的驱动时,主要考虑了 SPI_ENC 的本身特点,以及与 SPI NOR / SPI NAND 驱动的结合。

融入内核加密子系统

内核加密子系统提供了一个框架,通过该框架提供了不同算法的对接方法, 常见的算法以及对应的硬件加速实现都可以通过该框架提供给使用者。

SPI_ENC 硬件模块实现了 AES-128-CTR 算法,但是由于 COUNTER 的生成方式是通过自定义规则生成, 密钥由硬件从 eFuse 中读取等,使得该算法与正常的 AES-128-CTR 不同,因此融入内核加密子系统时, 将 SPI_ENC 抽象为一种特殊的 AES-128-CTR 算法实现 ,通过 AES-128-CTR 的 API 进行使用, 但是做一些特殊处理。


spienc_kenerl_crypto_arch

1. 加入内核加密子系统

将 SPI_ENC 的 AES-128-CTR 算法注册到内核加密子系统:

static struct aic_spienc_alg spienc_alg = {
    .alg = {
        .base.cra_name = "ctr(aes)",
        .base.cra_driver_name = "ctr-aes-spienc-aic",
        .base.cra_priority = 200,
        .base.cra_flags = CRYPTO_ALG_ASYNC |
                  CRYPTO_ALG_ALLOCATES_MEMORY |
                  CRYPTO_ALG_KERN_DRIVER_ONLY,
        .base.cra_blocksize = 1,
        .base.cra_ctxsize = sizeof(struct aic_spienc_ctx),
        .base.cra_alignmask = 0xf,
        .base.cra_module = THIS_MODULE,
        .init = aic_spienc_alg_init,
        .decrypt = aic_spienc_decrypt,
        .encrypt = aic_spienc_encrypt,
        .ivsize = AES_BLOCK_SIZE,
    },
};

设置说明:

  • cra_blocksize: 设置为 1,即使用本算法,可以按字节设置加解密的数据长度。

  • ivsize: 设置为 AES_BLOCK_SIZE,外部通过 IV 来提供地址、SPI ID 等信息

  • min_keysize/max_keysize: 不设置,使用 eFuse 密钥,非外部密钥

提供给外部(SPI NAND / SPI NOR)使用的 API:

- 加密子系统 API 说明
1 struct crypto_skcipher *crypto_alloc_skcipher( const char *alg_name, u32 type, u32 mask) 直接指定算法驱动名字ctr-aes-spienc-aic
2 struct skcipher_request *skcipher_request_alloc( struct crypto_skcipher *tfm, gfp_t gfp) 分配一个数据请求结构体
3 void skcipher_request_set_callback(struct skcipher_request *req, u32 flags,crypto_completion_t compl, void *data) 设置处理完毕时的回调函数
4 void skcipher_request_set_crypt(struct skcipher_request *req,struct scatterlist *src, struct scatterlist *dst, unsigned int cryptlen, void *iv) 无输入输出 buffer,仅提供数据长度。通过 IV 提供地址、SPI ID
5 int crypto_skcipher_decrypt(struct skcipher_request *req) 启动解密,用于读
6 int crypto_skcipher_encrypt(struct skcipher_request *req) 启动加密,用于写
7 void skcipher_request_free(struct skcipher_request *req) 释放资源
8 void crypto_free_skcipher(struct crypto_skcipher *tfm) 释放资源

注解

由于 SPI_ENC 内部使用 eFuse 所提供的密钥,因此不需要通过外部设置密钥函数:int crypto_skcipher_setkey(struct crypto_skcipher *tfm, const u8 *key, unsigned int keylen);来设置密钥。

调用 skcipher_request_set_crypt() API 时,通过 iv 提供的并不是 COUNTER 值,而是用于生成 COUNTER 值的信息,具体为:

struct aic_spienc_iv {
    u32 addr;       // 要读写的数据在 SPI NAND / SPI NOR 上的地址
    u32 cpos;       // 密文在本次传输数据中开始位置
    u32 tweak;      // 生成 COUNTER 的调整值
    u32 spi_id;     // 要使用的 SPI 控制器 ID
};

数据读写时启用

SPI NAND / SPI NOR 驱动通过发送命令的方式与 SPI NAND / SPI NOR 器件进行交互,从而实现数据的读写。 在使能 SPI_ENC 之后,SPI NAND / SPI NOR 驱动需要进行区分:

  • 非存储数据的 SPI 传输,不启动 SPI_ENC,按照原有驱动的流程执行

  • 存储数据的 SPI 传输,启动 SPI_ENC 进行加密或者解密

因此 SPI NAND / SPI NOR 驱动需要做一些改动,在对存储数据进行读写时,使用 Crypto API 启动 SPI_ENC。

以 SPI NAND 为例:

probe 时首先进行加密相关的初始,读取 DTS 中的 aic,encryptaic,spi-id 等信息。
 spinand_probe();
 |-> spinand_init(spinand);
"|-> spinand_enc_init(spinand);
 |-> mtd_device_register(mtd, NULL, 0);
读操作流程:
spinand_read_page();
|-> spinand_load_page_op(spinand, req);
|-> spinand_wait(spinand, &status);
|-> spinand_read_from_cache_op();
"   |-> spinand_enc_xfer_cfg(spinand, addr, clen);                          // 配置访问地址,以及密文的长度
"   |-> spinand_enc_read();
"       |-> spinand_enc_get_skcipher(spinand);
"       |   |-> crypto_alloc_skcipher("ctr-aes-spienc-aic", 0, 0);
"       |   |-> req = skcipher_request_alloc(tfm, GFP_NOFS);
"       |
"       |-> skcipher_request_set_callback();
"       |-> skcipher_request_set_crypt(req, 0, 0, decrypt_len, &ivinfo);    // 设置本次加密数据信息
"       |-> crypto_skcipher_decrypt(req);                                   // 启动 SPI_ENC
        |-> spi_mem_exec_op(desc->mem, &op);                                // 调用标准的 SPI API 进行数据传输
"       |-> spinand_enc_wait(spinand->priv);                                // 等待解密处理结束
写操作流程:
spinand_write_page();
|-> spinand_write_enable_op(spinand);
|-> spinand_write_to_cache_op(spinand, req);
|  "|-> spinand_enc_xfer_cfg(spinand, addr, clen);                          // 配置访问地址,以及密文的长度
|  "|-> spinand_enc_write();
|  "    |-> spinand_enc_get_skcipher(spinand);
|  "    |   |-> crypto_alloc_skcipher("ctr-aes-spienc-aic", 0, 0);
|  "    |   |-> req = skcipher_request_alloc(tfm, GFP_NOFS);
|  "    |
|  "    |-> skcipher_request_set_callback();
|  "    |-> skcipher_request_set_crypt(req, 0, 0, decrypt_len, &ivinfo);    // 设置本次加密数据信息
|  "    |-> crypto_skcipher_encrypt(req);                                   // 启动 SPI_ENC
|       |-> spi_mem_exec_op(desc->mem, &op);                                // 调用标准的 SPI API 进行数据传输
|  "    |-> spinand_enc_wait(spinand->priv);                                // 等待加密处理结束
|-> spinand_program_op(spinand, req);
|-> spinand_wait(spinand, &status);

空数据块的检测

SPI NAND / SPI NOR 在执行了擦除之后,存储单元上的数据被认为是空的,值都是 0xFF 。 但是在使用过程中,读取程序并不一定知道所读取的区域是否是被擦除过,因此在使能了 SPI_ENC 之后, 通过 SPI 读取回来该区域的数据都是被 SPI_ENC 解密后的数据。原本是被擦除后的 0xFF , 读回来的却是其他数据。

带来的问题:

有些程序,如文件系统,会判断读回来的数据是否都为 0xFF ,如果是,则认为是未使用的块,做特殊处理。 现在读回来的数据却被改变了,会导致原来的处理逻辑全部失效。

为了解决上述问题,SPI_ENC 提供了一个空块检测功能。如下图所示:

  • 首先按照正常的流程读取一块数据

  • 传输完成之后,检查 SPI_ENC 的状态,如果提示解密前的所有数据都是 0xFF ,则软件将读取的结果全部置为 0xFF


spienc_empty_detect1

2. 空块检测

相关的软件操作,在 spinand_enc_read()spi_nor_enc_read() 中完成。