| // 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; |
| } |