// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2019 HiSilicon Limited. */ #include #include #include #include #include "zip.h" #define HZIP_ZLIB_HEAD_SIZE 2 #define HZIP_GZIP_HEAD_SIZE 10 #define GZIP_HEAD_FHCRC_BIT BIT(1) #define GZIP_HEAD_FEXTRA_BIT BIT(2) #define GZIP_HEAD_FNAME_BIT BIT(3) #define GZIP_HEAD_FCOMMENT_BIT BIT(4) #define GZIP_HEAD_FLG_SHIFT 3 #define GZIP_HEAD_FEXTRA_SHIFT 10 #define GZIP_HEAD_FEXTRA_XLEN 2 #define GZIP_HEAD_FHCRC_SIZE 2 #define HZIP_CTX_Q_NUM 2 #define HZIP_GZIP_HEAD_BUF 256 #define HZIP_ALG_PRIORITY 300 #define HZIP_SGL_SGE_NR 10 static const u8 zlib_head[HZIP_ZLIB_HEAD_SIZE] = {0x78, 0x9c}; static const u8 gzip_head[HZIP_GZIP_HEAD_SIZE] = {0x1f, 0x8b, 0x08, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x03}; enum hisi_zip_alg_type { HZIP_ALG_TYPE_COMP = 0, HZIP_ALG_TYPE_DECOMP = 1, }; #define COMP_NAME_TO_TYPE(alg_name) \ (!strcmp((alg_name), "zlib-deflate") ? HZIP_ALG_TYPE_ZLIB : \ !strcmp((alg_name), "gzip") ? HZIP_ALG_TYPE_GZIP : 0) \ #define TO_HEAD_SIZE(req_type) \ (((req_type) == HZIP_ALG_TYPE_ZLIB) ? sizeof(zlib_head) : \ ((req_type) == HZIP_ALG_TYPE_GZIP) ? sizeof(gzip_head) : 0) \ #define TO_HEAD(req_type) \ (((req_type) == HZIP_ALG_TYPE_ZLIB) ? zlib_head : \ ((req_type) == HZIP_ALG_TYPE_GZIP) ? gzip_head : NULL) \ struct hisi_zip_req { struct acomp_req *req; int sskip; int dskip; struct hisi_acc_hw_sgl *hw_src; struct hisi_acc_hw_sgl *hw_dst; dma_addr_t dma_src; dma_addr_t dma_dst; int req_id; }; struct hisi_zip_req_q { struct hisi_zip_req *q; unsigned long *req_bitmap; rwlock_t req_lock; u16 size; }; struct hisi_zip_qp_ctx { struct hisi_qp *qp; struct hisi_zip_req_q req_q; struct hisi_acc_sgl_pool *sgl_pool; struct hisi_zip *zip_dev; struct hisi_zip_ctx *ctx; }; struct hisi_zip_ctx { #define QPC_COMP 0 #define QPC_DECOMP 1 struct hisi_zip_qp_ctx qp_ctx[HZIP_CTX_Q_NUM]; }; static int sgl_sge_nr_set(const char *val, const struct kernel_param *kp) { int ret; u16 n; if (!val) return -EINVAL; ret = kstrtou16(val, 10, &n); if (ret || n == 0 || n > HISI_ACC_SGL_SGE_NR_MAX) return -EINVAL; return param_set_int(val, kp); } static const struct kernel_param_ops sgl_sge_nr_ops = { .set = sgl_sge_nr_set, .get = param_get_int, }; static u16 sgl_sge_nr = HZIP_SGL_SGE_NR; module_param_cb(sgl_sge_nr, &sgl_sge_nr_ops, &sgl_sge_nr, 0444); MODULE_PARM_DESC(sgl_sge_nr, "Number of sge in sgl(1-255)"); static void hisi_zip_config_buf_type(struct hisi_zip_sqe *sqe, u8 buf_type) { u32 val; val = (sqe->dw9) & ~HZIP_BUF_TYPE_M; val |= FIELD_PREP(HZIP_BUF_TYPE_M, buf_type); sqe->dw9 = val; } static void hisi_zip_config_tag(struct hisi_zip_sqe *sqe, u32 tag) { sqe->tag = tag; } static void hisi_zip_fill_sqe(struct hisi_zip_sqe *sqe, u8 req_type, dma_addr_t s_addr, dma_addr_t d_addr, u32 slen, u32 dlen, int sskip, int dskip) { memset(sqe, 0, sizeof(struct hisi_zip_sqe)); sqe->input_data_length = slen - sskip; sqe->dw7 = FIELD_PREP(HZIP_IN_SGE_DATA_OFFSET_M, sskip); sqe->dw8 = FIELD_PREP(HZIP_OUT_SGE_DATA_OFFSET_M, dskip); sqe->dw9 = FIELD_PREP(HZIP_REQ_TYPE_M, req_type); sqe->dest_avail_out = dlen - dskip; sqe->source_addr_l = lower_32_bits(s_addr); sqe->source_addr_h = upper_32_bits(s_addr); sqe->dest_addr_l = lower_32_bits(d_addr); sqe->dest_addr_h = upper_32_bits(d_addr); } static int hisi_zip_start_qp(struct hisi_qp *qp, struct hisi_zip_qp_ctx *ctx, int alg_type, int req_type) { struct device *dev = &qp->qm->pdev->dev; int ret; qp->req_type = req_type; qp->alg_type = alg_type; qp->qp_ctx = ctx; ret = hisi_qm_start_qp(qp, 0); if (ret < 0) { dev_err(dev, "start qp failed!\n"); return ret; } ctx->qp = qp; return 0; } static void hisi_zip_release_qp(struct hisi_zip_qp_ctx *ctx) { hisi_qm_stop_qp(ctx->qp); hisi_qm_release_qp(ctx->qp); } static int hisi_zip_ctx_init(struct hisi_zip_ctx *hisi_zip_ctx, u8 req_type, int node) { struct hisi_qp *qps[HZIP_CTX_Q_NUM] = { NULL }; struct hisi_zip *hisi_zip; int ret, i, j; ret = zip_create_qps(qps, HZIP_CTX_Q_NUM, node); if (ret) { pr_err("Can not create zip qps!\n"); return -ENODEV; } hisi_zip = container_of(qps[0]->qm, struct hisi_zip, qm); for (i = 0; i < HZIP_CTX_Q_NUM; i++) { /* alg_type = 0 for compress, 1 for decompress in hw sqe */ ret = hisi_zip_start_qp(qps[i], &hisi_zip_ctx->qp_ctx[i], i, req_type); if (ret) { for (j = i - 1; j >= 0; j--) hisi_qm_stop_qp(hisi_zip_ctx->qp_ctx[j].qp); hisi_qm_free_qps(qps, HZIP_CTX_Q_NUM); return ret; } hisi_zip_ctx->qp_ctx[i].zip_dev = hisi_zip; } return 0; } static void hisi_zip_ctx_exit(struct hisi_zip_ctx *hisi_zip_ctx) { int i; for (i = 1; i >= 0; i--) hisi_zip_release_qp(&hisi_zip_ctx->qp_ctx[i]); } static u16 get_extra_field_size(const u8 *start) { return *((u16 *)start) + GZIP_HEAD_FEXTRA_XLEN; } static u32 get_name_field_size(const u8 *start) { return strlen(start) + 1; } static u32 get_comment_field_size(const u8 *start) { return strlen(start) + 1; } static u32 __get_gzip_head_size(const u8 *src) { u8 head_flg = *(src + GZIP_HEAD_FLG_SHIFT); u32 size = GZIP_HEAD_FEXTRA_SHIFT; if (head_flg & GZIP_HEAD_FEXTRA_BIT) size += get_extra_field_size(src + size); if (head_flg & GZIP_HEAD_FNAME_BIT) size += get_name_field_size(src + size); if (head_flg & GZIP_HEAD_FCOMMENT_BIT) size += get_comment_field_size(src + size); if (head_flg & GZIP_HEAD_FHCRC_BIT) size += GZIP_HEAD_FHCRC_SIZE; return size; } static int hisi_zip_create_req_q(struct hisi_zip_ctx *ctx) { struct hisi_zip_req_q *req_q; int i, ret; for (i = 0; i < HZIP_CTX_Q_NUM; i++) { req_q = &ctx->qp_ctx[i].req_q; req_q->size = QM_Q_DEPTH; req_q->req_bitmap = kcalloc(BITS_TO_LONGS(req_q->size), sizeof(long), GFP_KERNEL); if (!req_q->req_bitmap) { ret = -ENOMEM; if (i == 0) return ret; goto err_free_loop0; } rwlock_init(&req_q->req_lock); req_q->q = kcalloc(req_q->size, sizeof(struct hisi_zip_req), GFP_KERNEL); if (!req_q->q) { ret = -ENOMEM; if (i == 0) goto err_free_bitmap; else goto err_free_loop1; } } return 0; err_free_loop1: kfree(ctx->qp_ctx[QPC_DECOMP].req_q.req_bitmap); err_free_loop0: kfree(ctx->qp_ctx[QPC_COMP].req_q.q); err_free_bitmap: kfree(ctx->qp_ctx[QPC_COMP].req_q.req_bitmap); return ret; } static void hisi_zip_release_req_q(struct hisi_zip_ctx *ctx) { int i; for (i = 0; i < HZIP_CTX_Q_NUM; i++) { kfree(ctx->qp_ctx[i].req_q.q); kfree(ctx->qp_ctx[i].req_q.req_bitmap); } } static int hisi_zip_create_sgl_pool(struct hisi_zip_ctx *ctx) { struct hisi_zip_qp_ctx *tmp; struct device *dev; int i; for (i = 0; i < HZIP_CTX_Q_NUM; i++) { tmp = &ctx->qp_ctx[i]; dev = &tmp->qp->qm->pdev->dev; tmp->sgl_pool = hisi_acc_create_sgl_pool(dev, QM_Q_DEPTH << 1, sgl_sge_nr); if (IS_ERR(tmp->sgl_pool)) { if (i == 1) goto err_free_sgl_pool0; return -ENOMEM; } } return 0; err_free_sgl_pool0: hisi_acc_free_sgl_pool(&ctx->qp_ctx[QPC_COMP].qp->qm->pdev->dev, ctx->qp_ctx[QPC_COMP].sgl_pool); return -ENOMEM; } static void hisi_zip_release_sgl_pool(struct hisi_zip_ctx *ctx) { int i; for (i = 0; i < HZIP_CTX_Q_NUM; i++) hisi_acc_free_sgl_pool(&ctx->qp_ctx[i].qp->qm->pdev->dev, ctx->qp_ctx[i].sgl_pool); } static void hisi_zip_remove_req(struct hisi_zip_qp_ctx *qp_ctx, struct hisi_zip_req *req) { struct hisi_zip_req_q *req_q = &qp_ctx->req_q; write_lock(&req_q->req_lock); clear_bit(req->req_id, req_q->req_bitmap); memset(req, 0, sizeof(struct hisi_zip_req)); write_unlock(&req_q->req_lock); } static void hisi_zip_acomp_cb(struct hisi_qp *qp, void *data) { struct hisi_zip_sqe *sqe = data; struct hisi_zip_qp_ctx *qp_ctx = qp->qp_ctx; struct hisi_zip_dfx *dfx = &qp_ctx->zip_dev->dfx; struct hisi_zip_req_q *req_q = &qp_ctx->req_q; struct hisi_zip_req *req = req_q->q + sqe->tag; struct acomp_req *acomp_req = req->req; struct device *dev = &qp->qm->pdev->dev; u32 status, dlen, head_size; int err = 0; atomic64_inc(&dfx->recv_cnt); status = sqe->dw3 & HZIP_BD_STATUS_M; if (status != 0 && status != HZIP_NC_ERR) { dev_err(dev, "%scompress fail in qp%u: %u, output: %u\n", (qp->alg_type == 0) ? "" : "de", qp->qp_id, status, sqe->produced); atomic64_inc(&dfx->err_bd_cnt); err = -EIO; } dlen = sqe->produced; hisi_acc_sg_buf_unmap(dev, acomp_req->src, req->hw_src); hisi_acc_sg_buf_unmap(dev, acomp_req->dst, req->hw_dst); head_size = (qp->alg_type == 0) ? TO_HEAD_SIZE(qp->req_type) : 0; acomp_req->dlen = dlen + head_size; if (acomp_req->base.complete) acomp_request_complete(acomp_req, err); hisi_zip_remove_req(qp_ctx, req); } static void hisi_zip_set_acomp_cb(struct hisi_zip_ctx *ctx, void (*fn)(struct hisi_qp *, void *)) { int i; for (i = 0; i < HZIP_CTX_Q_NUM; i++) ctx->qp_ctx[i].qp->req_cb = fn; } static int hisi_zip_acomp_init(struct crypto_acomp *tfm) { const char *alg_name = crypto_tfm_alg_name(&tfm->base); struct hisi_zip_ctx *ctx = crypto_tfm_ctx(&tfm->base); int ret; ret = hisi_zip_ctx_init(ctx, COMP_NAME_TO_TYPE(alg_name), tfm->base.node); if (ret) return ret; ret = hisi_zip_create_req_q(ctx); if (ret) goto err_ctx_exit; ret = hisi_zip_create_sgl_pool(ctx); if (ret) goto err_release_req_q; hisi_zip_set_acomp_cb(ctx, hisi_zip_acomp_cb); return 0; err_release_req_q: hisi_zip_release_req_q(ctx); err_ctx_exit: hisi_zip_ctx_exit(ctx); return ret; } static void hisi_zip_acomp_exit(struct crypto_acomp *tfm) { struct hisi_zip_ctx *ctx = crypto_tfm_ctx(&tfm->base); hisi_zip_set_acomp_cb(ctx, NULL); hisi_zip_release_sgl_pool(ctx); hisi_zip_release_req_q(ctx); hisi_zip_ctx_exit(ctx); } static int add_comp_head(struct scatterlist *dst, u8 req_type) { int head_size = TO_HEAD_SIZE(req_type); const u8 *head = TO_HEAD(req_type); int ret; ret = sg_copy_from_buffer(dst, sg_nents(dst), head, head_size); if (ret != head_size) return -ENOMEM; return head_size; } static size_t get_gzip_head_size(struct scatterlist *sgl) { char buf[HZIP_GZIP_HEAD_BUF]; sg_copy_to_buffer(sgl, sg_nents(sgl), buf, sizeof(buf)); return __get_gzip_head_size(buf); } static size_t get_comp_head_size(struct scatterlist *src, u8 req_type) { switch (req_type) { case HZIP_ALG_TYPE_ZLIB: return TO_HEAD_SIZE(HZIP_ALG_TYPE_ZLIB); case HZIP_ALG_TYPE_GZIP: return get_gzip_head_size(src); default: pr_err("request type does not support!\n"); return -EINVAL; } } static struct hisi_zip_req *hisi_zip_create_req(struct acomp_req *req, struct hisi_zip_qp_ctx *qp_ctx, size_t head_size, bool is_comp) { struct hisi_zip_req_q *req_q = &qp_ctx->req_q; struct hisi_zip_req *q = req_q->q; struct hisi_zip_req *req_cache; int req_id; write_lock(&req_q->req_lock); req_id = find_first_zero_bit(req_q->req_bitmap, req_q->size); if (req_id >= req_q->size) { write_unlock(&req_q->req_lock); dev_dbg(&qp_ctx->qp->qm->pdev->dev, "req cache is full!\n"); return ERR_PTR(-EBUSY); } set_bit(req_id, req_q->req_bitmap); req_cache = q + req_id; req_cache->req_id = req_id; req_cache->req = req; if (is_comp) { req_cache->sskip = 0; req_cache->dskip = head_size; } else { req_cache->sskip = head_size; req_cache->dskip = 0; } write_unlock(&req_q->req_lock); return req_cache; } static int hisi_zip_do_work(struct hisi_zip_req *req, struct hisi_zip_qp_ctx *qp_ctx) { struct acomp_req *a_req = req->req; struct hisi_qp *qp = qp_ctx->qp; struct device *dev = &qp->qm->pdev->dev; struct hisi_acc_sgl_pool *pool = qp_ctx->sgl_pool; struct hisi_zip_dfx *dfx = &qp_ctx->zip_dev->dfx; struct hisi_zip_sqe zip_sqe; dma_addr_t input; dma_addr_t output; int ret; if (!a_req->src || !a_req->slen || !a_req->dst || !a_req->dlen) return -EINVAL; req->hw_src = hisi_acc_sg_buf_map_to_hw_sgl(dev, a_req->src, pool, req->req_id << 1, &input); if (IS_ERR(req->hw_src)) return PTR_ERR(req->hw_src); req->dma_src = input; req->hw_dst = hisi_acc_sg_buf_map_to_hw_sgl(dev, a_req->dst, pool, (req->req_id << 1) + 1, &output); if (IS_ERR(req->hw_dst)) { ret = PTR_ERR(req->hw_dst); goto err_unmap_input; } req->dma_dst = output; hisi_zip_fill_sqe(&zip_sqe, qp->req_type, input, output, a_req->slen, a_req->dlen, req->sskip, req->dskip); hisi_zip_config_buf_type(&zip_sqe, HZIP_SGL); hisi_zip_config_tag(&zip_sqe, req->req_id); /* send command to start a task */ atomic64_inc(&dfx->send_cnt); ret = hisi_qp_send(qp, &zip_sqe); if (ret < 0) { atomic64_inc(&dfx->send_busy_cnt); goto err_unmap_output; } return -EINPROGRESS; err_unmap_output: hisi_acc_sg_buf_unmap(dev, a_req->dst, req->hw_dst); err_unmap_input: hisi_acc_sg_buf_unmap(dev, a_req->src, req->hw_src); return ret; } static int hisi_zip_acompress(struct acomp_req *acomp_req) { struct hisi_zip_ctx *ctx = crypto_tfm_ctx(acomp_req->base.tfm); struct hisi_zip_qp_ctx *qp_ctx = &ctx->qp_ctx[QPC_COMP]; struct hisi_zip_req *req; int head_size; int ret; /* let's output compression head now */ head_size = add_comp_head(acomp_req->dst, qp_ctx->qp->req_type); if (head_size < 0) return -ENOMEM; req = hisi_zip_create_req(acomp_req, qp_ctx, (size_t)head_size, true); if (IS_ERR(req)) return PTR_ERR(req); ret = hisi_zip_do_work(req, qp_ctx); if (ret != -EINPROGRESS) hisi_zip_remove_req(qp_ctx, req); return ret; } static int hisi_zip_adecompress(struct acomp_req *acomp_req) { struct hisi_zip_ctx *ctx = crypto_tfm_ctx(acomp_req->base.tfm); struct hisi_zip_qp_ctx *qp_ctx = &ctx->qp_ctx[QPC_DECOMP]; struct hisi_zip_req *req; size_t head_size; int ret; head_size = get_comp_head_size(acomp_req->src, qp_ctx->qp->req_type); req = hisi_zip_create_req(acomp_req, qp_ctx, head_size, false); if (IS_ERR(req)) return PTR_ERR(req); ret = hisi_zip_do_work(req, qp_ctx); if (ret != -EINPROGRESS) hisi_zip_remove_req(qp_ctx, req); return ret; } static struct acomp_alg hisi_zip_acomp_zlib = { .init = hisi_zip_acomp_init, .exit = hisi_zip_acomp_exit, .compress = hisi_zip_acompress, .decompress = hisi_zip_adecompress, .base = { .cra_name = "zlib-deflate", .cra_driver_name = "hisi-zlib-acomp", .cra_module = THIS_MODULE, .cra_priority = HZIP_ALG_PRIORITY, .cra_ctxsize = sizeof(struct hisi_zip_ctx), } }; static struct acomp_alg hisi_zip_acomp_gzip = { .init = hisi_zip_acomp_init, .exit = hisi_zip_acomp_exit, .compress = hisi_zip_acompress, .decompress = hisi_zip_adecompress, .base = { .cra_name = "gzip", .cra_driver_name = "hisi-gzip-acomp", .cra_module = THIS_MODULE, .cra_priority = HZIP_ALG_PRIORITY, .cra_ctxsize = sizeof(struct hisi_zip_ctx), } }; int hisi_zip_register_to_crypto(void) { int ret = 0; ret = crypto_register_acomp(&hisi_zip_acomp_zlib); if (ret) { pr_err("Zlib acomp algorithm registration failed\n"); return ret; } ret = crypto_register_acomp(&hisi_zip_acomp_gzip); if (ret) { pr_err("Gzip acomp algorithm registration failed\n"); crypto_unregister_acomp(&hisi_zip_acomp_zlib); } return ret; } void hisi_zip_unregister_from_crypto(void) { crypto_unregister_acomp(&hisi_zip_acomp_gzip); crypto_unregister_acomp(&hisi_zip_acomp_zlib); }