brintos

brintos / linux-shallow public Read only

0
0
Text · 18.3 KiB · dc555cd Raw
753 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Thunderbolt XDomain property support4 *5 * Copyright (C) 2017, Intel Corporation6 * Authors: Michael Jamet <michael.jamet@intel.com>7 *          Mika Westerberg <mika.westerberg@linux.intel.com>8 */9 10#include <linux/err.h>11#include <linux/slab.h>12#include <linux/string.h>13#include <linux/uuid.h>14#include <linux/thunderbolt.h>15 16struct tb_property_entry {17	u32 key_hi;18	u32 key_lo;19	u16 length;20	u8 reserved;21	u8 type;22	u32 value;23};24 25struct tb_property_rootdir_entry {26	u32 magic;27	u32 length;28	struct tb_property_entry entries[];29};30 31struct tb_property_dir_entry {32	u32 uuid[4];33	struct tb_property_entry entries[];34};35 36#define TB_PROPERTY_ROOTDIR_MAGIC	0x5558440137 38static struct tb_property_dir *__tb_property_parse_dir(const u32 *block,39	size_t block_len, unsigned int dir_offset, size_t dir_len,40	bool is_root);41 42static inline void parse_dwdata(void *dst, const void *src, size_t dwords)43{44	be32_to_cpu_array(dst, src, dwords);45}46 47static inline void format_dwdata(void *dst, const void *src, size_t dwords)48{49	cpu_to_be32_array(dst, src, dwords);50}51 52static bool tb_property_entry_valid(const struct tb_property_entry *entry,53				  size_t block_len)54{55	switch (entry->type) {56	case TB_PROPERTY_TYPE_DIRECTORY:57	case TB_PROPERTY_TYPE_DATA:58	case TB_PROPERTY_TYPE_TEXT:59		if (entry->length > block_len)60			return false;61		if (entry->value + entry->length > block_len)62			return false;63		break;64 65	case TB_PROPERTY_TYPE_VALUE:66		if (entry->length != 1)67			return false;68		break;69	}70 71	return true;72}73 74static bool tb_property_key_valid(const char *key)75{76	return key && strlen(key) <= TB_PROPERTY_KEY_SIZE;77}78 79static struct tb_property *80tb_property_alloc(const char *key, enum tb_property_type type)81{82	struct tb_property *property;83 84	property = kzalloc(sizeof(*property), GFP_KERNEL);85	if (!property)86		return NULL;87 88	strcpy(property->key, key);89	property->type = type;90	INIT_LIST_HEAD(&property->list);91 92	return property;93}94 95static struct tb_property *tb_property_parse(const u32 *block, size_t block_len,96					const struct tb_property_entry *entry)97{98	char key[TB_PROPERTY_KEY_SIZE + 1];99	struct tb_property *property;100	struct tb_property_dir *dir;101 102	if (!tb_property_entry_valid(entry, block_len))103		return NULL;104 105	parse_dwdata(key, entry, 2);106	key[TB_PROPERTY_KEY_SIZE] = '\0';107 108	property = tb_property_alloc(key, entry->type);109	if (!property)110		return NULL;111 112	property->length = entry->length;113 114	switch (property->type) {115	case TB_PROPERTY_TYPE_DIRECTORY:116		dir = __tb_property_parse_dir(block, block_len, entry->value,117					      entry->length, false);118		if (!dir) {119			kfree(property);120			return NULL;121		}122		property->value.dir = dir;123		break;124 125	case TB_PROPERTY_TYPE_DATA:126		property->value.data = kcalloc(property->length, sizeof(u32),127					       GFP_KERNEL);128		if (!property->value.data) {129			kfree(property);130			return NULL;131		}132		parse_dwdata(property->value.data, block + entry->value,133			     entry->length);134		break;135 136	case TB_PROPERTY_TYPE_TEXT:137		property->value.text = kcalloc(property->length, sizeof(u32),138					       GFP_KERNEL);139		if (!property->value.text) {140			kfree(property);141			return NULL;142		}143		parse_dwdata(property->value.text, block + entry->value,144			     entry->length);145		/* Force null termination */146		property->value.text[property->length * 4 - 1] = '\0';147		break;148 149	case TB_PROPERTY_TYPE_VALUE:150		property->value.immediate = entry->value;151		break;152 153	default:154		property->type = TB_PROPERTY_TYPE_UNKNOWN;155		break;156	}157 158	return property;159}160 161static struct tb_property_dir *__tb_property_parse_dir(const u32 *block,162	size_t block_len, unsigned int dir_offset, size_t dir_len, bool is_root)163{164	const struct tb_property_entry *entries;165	size_t i, content_len, nentries;166	unsigned int content_offset;167	struct tb_property_dir *dir;168 169	dir = kzalloc(sizeof(*dir), GFP_KERNEL);170	if (!dir)171		return NULL;172 173	if (is_root) {174		content_offset = dir_offset + 2;175		content_len = dir_len;176	} else {177		dir->uuid = kmemdup(&block[dir_offset], sizeof(*dir->uuid),178				    GFP_KERNEL);179		if (!dir->uuid) {180			tb_property_free_dir(dir);181			return NULL;182		}183		content_offset = dir_offset + 4;184		content_len = dir_len - 4; /* Length includes UUID */185	}186 187	entries = (const struct tb_property_entry *)&block[content_offset];188	nentries = content_len / (sizeof(*entries) / 4);189 190	INIT_LIST_HEAD(&dir->properties);191 192	for (i = 0; i < nentries; i++) {193		struct tb_property *property;194 195		property = tb_property_parse(block, block_len, &entries[i]);196		if (!property) {197			tb_property_free_dir(dir);198			return NULL;199		}200 201		list_add_tail(&property->list, &dir->properties);202	}203 204	return dir;205}206 207/**208 * tb_property_parse_dir() - Parses properties from given property block209 * @block: Property block to parse210 * @block_len: Number of dword elements in the property block211 *212 * This function parses the XDomain properties data block into format that213 * can be traversed using the helper functions provided by this module.214 * Upon success returns the parsed directory. In case of error returns215 * %NULL. The resulting &struct tb_property_dir needs to be released by216 * calling tb_property_free_dir() when not needed anymore.217 *218 * The @block is expected to be root directory.219 */220struct tb_property_dir *tb_property_parse_dir(const u32 *block,221					      size_t block_len)222{223	const struct tb_property_rootdir_entry *rootdir =224		(const struct tb_property_rootdir_entry *)block;225 226	if (rootdir->magic != TB_PROPERTY_ROOTDIR_MAGIC)227		return NULL;228	if (rootdir->length > block_len)229		return NULL;230 231	return __tb_property_parse_dir(block, block_len, 0, rootdir->length,232				       true);233}234 235/**236 * tb_property_create_dir() - Creates new property directory237 * @uuid: UUID used to identify the particular directory238 *239 * Creates new, empty property directory. If @uuid is %NULL then the240 * directory is assumed to be root directory.241 */242struct tb_property_dir *tb_property_create_dir(const uuid_t *uuid)243{244	struct tb_property_dir *dir;245 246	dir = kzalloc(sizeof(*dir), GFP_KERNEL);247	if (!dir)248		return NULL;249 250	INIT_LIST_HEAD(&dir->properties);251	if (uuid) {252		dir->uuid = kmemdup(uuid, sizeof(*dir->uuid), GFP_KERNEL);253		if (!dir->uuid) {254			kfree(dir);255			return NULL;256		}257	}258 259	return dir;260}261EXPORT_SYMBOL_GPL(tb_property_create_dir);262 263static void tb_property_free(struct tb_property *property)264{265	switch (property->type) {266	case TB_PROPERTY_TYPE_DIRECTORY:267		tb_property_free_dir(property->value.dir);268		break;269 270	case TB_PROPERTY_TYPE_DATA:271		kfree(property->value.data);272		break;273 274	case TB_PROPERTY_TYPE_TEXT:275		kfree(property->value.text);276		break;277 278	default:279		break;280	}281 282	kfree(property);283}284 285/**286 * tb_property_free_dir() - Release memory allocated for property directory287 * @dir: Directory to release288 *289 * This will release all the memory the directory occupies including all290 * descendants. It is OK to pass %NULL @dir, then the function does291 * nothing.292 */293void tb_property_free_dir(struct tb_property_dir *dir)294{295	struct tb_property *property, *tmp;296 297	if (!dir)298		return;299 300	list_for_each_entry_safe(property, tmp, &dir->properties, list) {301		list_del(&property->list);302		tb_property_free(property);303	}304	kfree(dir->uuid);305	kfree(dir);306}307EXPORT_SYMBOL_GPL(tb_property_free_dir);308 309static size_t tb_property_dir_length(const struct tb_property_dir *dir,310				     bool recurse, size_t *data_len)311{312	const struct tb_property *property;313	size_t len = 0;314 315	if (dir->uuid)316		len += sizeof(*dir->uuid) / 4;317	else318		len += sizeof(struct tb_property_rootdir_entry) / 4;319 320	list_for_each_entry(property, &dir->properties, list) {321		len += sizeof(struct tb_property_entry) / 4;322 323		switch (property->type) {324		case TB_PROPERTY_TYPE_DIRECTORY:325			if (recurse) {326				len += tb_property_dir_length(327					property->value.dir, recurse, data_len);328			}329			/* Reserve dword padding after each directory */330			if (data_len)331				*data_len += 1;332			break;333 334		case TB_PROPERTY_TYPE_DATA:335		case TB_PROPERTY_TYPE_TEXT:336			if (data_len)337				*data_len += property->length;338			break;339 340		default:341			break;342		}343	}344 345	return len;346}347 348static ssize_t __tb_property_format_dir(const struct tb_property_dir *dir,349	u32 *block, unsigned int start_offset, size_t block_len)350{351	unsigned int data_offset, dir_end;352	const struct tb_property *property;353	struct tb_property_entry *entry;354	size_t dir_len, data_len = 0;355	int ret;356 357	/*358	 * The structure of property block looks like following. Leaf359	 * data/text is included right after the directory and each360	 * directory follows each other (even nested ones).361	 *362	 * +----------+ <-- start_offset363	 * |  header  | <-- root directory header364	 * +----------+ ---365	 * |  entry 0 | -^--------------------.366	 * +----------+  |                    |367	 * |  entry 1 | -|--------------------|--.368	 * +----------+  |                    |  |369	 * |  entry 2 | -|-----------------.  |  |370	 * +----------+  |                 |  |  |371	 * :          :  |  dir_len        |  |  |372	 * .          .  |                 |  |  |373	 * :          :  |                 |  |  |374	 * +----------+  |                 |  |  |375	 * |  entry n |  v                 |  |  |376	 * +----------+ <-- data_offset    |  |  |377	 * |  data 0  | <------------------|--'  |378	 * +----------+                    |     |379	 * |  data 1  | <------------------|-----'380	 * +----------+                    |381	 * | 00000000 | padding            |382	 * +----------+ <-- dir_end <------'383	 * |   UUID   | <-- directory UUID (child directory)384	 * +----------+385	 * |  entry 0 |386	 * +----------+387	 * |  entry 1 |388	 * +----------+389	 * :          :390	 * .          .391	 * :          :392	 * +----------+393	 * |  entry n |394	 * +----------+395	 * |  data 0  |396	 * +----------+397	 *398	 * We use dir_end to hold pointer to the end of the directory. It399	 * will increase as we add directories and each directory should be400	 * added starting from previous dir_end.401	 */402	dir_len = tb_property_dir_length(dir, false, &data_len);403	data_offset = start_offset + dir_len;404	dir_end = start_offset + data_len + dir_len;405 406	if (data_offset > dir_end)407		return -EINVAL;408	if (dir_end > block_len)409		return -EINVAL;410 411	/* Write headers first */412	if (dir->uuid) {413		struct tb_property_dir_entry *pe;414 415		pe = (struct tb_property_dir_entry *)&block[start_offset];416		memcpy(pe->uuid, dir->uuid, sizeof(pe->uuid));417		entry = pe->entries;418	} else {419		struct tb_property_rootdir_entry *re;420 421		re = (struct tb_property_rootdir_entry *)&block[start_offset];422		re->magic = TB_PROPERTY_ROOTDIR_MAGIC;423		re->length = dir_len - sizeof(*re) / 4;424		entry = re->entries;425	}426 427	list_for_each_entry(property, &dir->properties, list) {428		const struct tb_property_dir *child;429 430		format_dwdata(entry, property->key, 2);431		entry->type = property->type;432 433		switch (property->type) {434		case TB_PROPERTY_TYPE_DIRECTORY:435			child = property->value.dir;436			ret = __tb_property_format_dir(child, block, dir_end,437						       block_len);438			if (ret < 0)439				return ret;440			entry->length = tb_property_dir_length(child, false,441							       NULL);442			entry->value = dir_end;443			dir_end = ret;444			break;445 446		case TB_PROPERTY_TYPE_DATA:447			format_dwdata(&block[data_offset], property->value.data,448				      property->length);449			entry->length = property->length;450			entry->value = data_offset;451			data_offset += entry->length;452			break;453 454		case TB_PROPERTY_TYPE_TEXT:455			format_dwdata(&block[data_offset], property->value.text,456				      property->length);457			entry->length = property->length;458			entry->value = data_offset;459			data_offset += entry->length;460			break;461 462		case TB_PROPERTY_TYPE_VALUE:463			entry->length = property->length;464			entry->value = property->value.immediate;465			break;466 467		default:468			break;469		}470 471		entry++;472	}473 474	return dir_end;475}476 477/**478 * tb_property_format_dir() - Formats directory to the packed XDomain format479 * @dir: Directory to format480 * @block: Property block where the packed data is placed481 * @block_len: Length of the property block482 *483 * This function formats the directory to the packed format that can be484 * then send over the thunderbolt fabric to receiving host. Returns %0 in485 * case of success and negative errno on faulure. Passing %NULL in @block486 * returns number of entries the block takes.487 */488ssize_t tb_property_format_dir(const struct tb_property_dir *dir, u32 *block,489			       size_t block_len)490{491	ssize_t ret;492 493	if (!block) {494		size_t dir_len, data_len = 0;495 496		dir_len = tb_property_dir_length(dir, true, &data_len);497		return dir_len + data_len;498	}499 500	ret = __tb_property_format_dir(dir, block, 0, block_len);501	return ret < 0 ? ret : 0;502}503 504/**505 * tb_property_copy_dir() - Take a deep copy of directory506 * @dir: Directory to copy507 *508 * This function takes a deep copy of @dir and returns back the copy. In509 * case of error returns %NULL. The resulting directory needs to be510 * released by calling tb_property_free_dir().511 */512struct tb_property_dir *tb_property_copy_dir(const struct tb_property_dir *dir)513{514	struct tb_property *property, *p = NULL;515	struct tb_property_dir *d;516 517	if (!dir)518		return NULL;519 520	d = tb_property_create_dir(dir->uuid);521	if (!d)522		return NULL;523 524	list_for_each_entry(property, &dir->properties, list) {525		struct tb_property *p;526 527		p = tb_property_alloc(property->key, property->type);528		if (!p)529			goto err_free;530 531		p->length = property->length;532 533		switch (property->type) {534		case TB_PROPERTY_TYPE_DIRECTORY:535			p->value.dir = tb_property_copy_dir(property->value.dir);536			if (!p->value.dir)537				goto err_free;538			break;539 540		case TB_PROPERTY_TYPE_DATA:541			p->value.data = kmemdup(property->value.data,542						property->length * 4,543						GFP_KERNEL);544			if (!p->value.data)545				goto err_free;546			break;547 548		case TB_PROPERTY_TYPE_TEXT:549			p->value.text = kzalloc(p->length * 4, GFP_KERNEL);550			if (!p->value.text)551				goto err_free;552			strcpy(p->value.text, property->value.text);553			break;554 555		case TB_PROPERTY_TYPE_VALUE:556			p->value.immediate = property->value.immediate;557			break;558 559		default:560			break;561		}562 563		list_add_tail(&p->list, &d->properties);564	}565 566	return d;567 568err_free:569	kfree(p);570	tb_property_free_dir(d);571 572	return NULL;573}574 575/**576 * tb_property_add_immediate() - Add immediate property to directory577 * @parent: Directory to add the property578 * @key: Key for the property579 * @value: Immediate value to store with the property580 */581int tb_property_add_immediate(struct tb_property_dir *parent, const char *key,582			      u32 value)583{584	struct tb_property *property;585 586	if (!tb_property_key_valid(key))587		return -EINVAL;588 589	property = tb_property_alloc(key, TB_PROPERTY_TYPE_VALUE);590	if (!property)591		return -ENOMEM;592 593	property->length = 1;594	property->value.immediate = value;595 596	list_add_tail(&property->list, &parent->properties);597	return 0;598}599EXPORT_SYMBOL_GPL(tb_property_add_immediate);600 601/**602 * tb_property_add_data() - Adds arbitrary data property to directory603 * @parent: Directory to add the property604 * @key: Key for the property605 * @buf: Data buffer to add606 * @buflen: Number of bytes in the data buffer607 *608 * Function takes a copy of @buf and adds it to the directory.609 */610int tb_property_add_data(struct tb_property_dir *parent, const char *key,611			 const void *buf, size_t buflen)612{613	/* Need to pad to dword boundary */614	size_t size = round_up(buflen, 4);615	struct tb_property *property;616 617	if (!tb_property_key_valid(key))618		return -EINVAL;619 620	property = tb_property_alloc(key, TB_PROPERTY_TYPE_DATA);621	if (!property)622		return -ENOMEM;623 624	property->length = size / 4;625	property->value.data = kzalloc(size, GFP_KERNEL);626	if (!property->value.data) {627		kfree(property);628		return -ENOMEM;629	}630 631	memcpy(property->value.data, buf, buflen);632 633	list_add_tail(&property->list, &parent->properties);634	return 0;635}636EXPORT_SYMBOL_GPL(tb_property_add_data);637 638/**639 * tb_property_add_text() - Adds string property to directory640 * @parent: Directory to add the property641 * @key: Key for the property642 * @text: String to add643 *644 * Function takes a copy of @text and adds it to the directory.645 */646int tb_property_add_text(struct tb_property_dir *parent, const char *key,647			 const char *text)648{649	/* Need to pad to dword boundary */650	size_t size = round_up(strlen(text) + 1, 4);651	struct tb_property *property;652 653	if (!tb_property_key_valid(key))654		return -EINVAL;655 656	property = tb_property_alloc(key, TB_PROPERTY_TYPE_TEXT);657	if (!property)658		return -ENOMEM;659 660	property->length = size / 4;661	property->value.text = kzalloc(size, GFP_KERNEL);662	if (!property->value.text) {663		kfree(property);664		return -ENOMEM;665	}666 667	strcpy(property->value.text, text);668 669	list_add_tail(&property->list, &parent->properties);670	return 0;671}672EXPORT_SYMBOL_GPL(tb_property_add_text);673 674/**675 * tb_property_add_dir() - Adds a directory to the parent directory676 * @parent: Directory to add the property677 * @key: Key for the property678 * @dir: Directory to add679 */680int tb_property_add_dir(struct tb_property_dir *parent, const char *key,681			struct tb_property_dir *dir)682{683	struct tb_property *property;684 685	if (!tb_property_key_valid(key))686		return -EINVAL;687 688	property = tb_property_alloc(key, TB_PROPERTY_TYPE_DIRECTORY);689	if (!property)690		return -ENOMEM;691 692	property->value.dir = dir;693 694	list_add_tail(&property->list, &parent->properties);695	return 0;696}697EXPORT_SYMBOL_GPL(tb_property_add_dir);698 699/**700 * tb_property_remove() - Removes property from a parent directory701 * @property: Property to remove702 *703 * Note memory for @property is released as well so it is not allowed to704 * touch the object after call to this function.705 */706void tb_property_remove(struct tb_property *property)707{708	list_del(&property->list);709	kfree(property);710}711EXPORT_SYMBOL_GPL(tb_property_remove);712 713/**714 * tb_property_find() - Find a property from a directory715 * @dir: Directory where the property is searched716 * @key: Key to look for717 * @type: Type of the property718 *719 * Finds and returns property from the given directory. Does not recurse720 * into sub-directories. Returns %NULL if the property was not found.721 */722struct tb_property *tb_property_find(struct tb_property_dir *dir,723	const char *key, enum tb_property_type type)724{725	struct tb_property *property;726 727	list_for_each_entry(property, &dir->properties, list) {728		if (property->type == type && !strcmp(property->key, key))729			return property;730	}731 732	return NULL;733}734EXPORT_SYMBOL_GPL(tb_property_find);735 736/**737 * tb_property_get_next() - Get next property from directory738 * @dir: Directory holding properties739 * @prev: Previous property in the directory (%NULL returns the first)740 */741struct tb_property *tb_property_get_next(struct tb_property_dir *dir,742					 struct tb_property *prev)743{744	if (prev) {745		if (list_is_last(&prev->list, &dir->properties))746			return NULL;747		return list_next_entry(prev, list);748	}749	return list_first_entry_or_null(&dir->properties, struct tb_property,750					list);751}752EXPORT_SYMBOL_GPL(tb_property_get_next);753