// SPDX-License-Identifier: GPL-2.0
/*
* Thunderbolt XDomain property support
*
* Copyright (C) 2017, Intel Corporation
* Authors: Michael Jamet <michael.jamet@intel.com>
* Mika Westerberg <mika.westerberg@linux.intel.com>
*/
#include <linux/err.h>
#include <linux/overflow.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/uuid.h>
#include <linux/thunderbolt.h>
struct tb_property_entry {
u32 key_hi;
u32 key_lo;
u16 length;
u8 reserved;
u8 type;
u32 value;
};
struct tb_property_rootdir_entry {
u32 magic;
u32 length;
struct tb_property_entry entries[];
};
struct tb_property_dir_entry {
u32 uuid[4];
struct tb_property_entry entries[];
};
#define TB_PROPERTY_ROOTDIR_MAGIC 0x55584401
#define TB_PROPERTY_MAX_DEPTH 8
static struct tb_property_dir *__tb_property_parse_dir(const u32 *block,
size_t block_len, unsigned int dir_offset, size_t dir_len,
bool is_root, unsigned int depth);
static struct tb_property *tb_property_copy(const struct tb_property *property);
static inline void parse_dwdata(void *dst, const void *src, size_t dwords)
{
be32_to_cpu_array(dst, src, dwords);
}
static inline void format_dwdata(void *dst, const void *src, size_t dwords)
{
cpu_to_be32_array(dst, src, dwords);
}
static bool tb_property_entry_valid(const struct tb_property_entry *entry,
size_t block_len)
{
u32 end;
switch (entry->type) {
case TB_PROPERTY_TYPE_DIRECTORY:
case TB_PROPERTY_TYPE_DATA:
case TB_PROPERTY_TYPE_TEXT:
if (!entry->length)
return false;
if (entry->length > block_len)
return false;
if (check_add_overflow(entry->value, entry->length, &end) ||
end > block_len)
return false;
break;
case TB_PROPERTY_TYPE_VALUE:
if (entry->length != 1)
return false;
break;
}
return true;
}
static bool tb_property_key_valid(const char *key)
{
return key && strlen(key) <= TB_PROPERTY_KEY_SIZE;
}
static struct tb_property *
tb_property_alloc(const char *key, enum tb_property_type type)
{
struct tb_property *property;
property = kzalloc_obj(*property);
if (!property)
return NULL;
strcpy(property->key, key);
property->type = type;
INIT_LIST_HEAD(&property->list);
return property;
}
static struct tb_property *tb_property_parse(const u32 *block, size_t block_len,
const struct tb_property_entry *entry,
unsigned int depth)
{
char key[TB_PROPERTY_KEY_SIZE + 1];
struct tb_property *property;
struct tb_property_dir *dir;
if (!tb_property_entry_valid(entry, block_len))
return NULL;
parse_dwdata(key, entry, 2);
key[TB_PROPERTY_KEY_SIZE] = '\0';
property = tb_property_alloc(key, entry->type);
if (!property)
return NULL;
property->length = entry->length;
switch (property->type) {
case TB_PROPERTY_TYPE_DIRECTORY:
dir = __tb_property_parse_dir(block, block_len, entry->value,
entry->length, false, depth + 1);
if (!dir) {
kfree(property);
return NULL;
}
property->value.dir = dir;
break;
case TB_PROPERTY_TYPE_DATA:
property->value.data = kcalloc(property->length, sizeof(u32),
GFP_KERNEL);
if (!property->value.data) {
kfree(property);
return NULL;
}
parse_dwdata(property->value.data, block + entry->value,
entry->length);
break;
case TB_PROPERTY_TYPE_TEXT:
property->value.text = kcalloc(property->length, sizeof(u32),
GFP_KERNEL);
if (!property->value.text) {
kfree(property);
return NULL;
}
parse_dwdata(property->value.text, block + entry->value,
entry->length);
/* Force null termination */
property->value.text[property->length * 4 - 1] = '\0';
break;
case TB_PROPERTY_TYPE_VALUE:
property->value.immediate = entry->value;
break;
default:
property->type = TB_PROPERTY_TYPE_UNKNOWN;
break;
}
return property;
}
static struct tb_property_dir *__tb_property_parse_dir(const u32 *block,
size_t block_len, unsigned int dir_offset, size_t dir_len