Edit online

关键流程设计

7 Jun 2024
Read time: 3 minute(s)

初始化流程


ce_keyflow

1. 初始化流程
相关的代码流程如下。
aic_crypto_probe(pdev);
|-> ce_dev->base = devm_platform_ioremap_resource(pdev, 0);
|-> ret = devm_request_threaded_irq(dev, irq, aic_crypto_irq_handler,
|                                   aic_crypto_irq_thread, IRQF_ONESHOT,
|                                   dev_name(dev), ce_dev);
|-> ce_dev->clk = devm_clk_get(dev, NULL);
|-> clk_prepare_enable(ce_dev->clk);
|-> ce_dev->reset = devm_reset_control_get(dev, NULL);
|-> reset_control_assert(ce_dev->reset);
|-> reset_control_deassert(ce_dev->reset);
|-> aic_crypto_skcipher_accelerator_init(ce_dev);
|   |-> eng = crypto_engine_alloc_init_and_set(ce->dev, true, NULL, true,
|   |                                          ACCEL_QUEUE_MAX_SIZE);
|   |-> kfifo_alloc(&ce->sk_accel.req_fifo, ACCEL_QUEUE_MAX_SIZE, GFP_KERNEL);
|   |-> crypto_engine_start(ce->sk_accel.engine);
|   |-> crypto_register_skcipher(&sk_algs[i].alg);
|
|-> aic_crypto_akcipher_accelerator_init(ce_dev);
|-> aic_crypto_hash_accelerator_init(ce_dev);

数据处理流程

由于 CE 中几种类型算法的数据处理流程相似,这里仅以对称密钥算法的数据处理流程为例进行说明。

在处理步骤上,各种算法都遵循标准化的几个步骤:

  1. 从 Crypto Core 层将处理请求传递给 CE 算法

  2. CE 算法处理函数将请求转交给(transfer)给对应加速器的 crypto_engine 队列

  3. crypto_engine 中的处理线程从队列中取出请求,调用对应的 prepare/do_one_req 进行处理

  4. do_one_req 回调函数中,将对应的请求交给硬件处理

  5. 在中断处理函数中,取出结果,返回给调用者

对称密钥算法的具体处理调用流程如下所示。
aic_skcipher_aes_ecb_encrypt(req);
|-> aic_skcipher_crypt(req, FLG_AES | FLG_ECB);
    |-> crypto_transfer_skcipher_request_to_engine(eng, req);

crypto_engine
|-> aic_skcipher_prepare_req(engine, req);
|-> aic_skcipher_do_one_req(engine, req);
    |-> aic_crypto_enqueue_task(ce, algo, rctx->phy_task);

aic_crypto_irq_thread(int irq, void *arg);
|-> aic_skcipher_handle_irq(ce_dev);
    |-> crypto_finalize_skcipher_request(ce->sk_accel.engine, req, err);
        |-> aic_skcipher_unprepare_req(engine, req);
        |-> req.complete(req, err);

除了上述的大处理流程,还有一个关键点需要注意,就是 数据的对齐处理 。用户发起数据处理请求时, 提供了输入和输出的数据缓冲区,然而这些数据缓冲区对 CE 而言有两个问题:

  1. 这些缓冲区是虚拟地址空间的内存,并不一定是物理连续的内存空间

  2. 缓冲区的开始地址并不一定是对齐的,不一定满足 CE 的地址对齐要求

因此需要对输入和输出的数据做一些处理。

一个简单的处理方式是对输入和输出的数据,一律复制到驱动新申请的物理连续的缓冲区中, 使用该空间作为 CE 的硬件工作缓冲区,处理完成之后再复制到用户提供的输出缓冲区。 但是对每一笔数据都会有额外的两次数据拷贝操作,对于处理大量数据的应用场景,效率较低。

为了兼顾数据处理效率,CE 驱动针对可能出现的情况,做了几个分类, 原则上尽量避免数据拷贝

  1. 输入缓冲区和输出缓冲区 CE 都无法使用

    此种情况 CE 驱动为输入和输出缓冲区分配物理连续的工作缓冲区,并且需要对输入和输出数据进行复制。

  2. 输入缓冲区 CE 可用,输出缓冲区 CE 不可用

    此种情况 CE 驱动为输出缓冲区分配物理连续的工作缓冲区,CE 将数据处理完成之后,再复制到用户提供的输出缓冲区。

  3. 输入缓冲区 CE 不可用,输出缓冲区 CE 可用

    此种情况 CE 驱动为输入缓冲区分配物理连续的工作缓冲区,CE 驱动先将输入数据复制到工作缓冲区, 再启动 CE 处理,直接输出到输出缓冲区。

  4. 输入缓冲区和输出缓冲区都是 CE 可用

    此种情况效率最高,CE 直接使用用户提供的输入输出缓冲区。


    ce_data_buffer_for_ce

当用户处理大量数据时,为了提高系统的处理效率,应为输入和输出数据申请按页对齐的缓冲区,这样 CE 驱动可以直接使用,避免额外的复制操作。

中断处理流程

CE 驱动的中断处理比较简单,采用线程化的 IRQ 处理方式实现。

当中断发生时,首先在 irq handler 函数中保存当前的 IRQ 状态寄存器和错误状态寄存器的值。
static irqreturn_t aic_crypto_irq_handler(int irq, void *arg)
{
        struct aic_crypto_dev *ce_dev = arg;

        ce_dev->irq_status = readl(ce_dev->base + CE_REG_ISR);
        ce_dev->err_status = readl(ce_dev->base + CE_REG_ERR);
        writel(ce_dev->irq_status, ce_dev->base + CE_REG_ISR);
        return IRQ_WAKE_THREAD;
}
然后唤醒对应的处理线程,根据中断状态值,调用对应算法加速器的 IRQ 处理函数。
static irqreturn_t aic_crypto_irq_thread(int irq, void *arg)
{
        struct aic_crypto_dev *ce_dev = arg;

        if (ce_dev->irq_status & (0x1 << DMA_CHAN_SK_ACCELERATOR))
                aic_skcipher_handle_irq(ce_dev);
        if (ce_dev->irq_status & (0x1 << DMA_CHAN_AK_ACCELERATOR))
                aic_akcipher_handle_irq(ce_dev);
        if (ce_dev->irq_status & (0x1 << DMA_CHAN_HASH_ACCELERATOR))
                aic_hash_handle_irq(ce_dev);
        return IRQ_HANDLED;
}
如前面所述,各算法加速器的 IRQ 处理函数只做相关资源的释放,以及请求处理完成的通知。
aic_skcipher_handle_irq(ce_dev);
|-> crypto_finalize_skcipher_request(ce->sk_accel.engine, req, err);
    |-> aic_skcipher_unprepare_req(engine, req);
    |-> req.complete(req, err);