Linux Crypto Engine Overview

Overview

The Crypto Engine in the Linux kernel is an efficient encryption queue manager specifically designed to manage and schedule the execution of hardware cryptographic accelerators. Its primary goal is to efficiently offload cryptographic operations (such as encryption, decryption, hashing, authentication, etc.) to dedicated hardware, thereby significantly improving system performance and reducing CPU load.

The structure definition of the Crypto Engine is as follows:

struct crypto_engine {
 char   name[ENGINE_NAME_LEN];
bool   idling;
bool   busy;
bool   running;

bool   retry_support;

struct list_head list;
spinlock_t  queue_lock;
struct crypto_queue queue;
struct device  *dev;

bool   rt;

int (*prepare_crypt_hardware)(struct crypto_engine *engine);
int (*unprepare_crypt_hardware)(struct crypto_engine *engine);
int (*do_batch_requests)(struct crypto_engine *engine);

struct kthread_worker           *kworker;
struct kthread_work             pump_requests;

void    *priv_data;
struct crypto_async_request *cur_req;
};

The Crypto Engine only manages asynchronous requests that exist in the form of <span>crypto_async_request</span>. We need to define a custom transformation context <span>struct your_tfm_ctx</span>, placing <span>struct crypto_engine</span> at the beginning:

struct your_tfm_ctx {
 struct crypto_engine engine;
 ...
};

Since the Crypto Engine is completely unaware of the custom structure type <span>struct your_tfm_ctx</span>, it cannot obtain the transformation context using the <span>container_of</span> method. By placing <span>struct crypto_engine</span> at the start of the custom structure, the pointer to <span>struct crypto_engine *</span> passed by the framework is actually the address of the custom structure, allowing for direct type conversion to obtain the pointer to the custom structure. This way, the framework does not need to know the specific type of the driver’s custom structure, while the driver can conveniently access its own data.

Driver Implementation Callbacks

Before transmitting any requests, the context <span>struct crypto_engine_ctx</span> must be populated, as the Crypto Engine will invoke three callback functions to complete the hardware processing of the request, meaning the three callback functions defined by the engine must be implemented:

struct crypto_engine_op {
 int (*prepare_request)(struct crypto_engine *engine,
          void *areq);
int (*unprepare_request)(struct crypto_engine *engine,
     void *areq);
int (*do_one_request)(struct crypto_engine *engine,
         void *areq);
};

struct crypto_engine_ctx {
struct crypto_engine_op op;
};
  • prepare_request: Prepares for the request before processing, called before each corresponding request execution, such as DMA mapping, hardware initialization, etc.;
  • unprepare_request: Handles post-processing after the request is completed, called after each request processing is finished, such as DMA unmapping, cleaning hardware state, etc.;
  • do_one_request: Processes the current request by executing operations, such as starting hardware operations, triggering DMA transfers;

Note that these functions access the <span>crypto_async_request</span> structure associated with the received request. You can obtain the original request as follows:

container_of(areq, struct yourrequesttype_request, base);

Operation Process

The operation process of the Crypto Engine is as follows:

  1. When the hardware driver detects the device, populate the context <span>crypto_engine_ctx</span> and register the algorithm;
  2. Then call <span>crypto_engine_alloc_init</span> to initialize the engine object and allocate resources;
  3. Next, call <span>crypto_engine_start</span> to activate the engine work queue and start processing requests;
  4. Finally, call <span>crypto_engine_exit</span> to release engine resources and exit.

When the driver receives a <span>crypto_request</span>, it needs to transmit it to the Crypto Engine through the following interfaces:

  • crypto_transfer_aead_request_to_engine(): AEAD authenticated encryption request
  • crypto_transfer_akcipher_request_to_engine(): Asymmetric encryption request
  • crypto_transfer_hash_request_to_engine(): Hash request
  • crypto_transfer_kpp_request_to_engine(): Key agreement request
  • crypto_transfer_skcipher_request_to_engine(): Symmetric encryption request

At the end of request processing, the following interfaces must be called to notify the caller that the request processing is complete:

  • crypto_finalize_aead_request(): AEAD authenticated encryption request processing complete
  • crypto_finalize_akcipher_request(): Asymmetric encryption request processing complete
  • crypto_finalize_hash_request(): Hash request processing complete
  • crypto_finalize_kpp_request(): Key agreement request processing complete
  • crypto_finalize_skcipher_request(): Symmetric encryption request processing complete

Special Note: Finalize calls must be executed after the request has been completely processed (usually in hardware interrupts), otherwise it may lead to request blocking or memory leaks.

A typical driver code for the hardware engine is as follows:

// 1. Define context
struct my_ctx {
    struct crypto_engine engine;
    struct device *dev;
    void __iomem *regbase;
};

// 2. Implement engine operations
staticstruct crypto_engine_ops engine_ops = {
    .prepare_request = my_prepare,
    .unprepare_request = my_unprepare,
    .do_one_request = my_do_request,
};

// 3. Initialize in probe
static int my_probe(struct platform_device *pdev)
{
    struct my_ctx *ctx = devm_kzalloc(...);
    
    // Initialize engine
    ctx-&gt;engine = crypto_engine_alloc_init(dev, true);
    crypto_engine_add_ctx(&amp;ctx-&gt;engine, &amp;engine_ops);
    crypto_engine_start(ctx-&gt;engine);
    
    // Register algorithm
    crypto_register_skcipher(...);
}

// 4. Request entry function
static int my_encrypt(struct skcipher_request *req)
{
    struct my_ctx *ctx = crypto_skcipher_ctx(tfm);
    return crypto_transfer_skcipher_request_to_engine(&amp;ctx-&gt;engine, req);
}

// 5. Interrupt handler
static irqreturn_t my_irq_handler(int irq, void *data)
{
    struct my_ctx *ctx = data;
    struct skcipher_request *req = get_current_request();
    int err = check_hw_error();
    
    crypto_finalize_skcipher_request(&amp;ctx-&gt;engine, req, err);
}

Through this design pattern, driver developers can efficiently utilize the Linux Crypto Engine framework to fully leverage the performance advantages of hardware cryptographic accelerators.

Leave a Comment