root/drivers/crypto/intel/qat/qat_common/adf_mstate_mgr.c
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2024 Intel Corporation */

#include <linux/slab.h>
#include <linux/types.h>
#include "adf_mstate_mgr.h"

#define ADF_MSTATE_MAGIC        0xADF5CAEA
#define ADF_MSTATE_VERSION      0x1

struct adf_mstate_sect_h {
        u8 id[ADF_MSTATE_ID_LEN];
        u32 size;
        u32 sub_sects;
        u8 state[];
};

u32 adf_mstate_state_size(struct adf_mstate_mgr *mgr)
{
        return mgr->state - mgr->buf;
}

static inline u32 adf_mstate_avail_room(struct adf_mstate_mgr *mgr)
{
        return mgr->buf + mgr->size - mgr->state;
}

void adf_mstate_mgr_init(struct adf_mstate_mgr *mgr, u8 *buf, u32 size)
{
        mgr->buf = buf;
        mgr->state = buf;
        mgr->size = size;
        mgr->n_sects = 0;
};

struct adf_mstate_mgr *adf_mstate_mgr_new(u8 *buf, u32 size)
{
        struct adf_mstate_mgr *mgr;

        mgr = kzalloc_obj(*mgr);
        if (!mgr)
                return NULL;

        adf_mstate_mgr_init(mgr, buf, size);

        return mgr;
}

void adf_mstate_mgr_destroy(struct adf_mstate_mgr *mgr)
{
        kfree(mgr);
}

void adf_mstate_mgr_init_from_parent(struct adf_mstate_mgr *mgr,
                                     struct adf_mstate_mgr *p_mgr)
{
        adf_mstate_mgr_init(mgr, p_mgr->state,
                            p_mgr->size - adf_mstate_state_size(p_mgr));
}

void adf_mstate_mgr_init_from_psect(struct adf_mstate_mgr *mgr,
                                    struct adf_mstate_sect_h *p_sect)
{
        adf_mstate_mgr_init(mgr, p_sect->state, p_sect->size);
        mgr->n_sects = p_sect->sub_sects;
}

static void adf_mstate_preamble_init(struct adf_mstate_preh *preamble)
{
        preamble->magic = ADF_MSTATE_MAGIC;
        preamble->version = ADF_MSTATE_VERSION;
        preamble->preh_len = sizeof(*preamble);
        preamble->size = 0;
        preamble->n_sects = 0;
}

/* default preambles checker */
static int adf_mstate_preamble_def_checker(struct adf_mstate_preh *preamble,
                                           void *opaque)
{
        struct adf_mstate_mgr *mgr = opaque;

        if (preamble->magic != ADF_MSTATE_MAGIC ||
            preamble->version > ADF_MSTATE_VERSION ||
            preamble->preh_len > mgr->size) {
                pr_debug("QAT: LM - Invalid state (magic=%#x, version=%#x, hlen=%u), state_size=%u\n",
                         preamble->magic, preamble->version, preamble->preh_len,
                         mgr->size);
                return -EINVAL;
        }

        return 0;
}

struct adf_mstate_preh *adf_mstate_preamble_add(struct adf_mstate_mgr *mgr)
{
        struct adf_mstate_preh *pre = (struct adf_mstate_preh *)mgr->buf;

        if (adf_mstate_avail_room(mgr) < sizeof(*pre)) {
                pr_err("QAT: LM - Not enough space for preamble\n");
                return NULL;
        }

        adf_mstate_preamble_init(pre);
        mgr->state += pre->preh_len;

        return pre;
}

int adf_mstate_preamble_update(struct adf_mstate_mgr *mgr)
{
        struct adf_mstate_preh *preamble = (struct adf_mstate_preh *)mgr->buf;

        preamble->size = adf_mstate_state_size(mgr) - preamble->preh_len;
        preamble->n_sects = mgr->n_sects;

        return 0;
}

static void adf_mstate_dump_sect(struct adf_mstate_sect_h *sect,
                                 const char *prefix)
{
        pr_debug("QAT: LM - %s QAT state section %s\n", prefix, sect->id);
        print_hex_dump_debug("h-", DUMP_PREFIX_OFFSET, 16, 2, sect,
                             sizeof(*sect), true);
        print_hex_dump_debug("s-", DUMP_PREFIX_OFFSET, 16, 2, sect->state,
                             sect->size, true);
}

static inline void __adf_mstate_sect_update(struct adf_mstate_mgr *mgr,
                                            struct adf_mstate_sect_h *sect,
                                            u32 size,
                                            u32 n_subsects)
{
        sect->size += size;
        sect->sub_sects += n_subsects;
        mgr->n_sects++;
        mgr->state += sect->size;

        adf_mstate_dump_sect(sect, "Add");
}

void adf_mstate_sect_update(struct adf_mstate_mgr *p_mgr,
                            struct adf_mstate_mgr *curr_mgr,
                            struct adf_mstate_sect_h *sect)
{
        __adf_mstate_sect_update(p_mgr, sect, adf_mstate_state_size(curr_mgr),
                                 curr_mgr->n_sects);
}

static struct adf_mstate_sect_h *adf_mstate_sect_add_header(struct adf_mstate_mgr *mgr,
                                                            const char *id)
{
        struct adf_mstate_sect_h *sect = (struct adf_mstate_sect_h *)(mgr->state);

        if (adf_mstate_avail_room(mgr) < sizeof(*sect)) {
                pr_debug("QAT: LM - Not enough space for header of QAT state sect %s\n", id);
                return NULL;
        }

        strscpy(sect->id, id, sizeof(sect->id));
        sect->size = 0;
        sect->sub_sects = 0;
        mgr->state += sizeof(*sect);

        return sect;
}

struct adf_mstate_sect_h *adf_mstate_sect_add_vreg(struct adf_mstate_mgr *mgr,
                                                   const char *id,
                                                   struct adf_mstate_vreginfo *info)
{
        struct adf_mstate_sect_h *sect;

        sect = adf_mstate_sect_add_header(mgr, id);
        if (!sect)
                return NULL;

        if (adf_mstate_avail_room(mgr) < info->size) {
                pr_debug("QAT: LM - Not enough space for QAT state sect %s, requires %u\n",
                         id, info->size);
                return NULL;
        }

        memcpy(sect->state, info->addr, info->size);
        __adf_mstate_sect_update(mgr, sect, info->size, 0);

        return sect;
}

struct adf_mstate_sect_h *adf_mstate_sect_add(struct adf_mstate_mgr *mgr,
                                              const char *id,
                                              adf_mstate_populate populate,
                                              void *opaque)
{
        struct adf_mstate_mgr sub_sects_mgr;
        struct adf_mstate_sect_h *sect;
        int avail_room, size;

        sect = adf_mstate_sect_add_header(mgr, id);
        if (!sect)
                return NULL;

        if (!populate)
                return sect;

        avail_room = adf_mstate_avail_room(mgr);
        adf_mstate_mgr_init_from_parent(&sub_sects_mgr, mgr);

        size = (*populate)(&sub_sects_mgr, sect->state, avail_room, opaque);
        if (size < 0)
                return NULL;

        size += adf_mstate_state_size(&sub_sects_mgr);
        if (avail_room < size) {
                pr_debug("QAT: LM - Not enough space for QAT state sect %s, requires %u\n",
                         id, size);
                return NULL;
        }
        __adf_mstate_sect_update(mgr, sect, size, sub_sects_mgr.n_sects);

        return sect;
}

static int adf_mstate_sect_validate(struct adf_mstate_mgr *mgr)
{
        struct adf_mstate_sect_h *start = (struct adf_mstate_sect_h *)mgr->state;
        struct adf_mstate_sect_h *sect = start;
        u64 end;
        int i;

        end = (uintptr_t)mgr->buf + mgr->size;
        for (i = 0; i < mgr->n_sects; i++) {
                uintptr_t s_start = (uintptr_t)sect->state;
                uintptr_t s_end = s_start + sect->size;

                if (s_end < s_start || s_end > end) {
                        pr_debug("QAT: LM - Corrupted state section (index=%u, size=%u) in state_mgr (size=%u, secs=%u)\n",
                                 i, sect->size, mgr->size, mgr->n_sects);
                        return -EINVAL;
                }
                sect = (struct adf_mstate_sect_h *)s_end;
        }

        pr_debug("QAT: LM - Scanned section (last child=%s, size=%lu) in state_mgr (size=%u, secs=%u)\n",
                 start->id, sizeof(struct adf_mstate_sect_h) * (ulong)(sect - start),
                 mgr->size, mgr->n_sects);

        return 0;
}

u32 adf_mstate_state_size_from_remote(struct adf_mstate_mgr *mgr)
{
        struct adf_mstate_preh *preh = (struct adf_mstate_preh *)mgr->buf;

        return preh->preh_len + preh->size;
}

int adf_mstate_mgr_init_from_remote(struct adf_mstate_mgr *mgr, u8 *buf, u32 size,
                                    adf_mstate_preamble_checker pre_checker,
                                    void *opaque)
{
        struct adf_mstate_preh *pre;
        int ret;

        adf_mstate_mgr_init(mgr, buf, size);
        pre = (struct adf_mstate_preh *)(mgr->buf);

        pr_debug("QAT: LM - Dump state preambles\n");
        print_hex_dump_debug("", DUMP_PREFIX_OFFSET, 16, 2, pre, pre->preh_len, 0);

        if (pre_checker)
                ret = (*pre_checker)(pre, opaque);
        else
                ret = adf_mstate_preamble_def_checker(pre, mgr);
        if (ret)
                return ret;

        mgr->state = mgr->buf + pre->preh_len;
        mgr->n_sects = pre->n_sects;

        return adf_mstate_sect_validate(mgr);
}

struct adf_mstate_sect_h *adf_mstate_sect_lookup(struct adf_mstate_mgr *mgr,
                                                 const char *id,
                                                 adf_mstate_action action,
                                                 void *opaque)
{
        struct adf_mstate_sect_h *sect = (struct adf_mstate_sect_h *)mgr->state;
        struct adf_mstate_mgr sub_sects_mgr;
        int i, ret;

        for (i = 0; i < mgr->n_sects; i++) {
                if (!strncmp(sect->id, id, sizeof(sect->id)))
                        goto found;

                sect = (struct adf_mstate_sect_h *)(sect->state + sect->size);
        }

        return NULL;

found:
        adf_mstate_dump_sect(sect, "Found");

        adf_mstate_mgr_init_from_psect(&sub_sects_mgr, sect);
        if (sect->sub_sects && adf_mstate_sect_validate(&sub_sects_mgr))
                return NULL;

        if (!action)
                return sect;

        ret = (*action)(&sub_sects_mgr, sect->state, sect->size, opaque);
        if (ret)
                return NULL;

        return sect;
}