关键流程设计
初始化流程
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 中几种类型算法的数据处理流程相似,这里仅以对称密钥算法的数据处理流程为例进行说明。
在处理步骤上,各种算法都遵循标准化的几个步骤:
-
从 Crypto Core 层将处理请求传递给 CE 算法
-
CE 算法处理函数将请求转交给(transfer)给对应加速器的 crypto_engine 队列
-
crypto_engine 中的处理线程从队列中取出请求,调用对应的 prepare/do_one_req 进行处理
-
do_one_req 回调函数中,将对应的请求交给硬件处理
-
在中断处理函数中,取出结果,返回给调用者
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 而言有两个问题:
-
这些缓冲区是虚拟地址空间的内存,并不一定是物理连续的内存空间
-
缓冲区的开始地址并不一定是对齐的,不一定满足 CE 的地址对齐要求
因此需要对输入和输出的数据做一些处理。
一个简单的处理方式是对输入和输出的数据,一律复制到驱动新申请的物理连续的缓冲区中, 使用该空间作为 CE 的硬件工作缓冲区,处理完成之后再复制到用户提供的输出缓冲区。 但是对每一笔数据都会有额外的两次数据拷贝操作,对于处理大量数据的应用场景,效率较低。
为了兼顾数据处理效率,CE 驱动针对可能出现的情况,做了几个分类, 原则上尽量避免数据拷贝 。
-
输入缓冲区和输出缓冲区 CE 都无法使用
此种情况 CE 驱动为输入和输出缓冲区分配物理连续的工作缓冲区,并且需要对输入和输出数据进行复制。
-
输入缓冲区 CE 可用,输出缓冲区 CE 不可用
此种情况 CE 驱动为输出缓冲区分配物理连续的工作缓冲区,CE 将数据处理完成之后,再复制到用户提供的输出缓冲区。
-
输入缓冲区 CE 不可用,输出缓冲区 CE 可用
此种情况 CE 驱动为输入缓冲区分配物理连续的工作缓冲区,CE 驱动先将输入数据复制到工作缓冲区, 再启动 CE 处理,直接输出到输出缓冲区。
输入缓冲区和输出缓冲区都是 CE 可用
此种情况效率最高,CE 直接使用用户提供的输入输出缓冲区。
当用户处理大量数据时,为了提高系统的处理效率,应为输入和输出数据申请按页对齐的缓冲区,这样 CE 驱动可以直接使用,避免额外的复制操作。
中断处理流程
CE 驱动的中断处理比较简单,采用线程化的 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;
}
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;
}
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);