980 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			980 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // 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);
 | |
| 	if (rval < 0)
 | |
| 		return rval;
 | |
| 
 | |
| 	block_id = ccs_data_parse_block_id(block, is_first);
 | |
| 
 | |
| 	if (verbose)
 | |
| 		dev_dbg(dev,
 | |
| 			"Block ID 0x%2.2x, header length %zu, payload length %zu\n",
 | |
| 			block_id, hlen, plen);
 | |
| 
 | |
| 	if (!has_headroom(&block->length, hlen + plen, endp))
 | |
| 		return -ENODATA;
 | |
| 
 | |
| 	if (__block_id)
 | |
| 		*__block_id = block_id;
 | |
| 
 | |
| 	if (payload)
 | |
| 		*payload = (void *)&block->length + hlen;
 | |
| 
 | |
| 	if (next_block)
 | |
| 		*next_block = (void *)&block->length + hlen + plen;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int ccs_data_parse_regs(struct bin_container *bin,
 | |
| 			       struct ccs_reg **__regs,
 | |
| 			       size_t *__num_regs, const void *payload,
 | |
| 			       const void *endp, struct device *dev)
 | |
| {
 | |
| 	struct ccs_reg *regs_base = NULL, *regs = NULL;
 | |
| 	size_t num_regs = 0;
 | |
| 	u16 addr = 0;
 | |
| 
 | |
| 	if (bin->base && __regs) {
 | |
| 		regs = regs_base = bin_alloc(bin, sizeof(*regs) * *__num_regs);
 | |
| 		if (!regs)
 | |
| 			return -ENOMEM;
 | |
| 	}
 | |
| 
 | |
| 	while (payload < endp && num_regs < INT_MAX) {
 | |
| 		const struct __ccs_data_block_regs *r = payload;
 | |
| 		size_t len;
 | |
| 		const void *data;
 | |
| 
 | |
| 		if (!is_contained(r, endp))
 | |
| 			return -ENODATA;
 | |
| 
 | |
| 		switch (r->reg_len >> CCS_DATA_BLOCK_REGS_SEL_SHIFT) {
 | |
| 		case CCS_DATA_BLOCK_REGS_SEL_REGS:
 | |
| 			addr += r->reg_len & CCS_DATA_BLOCK_REGS_ADDR_MASK;
 | |
| 			len = ((r->reg_len & CCS_DATA_BLOCK_REGS_LEN_MASK)
 | |
| 			       >> CCS_DATA_BLOCK_REGS_LEN_SHIFT) + 1;
 | |
| 
 | |
| 			if (!is_contained_with_headroom(r, len, endp))
 | |
| 				return -ENODATA;
 | |
| 
 | |
| 			data = r + 1;
 | |
| 			break;
 | |
| 		case CCS_DATA_BLOCK_REGS_SEL_REGS2: {
 | |
| 			const struct __ccs_data_block_regs2 *r2 = payload;
 | |
| 
 | |
| 			if (!is_contained(r2, endp))
 | |
| 				return -ENODATA;
 | |
| 
 | |
| 			addr += ((u16)(r2->reg_len &
 | |
| 				       CCS_DATA_BLOCK_REGS_2_ADDR_MASK) << 8)
 | |
| 				+ r2->addr;
 | |
| 			len = ((r2->reg_len & CCS_DATA_BLOCK_REGS_2_LEN_MASK)
 | |
| 			       >> CCS_DATA_BLOCK_REGS_2_LEN_SHIFT) + 1;
 | |
| 
 | |
| 			if (!is_contained_with_headroom(r2, len, endp))
 | |
| 				return -ENODATA;
 | |
| 
 | |
| 			data = r2 + 1;
 | |
| 			break;
 | |
| 		}
 | |
| 		case CCS_DATA_BLOCK_REGS_SEL_REGS3: {
 | |
| 			const struct __ccs_data_block_regs3 *r3 = payload;
 | |
| 
 | |
| 			if (!is_contained(r3, endp))
 | |
| 				return -ENODATA;
 | |
| 
 | |
| 			addr = ((u16)r3->addr[0] << 8) + r3->addr[1];
 | |
| 			len = (r3->reg_len & CCS_DATA_BLOCK_REGS_3_LEN_MASK) + 1;
 | |
| 
 | |
| 			if (!is_contained_with_headroom(r3, len, endp))
 | |
| 				return -ENODATA;
 | |
| 
 | |
| 			data = r3 + 1;
 | |
| 			break;
 | |
| 		}
 | |
| 		default:
 | |
| 			return -EINVAL;
 | |
| 		}
 | |
| 
 | |
| 		num_regs++;
 | |
| 
 | |
| 		if (!bin->base) {
 | |
| 			bin_reserve(bin, len);
 | |
| 		} else if (__regs) {
 | |
| 			if (!regs)
 | |
| 				return -EIO;
 | |
| 
 | |
| 			regs->addr = addr;
 | |
| 			regs->len = len;
 | |
| 			regs->value = bin_alloc(bin, len);
 | |
| 			if (!regs->value)
 | |
| 				return -ENOMEM;
 | |
| 
 | |
| 			memcpy(regs->value, data, len);
 | |
| 			regs++;
 | |
| 		}
 | |
| 
 | |
| 		addr += len;
 | |
| 		payload = data + len;
 | |
| 	}
 | |
| 
 | |
| 	if (!bin->base)
 | |
| 		bin_reserve(bin, sizeof(*regs) * num_regs);
 | |
| 
 | |
| 	if (__num_regs)
 | |
| 		*__num_regs = num_regs;
 | |
| 
 | |
| 	if (bin->base && __regs) {
 | |
| 		if (!regs_base)
 | |
| 			return -EIO;
 | |
| 
 | |
| 		*__regs = regs_base;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int ccs_data_parse_reg_rules(struct bin_container *bin,
 | |
| 				    struct ccs_reg **__regs,
 | |
| 				    size_t *__num_regs,
 | |
| 				    const void *payload,
 | |
| 				    const void *endp, struct device *dev)
 | |
| {
 | |
| 	int rval;
 | |
| 
 | |
| 	if (!bin->base)
 | |
| 		return ccs_data_parse_regs(bin, NULL, NULL, payload, endp, dev);
 | |
| 
 | |
| 	rval = ccs_data_parse_regs(bin, NULL, __num_regs, payload, endp, dev);
 | |
| 	if (rval)
 | |
| 		return rval;
 | |
| 
 | |
| 	return ccs_data_parse_regs(bin, __regs, __num_regs, payload, endp,
 | |
| 				   dev);
 | |
| }
 | |
| 
 | |
| static void assign_ffd_entry(struct ccs_frame_format_desc *desc,
 | |
| 			     const struct __ccs_data_block_ffd_entry *ent)
 | |
| {
 | |
| 	desc->pixelcode = ent->pixelcode;
 | |
| 	desc->value = ((u16)ent->value[0] << 8) + ent->value[1];
 | |
| }
 | |
| 
 | |
| static int ccs_data_parse_ffd(struct bin_container *bin,
 | |
| 			      struct ccs_frame_format_descs **ffd,
 | |
| 			      const void *payload,
 | |
| 			      const void *endp, struct device *dev)
 | |
| {
 | |
| 	const struct __ccs_data_block_ffd *__ffd = payload;
 | |
| 	const struct __ccs_data_block_ffd_entry *__entry;
 | |
| 	unsigned int i;
 | |
| 
 | |
| 	if (!is_contained(__ffd, endp))
 | |
| 		return -ENODATA;
 | |
| 
 | |
| 	if ((void *)__ffd + sizeof(*__ffd) +
 | |
| 	    ((u32)__ffd->num_column_descs +
 | |
| 	     (u32)__ffd->num_row_descs) *
 | |
| 	    sizeof(struct __ccs_data_block_ffd_entry) != endp)
 | |
| 		return -ENODATA;
 | |
| 
 | |
| 	if (!bin->base) {
 | |
| 		bin_reserve(bin, sizeof(**ffd));
 | |
| 		bin_reserve(bin, __ffd->num_column_descs *
 | |
| 			    sizeof(struct ccs_frame_format_desc));
 | |
| 		bin_reserve(bin, __ffd->num_row_descs *
 | |
| 			    sizeof(struct ccs_frame_format_desc));
 | |
| 
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	*ffd = bin_alloc(bin, sizeof(**ffd));
 | |
| 	if (!*ffd)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	(*ffd)->num_column_descs = __ffd->num_column_descs;
 | |
| 	(*ffd)->num_row_descs = __ffd->num_row_descs;
 | |
| 	__entry = (void *)(__ffd + 1);
 | |
| 
 | |
| 	(*ffd)->column_descs = bin_alloc(bin, __ffd->num_column_descs *
 | |
| 					 sizeof(*(*ffd)->column_descs));
 | |
| 	if (!(*ffd)->column_descs)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	for (i = 0; i < __ffd->num_column_descs; i++, __entry++)
 | |
| 		assign_ffd_entry(&(*ffd)->column_descs[i], __entry);
 | |
| 
 | |
| 	(*ffd)->row_descs = bin_alloc(bin, __ffd->num_row_descs *
 | |
| 				      sizeof(*(*ffd)->row_descs));
 | |
| 	if (!(*ffd)->row_descs)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	for (i = 0; i < __ffd->num_row_descs; i++, __entry++)
 | |
| 		assign_ffd_entry(&(*ffd)->row_descs[i], __entry);
 | |
| 
 | |
| 	if (__entry != endp)
 | |
| 		return -EPROTO;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int ccs_data_parse_pdaf_readout(struct bin_container *bin,
 | |
| 				       struct ccs_pdaf_readout **pdaf_readout,
 | |
| 				       const void *payload,
 | |
| 				       const void *endp, struct device *dev)
 | |
| {
 | |
| 	const struct __ccs_data_block_pdaf_readout *__pdaf = payload;
 | |
| 
 | |
| 	if (!is_contained(__pdaf, endp))
 | |
| 		return -ENODATA;
 | |
| 
 | |
| 	if (!bin->base) {
 | |
| 		bin_reserve(bin, sizeof(**pdaf_readout));
 | |
| 	} else {
 | |
| 		*pdaf_readout = bin_alloc(bin, sizeof(**pdaf_readout));
 | |
| 		if (!*pdaf_readout)
 | |
| 			return -ENOMEM;
 | |
| 
 | |
| 		(*pdaf_readout)->pdaf_readout_info_order =
 | |
| 			__pdaf->pdaf_readout_info_order;
 | |
| 	}
 | |
| 
 | |
| 	return ccs_data_parse_ffd(bin, !bin->base ? NULL : &(*pdaf_readout)->ffd,
 | |
| 				  __pdaf + 1, endp, dev);
 | |
| }
 | |
| 
 | |
| static int ccs_data_parse_rules(struct bin_container *bin,
 | |
| 				struct ccs_rule **__rules,
 | |
| 				size_t *__num_rules, const void *payload,
 | |
| 				const void *endp, struct device *dev)
 | |
| {
 | |
| 	struct ccs_rule *rules_base = NULL, *rules = NULL, *next_rule = NULL;
 | |
| 	size_t num_rules = 0;
 | |
| 	const void *__next_rule = payload;
 | |
| 	int rval;
 | |
| 
 | |
| 	if (bin->base) {
 | |
| 		rules_base = next_rule =
 | |
| 			bin_alloc(bin, sizeof(*rules) * *__num_rules);
 | |
| 		if (!rules_base)
 | |
| 			return -ENOMEM;
 | |
| 	}
 | |
| 
 | |
| 	while (__next_rule < endp) {
 | |
| 		size_t rule_hlen, rule_plen, rule_plen2;
 | |
| 		const u8 *__rule_type;
 | |
| 		const void *rule_payload;
 | |
| 
 | |
| 		/* Size of a single rule */
 | |
| 		rval = ccs_data_parse_length_specifier(__next_rule, &rule_hlen,
 | |
| 						       &rule_plen, endp);
 | |
| 
 | |
| 		if (rval < 0)
 | |
| 			return rval;
 | |
| 
 | |
| 		__rule_type = __next_rule + rule_hlen;
 | |
| 
 | |
| 		if (!is_contained(__rule_type, endp))
 | |
| 			return -ENODATA;
 | |
| 
 | |
| 		rule_payload = __rule_type + 1;
 | |
| 		rule_plen2 = rule_plen - sizeof(*__rule_type);
 | |
| 
 | |
| 		if (*__rule_type == CCS_DATA_BLOCK_RULE_ID_IF) {
 | |
| 			const struct __ccs_data_block_rule_if *__if_rules =
 | |
| 				rule_payload;
 | |
| 			const size_t __num_if_rules =
 | |
| 				rule_plen2 / sizeof(*__if_rules);
 | |
| 			struct ccs_if_rule *if_rule;
 | |
| 
 | |
| 			if (!has_headroom(__if_rules,
 | |
| 					  sizeof(*__if_rules) * __num_if_rules,
 | |
| 					  rule_payload + rule_plen2))
 | |
| 				return -ENODATA;
 | |
| 
 | |
| 			/* Also check there is no extra data */
 | |
| 			if (__if_rules + __num_if_rules !=
 | |
| 			    rule_payload + rule_plen2)
 | |
| 				return -EINVAL;
 | |
| 
 | |
| 			if (!bin->base) {
 | |
| 				bin_reserve(bin,
 | |
| 					    sizeof(*if_rule) *
 | |
| 					    __num_if_rules);
 | |
| 				num_rules++;
 | |
| 			} else {
 | |
| 				unsigned int i;
 | |
| 
 | |
| 				if (!next_rule)
 | |
| 					return -EIO;
 | |
| 
 | |
| 				rules = next_rule;
 | |
| 				next_rule++;
 | |
| 
 | |
| 				if_rule = bin_alloc(bin,
 | |
| 						    sizeof(*if_rule) *
 | |
| 						    __num_if_rules);
 | |
| 				if (!if_rule)
 | |
| 					return -ENOMEM;
 | |
| 
 | |
| 				for (i = 0; i < __num_if_rules; i++) {
 | |
| 					if_rule[i].addr =
 | |
| 						((u16)__if_rules[i].addr[0]
 | |
| 						 << 8) +
 | |
| 						__if_rules[i].addr[1];
 | |
| 					if_rule[i].value = __if_rules[i].value;
 | |
| 					if_rule[i].mask = __if_rules[i].mask;
 | |
| 				}
 | |
| 
 | |
| 				rules->if_rules = if_rule;
 | |
| 				rules->num_if_rules = __num_if_rules;
 | |
| 			}
 | |
| 		} else {
 | |
| 			/* Check there was an if rule before any other rules */
 | |
| 			if (bin->base && !rules)
 | |
| 				return -EINVAL;
 | |
| 
 | |
| 			switch (*__rule_type) {
 | |
| 			case CCS_DATA_BLOCK_RULE_ID_READ_ONLY_REGS:
 | |
| 				rval = ccs_data_parse_reg_rules(bin,
 | |
| 								rules ?
 | |
| 								&rules->read_only_regs : NULL,
 | |
| 								rules ?
 | |
| 								&rules->num_read_only_regs : NULL,
 | |
| 								rule_payload,
 | |
| 								rule_payload + rule_plen2,
 | |
| 								dev);
 | |
| 				if (rval)
 | |
| 					return rval;
 | |
| 				break;
 | |
| 			case CCS_DATA_BLOCK_RULE_ID_FFD:
 | |
| 				rval = ccs_data_parse_ffd(bin, rules ?
 | |
| 							  &rules->frame_format : NULL,
 | |
| 							  rule_payload,
 | |
| 							  rule_payload + rule_plen2,
 | |
| 							  dev);
 | |
| 				if (rval)
 | |
| 					return rval;
 | |
| 				break;
 | |
| 			case CCS_DATA_BLOCK_RULE_ID_MSR:
 | |
| 				rval = ccs_data_parse_reg_rules(bin,
 | |
| 								rules ?
 | |
| 								&rules->manufacturer_regs : NULL,
 | |
| 								rules ?
 | |
| 								&rules->num_manufacturer_regs : NULL,
 | |
| 								rule_payload,
 | |
| 								rule_payload + rule_plen2,
 | |
| 								dev);
 | |
| 				if (rval)
 | |
| 					return rval;
 | |
| 				break;
 | |
| 			case CCS_DATA_BLOCK_RULE_ID_PDAF_READOUT:
 | |
| 				rval = ccs_data_parse_pdaf_readout(bin,
 | |
| 								   rules ?
 | |
| 								   &rules->pdaf_readout : NULL,
 | |
| 								   rule_payload,
 | |
| 								   rule_payload + rule_plen2,
 | |
| 								   dev);
 | |
| 				if (rval)
 | |
| 					return rval;
 | |
| 				break;
 | |
| 			default:
 | |
| 				dev_dbg(dev,
 | |
| 					"Don't know how to handle rule type %u!\n",
 | |
| 					*__rule_type);
 | |
| 				return -EINVAL;
 | |
| 			}
 | |
| 		}
 | |
| 		__next_rule = __next_rule + rule_hlen + rule_plen;
 | |
| 	}
 | |
| 
 | |
| 	if (!bin->base) {
 | |
| 		bin_reserve(bin, sizeof(*rules) * num_rules);
 | |
| 		*__num_rules = num_rules;
 | |
| 	} else {
 | |
| 		if (!rules_base)
 | |
| 			return -EIO;
 | |
| 
 | |
| 		*__rules = rules_base;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int ccs_data_parse_pdaf(struct bin_container *bin, struct ccs_pdaf_pix_loc **pdaf,
 | |
| 			       const void *payload, const void *endp,
 | |
| 			       struct device *dev)
 | |
| {
 | |
| 	const struct __ccs_data_block_pdaf_pix_loc *__pdaf = payload;
 | |
| 	const struct __ccs_data_block_pdaf_pix_loc_block_desc_group *__bdesc_group;
 | |
| 	const struct __ccs_data_block_pdaf_pix_loc_pixel_desc *__pixel_desc;
 | |
| 	unsigned int i;
 | |
| 	u16 num_block_desc_groups;
 | |
| 	u8 max_block_type_id = 0;
 | |
| 	const u8 *__num_pixel_descs;
 | |
| 
 | |
| 	if (!is_contained(__pdaf, endp))
 | |
| 		return -ENODATA;
 | |
| 
 | |
| 	if (bin->base) {
 | |
| 		*pdaf = bin_alloc(bin, sizeof(**pdaf));
 | |
| 		if (!*pdaf)
 | |
| 			return -ENOMEM;
 | |
| 	} else {
 | |
| 		bin_reserve(bin, sizeof(**pdaf));
 | |
| 	}
 | |
| 
 | |
| 	num_block_desc_groups =
 | |
| 		((u16)__pdaf->num_block_desc_groups[0] << 8) +
 | |
| 		__pdaf->num_block_desc_groups[1];
 | |
| 
 | |
| 	if (bin->base) {
 | |
| 		(*pdaf)->main_offset_x =
 | |
| 			((u16)__pdaf->main_offset_x[0] << 8) +
 | |
| 			__pdaf->main_offset_x[1];
 | |
| 		(*pdaf)->main_offset_y =
 | |
| 			((u16)__pdaf->main_offset_y[0] << 8) +
 | |
| 			__pdaf->main_offset_y[1];
 | |
| 		(*pdaf)->global_pdaf_type = __pdaf->global_pdaf_type;
 | |
| 		(*pdaf)->block_width = __pdaf->block_width;
 | |
| 		(*pdaf)->block_height = __pdaf->block_height;
 | |
| 		(*pdaf)->num_block_desc_groups = num_block_desc_groups;
 | |
| 	}
 | |
| 
 | |
| 	__bdesc_group = (const void *)(__pdaf + 1);
 | |
| 
 | |
| 	if (bin->base) {
 | |
| 		(*pdaf)->block_desc_groups =
 | |
| 			bin_alloc(bin,
 | |
| 				  sizeof(struct ccs_pdaf_pix_loc_block_desc_group) *
 | |
| 				  num_block_desc_groups);
 | |
| 		if (!(*pdaf)->block_desc_groups)
 | |
| 			return -ENOMEM;
 | |
| 	} else {
 | |
| 		bin_reserve(bin, sizeof(struct ccs_pdaf_pix_loc_block_desc_group) *
 | |
| 			    num_block_desc_groups);
 | |
| 	}
 | |
| 
 | |
| 	for (i = 0; i < num_block_desc_groups; i++) {
 | |
| 		const struct __ccs_data_block_pdaf_pix_loc_block_desc *__bdesc;
 | |
| 		u16 num_block_descs;
 | |
| 		unsigned int j;
 | |
| 
 | |
| 		if (!is_contained(__bdesc_group, endp))
 | |
| 			return -ENODATA;
 | |
| 
 | |
| 		num_block_descs =
 | |
| 			((u16)__bdesc_group->num_block_descs[0] << 8) +
 | |
| 			__bdesc_group->num_block_descs[1];
 | |
| 
 | |
| 		if (bin->base) {
 | |
| 			(*pdaf)->block_desc_groups[i].repeat_y =
 | |
| 				__bdesc_group->repeat_y;
 | |
| 			(*pdaf)->block_desc_groups[i].num_block_descs =
 | |
| 				num_block_descs;
 | |
| 		}
 | |
| 
 | |
| 		__bdesc = (const void *)(__bdesc_group + 1);
 | |
| 
 | |
| 		if (bin->base) {
 | |
| 			(*pdaf)->block_desc_groups[i].block_descs =
 | |
| 				bin_alloc(bin,
 | |
| 					  sizeof(struct ccs_pdaf_pix_loc_block_desc) *
 | |
| 					  num_block_descs);
 | |
| 			if (!(*pdaf)->block_desc_groups[i].block_descs)
 | |
| 				return -ENOMEM;
 | |
| 		} else {
 | |
| 			bin_reserve(bin, sizeof(struct ccs_pdaf_pix_loc_block_desc) *
 | |
| 				    num_block_descs);
 | |
| 		}
 | |
| 
 | |
| 		for (j = 0; j < num_block_descs; j++, __bdesc++) {
 | |
| 			struct ccs_pdaf_pix_loc_block_desc *bdesc;
 | |
| 
 | |
| 			if (!is_contained(__bdesc, endp))
 | |
| 				return -ENODATA;
 | |
| 
 | |
| 			if (max_block_type_id <= __bdesc->block_type_id)
 | |
| 				max_block_type_id = __bdesc->block_type_id + 1;
 | |
| 
 | |
| 			if (!bin->base)
 | |
| 				continue;
 | |
| 
 | |
| 			bdesc = &(*pdaf)->block_desc_groups[i].block_descs[j];
 | |
| 
 | |
| 			bdesc->repeat_x = ((u16)__bdesc->repeat_x[0] << 8)
 | |
| 				+ __bdesc->repeat_x[1];
 | |
| 
 | |
| 			if (__bdesc->block_type_id >= num_block_descs)
 | |
| 				return -EINVAL;
 | |
| 
 | |
| 			bdesc->block_type_id = __bdesc->block_type_id;
 | |
| 		}
 | |
| 
 | |
| 		__bdesc_group = (const void *)__bdesc;
 | |
| 	}
 | |
| 
 | |
| 	__num_pixel_descs = (const void *)__bdesc_group;
 | |
| 
 | |
| 	if (bin->base) {
 | |
| 		(*pdaf)->pixel_desc_groups =
 | |
| 			bin_alloc(bin,
 | |
| 				  sizeof(struct ccs_pdaf_pix_loc_pixel_desc_group) *
 | |
| 				  max_block_type_id);
 | |
| 		if (!(*pdaf)->pixel_desc_groups)
 | |
| 			return -ENOMEM;
 | |
| 		(*pdaf)->num_pixel_desc_grups = max_block_type_id;
 | |
| 	} else {
 | |
| 		bin_reserve(bin, sizeof(struct ccs_pdaf_pix_loc_pixel_desc_group) *
 | |
| 			    max_block_type_id);
 | |
| 	}
 | |
| 
 | |
| 	for (i = 0; i < max_block_type_id; i++) {
 | |
| 		struct ccs_pdaf_pix_loc_pixel_desc_group *pdgroup = NULL;
 | |
| 		unsigned int j;
 | |
| 
 | |
| 		if (!is_contained(__num_pixel_descs, endp))
 | |
| 			return -ENODATA;
 | |
| 
 | |
| 		if (bin->base) {
 | |
| 			pdgroup = &(*pdaf)->pixel_desc_groups[i];
 | |
| 			pdgroup->descs =
 | |
| 				bin_alloc(bin,
 | |
| 					  sizeof(struct ccs_pdaf_pix_loc_pixel_desc) *
 | |
| 					  *__num_pixel_descs);
 | |
| 			if (!pdgroup->descs)
 | |
| 				return -ENOMEM;
 | |
| 			pdgroup->num_descs = *__num_pixel_descs;
 | |
| 		} else {
 | |
| 			bin_reserve(bin, sizeof(struct ccs_pdaf_pix_loc_pixel_desc) *
 | |
| 				    *__num_pixel_descs);
 | |
| 		}
 | |
| 
 | |
| 		__pixel_desc = (const void *)(__num_pixel_descs + 1);
 | |
| 
 | |
| 		for (j = 0; j < *__num_pixel_descs; j++, __pixel_desc++) {
 | |
| 			struct ccs_pdaf_pix_loc_pixel_desc *pdesc;
 | |
| 
 | |
| 			if (!is_contained(__pixel_desc, endp))
 | |
| 				return -ENODATA;
 | |
| 
 | |
| 			if (!bin->base)
 | |
| 				continue;
 | |
| 
 | |
| 			if (!pdgroup)
 | |
| 				return -EIO;
 | |
| 
 | |
| 			pdesc = &pdgroup->descs[j];
 | |
| 			pdesc->pixel_type = __pixel_desc->pixel_type;
 | |
| 			pdesc->small_offset_x = __pixel_desc->small_offset_x;
 | |
| 			pdesc->small_offset_y = __pixel_desc->small_offset_y;
 | |
| 		}
 | |
| 
 | |
| 		__num_pixel_descs = (const void *)(__pixel_desc + 1);
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int ccs_data_parse_license(struct bin_container *bin,
 | |
| 				  char **__license,
 | |
| 				  size_t *__license_length,
 | |
| 				  const void *payload, const void *endp)
 | |
| {
 | |
| 	size_t size = endp - payload;
 | |
| 	char *license;
 | |
| 
 | |
| 	if (!bin->base) {
 | |
| 		bin_reserve(bin, size);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	license = bin_alloc(bin, size);
 | |
| 	if (!license)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	memcpy(license, payload, size);
 | |
| 
 | |
| 	*__license = license;
 | |
| 	*__license_length = size;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int ccs_data_parse_end(bool *end, const void *payload, const void *endp,
 | |
| 			      struct device *dev)
 | |
| {
 | |
| 	const struct __ccs_data_block_end *__end = payload;
 | |
| 
 | |
| 	if (__end + 1 != endp) {
 | |
| 		dev_dbg(dev, "Invalid end block length %u\n",
 | |
| 			(unsigned int)(endp - payload));
 | |
| 		return -ENODATA;
 | |
| 	}
 | |
| 
 | |
| 	*end = true;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int __ccs_data_parse(struct bin_container *bin,
 | |
| 			    struct ccs_data_container *ccsdata,
 | |
| 			    const void *data, size_t len, struct device *dev,
 | |
| 			    bool verbose)
 | |
| {
 | |
| 	const struct __ccs_data_block *block = data;
 | |
| 	const struct __ccs_data_block *endp = data + len;
 | |
| 	unsigned int version;
 | |
| 	bool is_first = true;
 | |
| 	int rval;
 | |
| 
 | |
| 	version = ccs_data_parse_format_version(block);
 | |
| 	if (version != CCS_STATIC_DATA_VERSION) {
 | |
| 		dev_dbg(dev, "Don't know how to handle version %u\n", version);
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	if (verbose)
 | |
| 		dev_dbg(dev, "Parsing CCS static data version %u\n", version);
 | |
| 
 | |
| 	if (!bin->base)
 | |
| 		*ccsdata = (struct ccs_data_container){ 0 };
 | |
| 
 | |
| 	while (block < endp) {
 | |
| 		const struct __ccs_data_block *next_block;
 | |
| 		unsigned int block_id;
 | |
| 		const void *payload;
 | |
| 
 | |
| 		rval = ccs_data_block_parse_header(block, is_first, &block_id,
 | |
| 						   &payload, &next_block, endp,
 | |
| 						   dev,
 | |
| 						   bin->base ? false : verbose);
 | |
| 
 | |
| 		if (rval < 0)
 | |
| 			return rval;
 | |
| 
 | |
| 		switch (block_id) {
 | |
| 		case CCS_DATA_BLOCK_ID_DUMMY:
 | |
| 			break;
 | |
| 		case CCS_DATA_BLOCK_ID_DATA_VERSION:
 | |
| 			rval = ccs_data_parse_version(bin, ccsdata, payload,
 | |
| 						      next_block);
 | |
| 			if (rval < 0)
 | |
| 				return rval;
 | |
| 			break;
 | |
| 		case CCS_DATA_BLOCK_ID_SENSOR_READ_ONLY_REGS:
 | |
| 			rval = ccs_data_parse_regs(
 | |
| 				bin, &ccsdata->sensor_read_only_regs,
 | |
| 				&ccsdata->num_sensor_read_only_regs, payload,
 | |
| 				next_block, dev);
 | |
| 			if (rval < 0)
 | |
| 				return rval;
 | |
| 			break;
 | |
| 		case CCS_DATA_BLOCK_ID_SENSOR_MANUFACTURER_REGS:
 | |
| 			rval = ccs_data_parse_regs(
 | |
| 				bin, &ccsdata->sensor_manufacturer_regs,
 | |
| 				&ccsdata->num_sensor_manufacturer_regs, payload,
 | |
| 				next_block, dev);
 | |
| 			if (rval < 0)
 | |
| 				return rval;
 | |
| 			break;
 | |
| 		case CCS_DATA_BLOCK_ID_MODULE_READ_ONLY_REGS:
 | |
| 			rval = ccs_data_parse_regs(
 | |
| 				bin, &ccsdata->module_read_only_regs,
 | |
| 				&ccsdata->num_module_read_only_regs, payload,
 | |
| 				next_block, dev);
 | |
| 			if (rval < 0)
 | |
| 				return rval;
 | |
| 			break;
 | |
| 		case CCS_DATA_BLOCK_ID_MODULE_MANUFACTURER_REGS:
 | |
| 			rval = ccs_data_parse_regs(
 | |
| 				bin, &ccsdata->module_manufacturer_regs,
 | |
| 				&ccsdata->num_module_manufacturer_regs, payload,
 | |
| 				next_block, dev);
 | |
| 			if (rval < 0)
 | |
| 				return rval;
 | |
| 			break;
 | |
| 		case CCS_DATA_BLOCK_ID_SENSOR_PDAF_PIXEL_LOCATION:
 | |
| 			rval = ccs_data_parse_pdaf(bin, &ccsdata->sensor_pdaf,
 | |
| 						   payload, next_block, dev);
 | |
| 			if (rval < 0)
 | |
| 				return rval;
 | |
| 			break;
 | |
| 		case CCS_DATA_BLOCK_ID_MODULE_PDAF_PIXEL_LOCATION:
 | |
| 			rval = ccs_data_parse_pdaf(bin, &ccsdata->module_pdaf,
 | |
| 						   payload, next_block, dev);
 | |
| 			if (rval < 0)
 | |
| 				return rval;
 | |
| 			break;
 | |
| 		case CCS_DATA_BLOCK_ID_SENSOR_RULE_BASED_BLOCK:
 | |
| 			rval = ccs_data_parse_rules(
 | |
| 				bin, &ccsdata->sensor_rules,
 | |
| 				&ccsdata->num_sensor_rules, payload, next_block,
 | |
| 				dev);
 | |
| 			if (rval < 0)
 | |
| 				return rval;
 | |
| 			break;
 | |
| 		case CCS_DATA_BLOCK_ID_MODULE_RULE_BASED_BLOCK:
 | |
| 			rval = ccs_data_parse_rules(
 | |
| 				bin, &ccsdata->module_rules,
 | |
| 				&ccsdata->num_module_rules, payload, next_block,
 | |
| 				dev);
 | |
| 			if (rval < 0)
 | |
| 				return rval;
 | |
| 			break;
 | |
| 		case CCS_DATA_BLOCK_ID_LICENSE:
 | |
| 			rval = ccs_data_parse_license(bin, &ccsdata->license,
 | |
| 						      &ccsdata->license_length,
 | |
| 						      payload, next_block);
 | |
| 			if (rval < 0)
 | |
| 				return rval;
 | |
| 			break;
 | |
| 		case CCS_DATA_BLOCK_ID_END:
 | |
| 			rval = ccs_data_parse_end(&ccsdata->end, payload,
 | |
| 						  next_block, dev);
 | |
| 			if (rval < 0)
 | |
| 				return rval;
 | |
| 			break;
 | |
| 		default:
 | |
| 			dev_dbg(dev, "WARNING: not handling block ID 0x%2.2x\n",
 | |
| 				block_id);
 | |
| 		}
 | |
| 
 | |
| 		block = next_block;
 | |
| 		is_first = false;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * ccs_data_parse - Parse a CCS static data file into a usable in-memory
 | |
|  *		    data structure
 | |
|  * @ccsdata:	CCS static data in-memory data structure
 | |
|  * @data:	CCS static data binary
 | |
|  * @len:	Length of @data
 | |
|  * @dev:	Device the data is related to (used for printing debug messages)
 | |
|  * @verbose:	Whether to be verbose or not
 | |
|  */
 | |
| int ccs_data_parse(struct ccs_data_container *ccsdata, const void *data,
 | |
| 		   size_t len, struct device *dev, bool verbose)
 | |
| {
 | |
| 	struct bin_container bin = { 0 };
 | |
| 	int rval;
 | |
| 
 | |
| 	rval = __ccs_data_parse(&bin, ccsdata, data, len, dev, verbose);
 | |
| 	if (rval)
 | |
| 		return rval;
 | |
| 
 | |
| 	rval = bin_backing_alloc(&bin);
 | |
| 	if (rval)
 | |
| 		return rval;
 | |
| 
 | |
| 	rval = __ccs_data_parse(&bin, ccsdata, data, len, dev, false);
 | |
| 	if (rval)
 | |
| 		goto out_free;
 | |
| 
 | |
| 	if (verbose && ccsdata->version)
 | |
| 		print_ccs_data_version(dev, ccsdata->version);
 | |
| 
 | |
| 	if (bin.now != bin.end) {
 | |
| 		rval = -EPROTO;
 | |
| 		dev_dbg(dev, "parsing mismatch; base %p; now %p; end %p\n",
 | |
| 			bin.base, bin.now, bin.end);
 | |
| 		goto out_free;
 | |
| 	}
 | |
| 
 | |
| 	ccsdata->backing = bin.base;
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| out_free:
 | |
| 	kvfree(bin.base);
 | |
| 
 | |
| 	return rval;
 | |
| }
 |