8.1.5. 设计说明

8.1.5.1. 源码说明

相关模块

源码路径

Crypto subsystem

linux-5.10/crypto/

Driver

linux-5.10/drivers/crypto/zx/spienc/

8.1.5.2. 模块架构

../../../_images/spienc_arch.png

文件系统或者应用程序通过 MTD 层访问 SPI NOR / SPI NAND 设备的数据,SPI NOR / SPI NAND 驱动在通过 SPI 总线传输数据。

在 SPI NOR / SPI NAND 驱动通过 SPI 总线访问相关数据时,其实需要发送一系列的命令来进行数据的读写。 这个过程中,命令通信的数据,包括一些读写 SPI NOR / SPI NAND 的寄存器数据都不能加密, 仅存储数据部分需要进行加密处理。因此,虽然 SPI_ENC 是在硬件总线上对数据进行加密, 但是软件使用时需要在 SPI NOR / SPI NAND 驱动 根据需要,提供必要的加解密信息(数据地址和长度), 并且启动相应的数据传输加解密。

因此在软件层次上,SPI NOR / SPI NAND 驱动依赖 SPI 驱动以及 SPI_ENC 驱动,而 SPI 驱动与 SPI_ENC 驱动是并列关系。

8.1.5.3. 设计要点

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

8.1.5.3.1. 融入内核加密子系统

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

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

../../../_images/spienc_kenerl_crypto_arch.png

图 8.9 加入内核加密子系统

将 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
};

8.1.5.3.2. 数据读写时启用

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);

8.1.5.3.3. 空数据块的检测

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

带来的问题:

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

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

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

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

../../../_images/spienc_empty_detect.png

图 8.10 空块检测

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

8.1.5.4. 关键流程

8.1.5.4.1. 初始化

aic_spienc_probe();
|-> drvdata->base = devm_platform_ioremap_resource(pdev, 0);
|-> irq = platform_get_irq(pdev, 0);
|-> devm_request_threaded_irq(dev, irq, aic_spienc_irq_handler,
                              aic_spienc_irq_thread, IRQF_ONESHOT,
                              dev_name(dev), drvdata);
|-> drvdata->clk = devm_clk_get(dev, NULL);
|-> clk_prepare_enable(drvdata->clk);
|-> devm_reset_control_get(dev, NULL);
|-> platform_set_drvdata(pdev, drvdata);
|-> crypto_register_skcipher(&spienc_alg.alg);  // 注册算法

8.1.5.4.2. 加解密配置

aic_spienc_encrypt(req);
|-> aic_spienc_xcrypt(req);
    |-> aic_spienc_attach_bus(drvdata, ivinfo->spi_id);
    |-> writel(ivinfo->addr, (drvdata->base + SPIE_REG_ADDR));
    |-> writel(ivinfo->cpos, (drvdata->base + SPIE_REG_CPOS));
    |-> writel(clen, (drvdata->base + SPIE_REG_CLEN));
    |-> writel(tweak, (drvdata->base + SPIE_REG_TWEAK));

解密流程相同。

8.1.5.4.3. 中断流程

当中断发生时,首先在 irq handler 中读取并保存状态寄存器。

static irqreturn_t aic_spienc_irq_handler(int irq, void *arg)
{
    struct aic_spienc_drvdata *drvdata = arg;

    drvdata->irq_sts = readl(drvdata->base + SPIE_REG_ISR);

    return IRQ_WAKE_THREAD;
}

然后唤醒对应的处理线程进行处理。

static irqreturn_t aic_spienc_irq_thread(int irq, void *arg)
{
    struct aic_spienc_drvdata *drvdata = arg;
    struct crypto_async_request *base;
    int err = 0;
    u32 val = 0;

    if (drvdata->irq_sts & SPIE_INTR_ENC_DEC_FIN_MSK) {
        if (drvdata->irq_sts & SPIE_INTR_ALL_EMP_MSK)
            err = AIC_SPIENC_ALL_FF;
        base = &drvdata->req->base;
        base->complete(base, err);

        /* Stop it */
        val = readl((drvdata->base + SPIE_REG_CTL));
        val &= ~SPIE_START_MSK;
        writel(val, (drvdata->base + SPIE_REG_CTL));

        /* Clear interrupts */
        drvdata->irq_sts = 0;
        writel(SPIE_INTR_ALL_MSK, (drvdata->base + SPIE_REG_ISR));
    }

    return IRQ_HANDLED;
}

8.1.5.5. 数据结构

设备数据结构。

struct aic_spienc_drvdata {
    struct attribute_group attrs;
    struct device *dev;
    void __iomem *base;
    struct clk *clk;
    struct skcipher_request *req;
    u32 tweak; /* Tweak value for hardware to generate counter */
    u32 irq_sts;
};

8.1.5.6. 接口设计

8.1.5.6.1. aic_spienc_alg_init

函数原型

int aic_spienc_alg_init(struct crypto_skcipher *tfm)

功能说明

对称密钥算法的初始化函数

参数定义

struct crypto_skcipher *tfm
算法实例指针

返回值

0: 成功
其他: 失败

注意事项

8.1.5.6.2. aic_spienc_probe

函数原型

int aic_spienc_probe(struct platform_device *pdev)

功能说明

驱动的初始化函数

参数定义

struct platform_device *pdev
设备指针

返回值

0: 成功
其他: 失败

注意事项

8.1.5.6.3. aic_spienc_attach_bus

函数原型

int aic_spienc_attach_bus(struct aic_spienc_drvdata *drvdata, u32 bus)

功能说明

将 SPI ENC 连接到指定的 SPI 控制器

参数定义

struct aic_spienc_drvdata *drvdata
设备驱动数据指针
u32 bus
SPI 控制器 ID

返回值

0: 成功
其他: 失败

注意事项

8.1.5.6.4. aic_spienc_encrypt

函数原型

int aic_spienc_encrypt(struct skcipher_request *req)

功能说明

配置 SPI_ENC 启动数据加密

参数定义

struct skcipher_request *req
加密请求指针

返回值

0: 成功
其他: 失败

注意事项

8.1.5.6.5. aic_spienc_decrypt

函数原型

int aic_spienc_decrypt(struct skcipher_request *req)

功能说明

配置 SPI_ENC 启动数据解密

参数定义

struct skcipher_request *req
解密请求指针

返回值

0: 成功
其他: 失败

注意事项