// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* CCS static data binary parser library
*
* Copyright 2019--2020 Intel Corporation
*/
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/limits.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include "ccs-data-defs.h"
struct bin_container {
void *base;
void *now;
void *end;
size_t size;
};
static void *bin_alloc(struct bin_container *bin, size_t len)
{
void *ptr;
len = ALIGN(len, 8);
if (bin->end - bin->now < len)
return NULL;
ptr = bin->now;
bin->now += len;
return ptr;
}
static void bin_reserve(struct bin_container *bin, size_t len)
{
bin->size += ALIGN(len, 8);
}
static int bin_backing_alloc(struct bin_container *bin)
{
bin->base = bin->now = kvzalloc(bin->size, GFP_KERNEL);
if (!bin->base)
return -ENOMEM;
bin->end = bin->base + bin->size;
return 0;
}
#define is_contained(var, endp) \
(sizeof(*var) <= (endp) - (void *)(var))
#define has_headroom(ptr, headroom, endp) \
((headroom) <= (endp) - (void *)(ptr))
#define is_contained_with_headroom(var, headroom, endp) \
(sizeof(*var) + (headroom) <= (endp) - (void *)(var))
static int
ccs_data_parse_length_specifier(const struct __ccs_data_length_specifier *__len,
size_t *__hlen, size_t *__plen,
const void *endp)
{
size_t hlen, plen;
if (!is_contained(__len, endp))
return -ENODATA;
switch (__len->length >> CCS_DATA_LENGTH_SPECIFIER_SIZE_SHIFT) {
case CCS_DATA_LENGTH_SPECIFIER_1:
hlen = sizeof(*__len);
plen = __len->length &
((1 << CCS_DATA_LENGTH_SPECIFIER_SIZE_SHIFT) - 1);
break;
case CCS_DATA_LENGTH_SPECIFIER_2: {
struct __ccs_data_length_specifier2 *__len2 = (void *)__len;
if (!is_contained(__len2, endp))
return -ENODATA;
hlen = sizeof(*__len2);
plen = ((size_t)
(__len2->length[0] &
((1 << CCS_DATA_LENGTH_SPECIFIER_SIZE_SHIFT) - 1))
<< 8) + __len2->length[1];
break;
}
case CCS_DATA_LENGTH_SPECIFIER_3: {
struct __ccs_data_length_specifier3 *__len3 = (void *)__len;
if (!is_contained(__len3, endp))
return -ENODATA;
hlen = sizeof(*__len3);
plen = ((size_t)
(__len3->length[0] &
((1 << CCS_DATA_LENGTH_SPECIFIER_SIZE_SHIFT) - 1))
<< 16) + (__len3->length[0] << 8) + __len3->length[1];
break;
}
default:
return -EINVAL;
}
if (!has_headroom(__len, hlen + plen, endp))
return -ENODATA;
*__hlen = hlen;
*__plen = plen;
return 0;
}
static u8
ccs_data_parse_format_version(const struct __ccs_data_block *block)
{
return block->id >> CCS_DATA_BLOCK_HEADER_ID_VERSION_SHIFT;
}
static u8 ccs_data_parse_block_id(const struct __ccs_data_block *block,
bool is_first)
{
if (!is_first)
return block->id;
return block->id & ((1 << CCS_DATA_BLOCK_HEADER_ID_VERSION_SHIFT) - 1);
}
static int ccs_data_parse_version(struct bin_container *bin,
struct ccs_data_container *ccsdata,
const void *payload, const void *endp)
{
const struct __ccs_data_block_version *v = payload;
struct ccs_data_block_version *vv;
if (v + 1 != endp)
return -ENODATA;
if (!bin->base) {
bin_reserve(bin, sizeof(*ccsdata->version));
return 0;
}
ccsdata->version = bin_alloc(bin, sizeof(*ccsdata->version));
if (!ccsdata->version)
return -ENOMEM;
vv = ccsdata->version;
vv->version_major = ((u16)v->static_data_version_major[0] << 8) +
v->static_data_version_major[1];
vv->version_minor = ((u16)v->static_data_version_minor[0] << 8) +
v->static_data_version_minor[1];
vv->date_year = ((u16)v->year[0] << 8) + v->year[1];
vv->date_month = v->month;
vv->date_day = v->day;
return 0;
}
static void print_ccs_data_version(struct device *dev,
struct ccs_data_block_version *v)
{
dev_dbg(dev,
"static data version %4.4x.%4.4x, date %4.4u-%2.2u-%2.2u\n",
v->version_major, v->version_minor,
v->date_year, v->date_month, v->date_day);
}
static int ccs_data_block_parse_header(const struct __ccs_data_block *block,
bool is_first, unsigned int *__block_id,
const void **payload,
const struct __ccs_data_block **next_block,
const void *endp, struct device *dev,
bool verbose)
{
size_t plen, hlen;
u8 block_id;
int rval;
if (!is_contained(block, endp))
return -ENODATA;
rval = ccs_data_parse_length_specifier(&block->length, &hlen, &plen,
endp