| // 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); | |
| switch (*__rule_type) { | |
| case 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; | |
| } | |
| break; | |
| } | |
| case CCS_DATA_BLOCK_RULE_ID_READ_ONLY_REGS: | |
| rval = ccs_data_parse_reg_rules(bin, &rules->read_only_regs, | |
| &rules->num_read_only_regs, | |
| 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->frame_format, | |
| 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->manufacturer_regs, | |
| &rules->num_manufacturer_regs, | |
| 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->pdaf_readout, | |
| 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; | |
| } |