diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 08:09:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 08:09:22 +0000 |
commit | 6b4d032d4964caaa85be4ba8f3a7874afbf958cc (patch) | |
tree | a9846103274b39705a7e8be9fc28c006c18094b1 /carl9170fw/tools/lib/carlfw.c | |
parent | Initial commit. (diff) | |
download | firmware-nonfree-6b4d032d4964caaa85be4ba8f3a7874afbf958cc.tar.xz firmware-nonfree-6b4d032d4964caaa85be4ba8f3a7874afbf958cc.zip |
Adding upstream version 20230625.upstream/20230625
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'carl9170fw/tools/lib/carlfw.c')
-rw-r--r-- | carl9170fw/tools/lib/carlfw.c | 630 |
1 files changed, 630 insertions, 0 deletions
diff --git a/carl9170fw/tools/lib/carlfw.c b/carl9170fw/tools/lib/carlfw.c new file mode 100644 index 0000000..ce61afb --- /dev/null +++ b/carl9170fw/tools/lib/carlfw.c @@ -0,0 +1,630 @@ +/* + * Copyright 2010-2011 Christian Lamparter <chunkeey@googlemail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <error.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "carlfw.h" + +struct carlfw_file { + char *name; + size_t len; + char *data; +}; + +struct carlfw { + struct carlfw_file fw; + struct carlfw_file hdr; + + struct list_head desc_list; + unsigned int desc_list_entries, + desc_list_len; +}; + +#define carlfw_walk_descs(iter, fw) \ + list_for_each_entry(iter, &fw->desc_list, h.list) + +struct carlfw_list_entry_head { + struct list_head list; +}; + +struct carlfw_list_entry { + struct carlfw_list_entry_head h; + union { + struct carl9170fw_desc_head head; + uint32_t data[0]; + char text[0]; + }; +}; + +static inline struct carlfw_list_entry *carlfw_desc_to_entry(struct carl9170fw_desc_head *head) +{ + return container_of(head, struct carlfw_list_entry, head); +} + +static inline struct carl9170fw_desc_head *carlfw_entry_to_desc(struct carlfw_list_entry *entry) +{ + return &entry->head; +} + +static void carlfw_entry_unlink(struct carlfw *fw, + struct carlfw_list_entry *entry) +{ + fw->desc_list_entries--; + fw->desc_list_len -= le16_to_cpu(entry->head.length); + list_del(&entry->h.list); +} + +static void carlfw_entry_del(struct carlfw *fw, + struct carlfw_list_entry *entry) +{ + carlfw_entry_unlink(fw, entry); + free(entry); +} + +static struct carlfw_list_entry *carlfw_find_entry(struct carlfw *fw, + const uint8_t descid[4], + unsigned int len, + uint8_t compatible_revision) +{ + struct carlfw_list_entry *iter; + + carlfw_walk_descs(iter, fw) { + if (carl9170fw_desc_cmp(&iter->head, descid, len, + compatible_revision)) + return (void *)iter; + } + + return NULL; +} + +static struct carlfw_list_entry *__carlfw_entry_add_prepare(struct carlfw *fw, + const struct carl9170fw_desc_head *desc) +{ + struct carlfw_list_entry *tmp; + unsigned int len; + + len = le16_to_cpu(desc->length); + + if (len < sizeof(struct carl9170fw_desc_head)) + return ERR_PTR(-EINVAL); + + tmp = malloc(sizeof(*tmp) + len); + if (!tmp) + return ERR_PTR(-ENOMEM); + + fw->desc_list_entries++; + fw->desc_list_len += len; + + memcpy(tmp->data, desc, len); + return tmp; +} + +static void __carlfw_release(struct carlfw_file *f) +{ + f->len = 0; + if (f->name) + free(f->name); + f->name = NULL; + + if (f->data) + free(f->data); + f->data = NULL; +} + +void carlfw_release(struct carlfw *fw) +{ + struct carlfw_list_entry *entry; + + if (!IS_ERR_OR_NULL(fw)) { + while (!list_empty(&fw->desc_list)) { + entry = list_entry(fw->desc_list.next, + struct carlfw_list_entry, h.list); + carlfw_entry_del(fw, entry); + } + + __carlfw_release(&fw->fw); + __carlfw_release(&fw->hdr); + free(fw); + } +} + +static int __carlfw_load(struct carlfw_file *file, const char *name, const char *mode) +{ + struct stat file_stat; + FILE *fh; + int err; + + fh = fopen(name, mode); + if (fh == NULL) + return errno ? -errno : -1; + + err = fstat(fileno(fh), &file_stat); + if (err) + return errno ? -errno : -1; + + file->len = file_stat.st_size; + file->data = malloc(file->len); + if (file->data == NULL) + return -ENOMEM; + + err = fread(file->data, file->len, 1, fh); + if (err != 1) + return -ferror(fh); + + file->name = strdup(name); + fclose(fh); + + if (!file->name) + return -ENOMEM; + + return 0; +} + +static void *__carlfw_find_desc(struct carlfw_file *file, + uint8_t descid[4], + unsigned int len, + uint8_t compatible_revision) +{ + int scan = file->len, found = 0; + struct carl9170fw_desc_head *tmp = NULL; + + while (scan >= 0) { + if (file->data[scan] == descid[CARL9170FW_MAGIC_SIZE - found - 1]) + found++; + else + found = 0; + + if (found == CARL9170FW_MAGIC_SIZE) + break; + + scan--; + } + + if (found == CARL9170FW_MAGIC_SIZE) { + tmp = (void *) &file->data[scan]; + + if (!CHECK_HDR_VERSION(tmp, compatible_revision) && + (le16_to_cpu(tmp->length) >= len)) + return tmp; + } + + return NULL; +} + +void *carlfw_find_desc(struct carlfw *fw, + const uint8_t descid[4], + const unsigned int len, + const uint8_t compatible_revision) +{ + struct carlfw_list_entry *tmp; + + tmp = carlfw_find_entry(fw, descid, len, compatible_revision); + + return tmp ? carlfw_entry_to_desc(tmp) : NULL; +} + +int carlfw_desc_add_tail(struct carlfw *fw, + const struct carl9170fw_desc_head *desc) +{ + struct carlfw_list_entry *tmp; + + tmp = __carlfw_entry_add_prepare(fw, desc); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + + list_add_tail(&tmp->h.list, &fw->desc_list); + return 0; +} + +int carlfw_desc_add(struct carlfw *fw, + const struct carl9170fw_desc_head *desc, + struct carl9170fw_desc_head *prev, + struct carl9170fw_desc_head *next) +{ + struct carlfw_list_entry *tmp; + + tmp = __carlfw_entry_add_prepare(fw, desc); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + + list_add(&tmp->h.list, &((carlfw_desc_to_entry(prev))->h.list), + &((carlfw_desc_to_entry(next))->h.list)); + return 0; +} + +int carlfw_desc_add_before(struct carlfw *fw, + const struct carl9170fw_desc_head *desc, + struct carl9170fw_desc_head *pos) +{ + struct carl9170fw_desc_head *prev; + struct carlfw_list_entry *prev_entry; + + prev_entry = carlfw_desc_to_entry(pos); + + prev = carlfw_entry_to_desc((struct carlfw_list_entry *) prev_entry->h.list.prev); + + return carlfw_desc_add(fw, desc, prev, pos); +} + +void carlfw_desc_unlink(struct carlfw *fw, + struct carl9170fw_desc_head *desc) +{ + carlfw_entry_unlink(fw, carlfw_desc_to_entry(desc)); +} + +void carlfw_desc_del(struct carlfw *fw, + struct carl9170fw_desc_head *desc) +{ + carlfw_entry_del(fw, carlfw_desc_to_entry(desc)); +} + +void *carlfw_desc_mod_len(struct carlfw *fw __unused, + struct carl9170fw_desc_head *desc, size_t len) +{ + struct carlfw_list_entry *obj, tmp; + int new_len = le16_to_cpu(desc->length) + len; + + if (new_len < (int)sizeof(*desc)) + return ERR_PTR(-EINVAL); + + if (new_len > CARL9170FW_DESC_MAX_LENGTH) + return ERR_PTR(-E2BIG); + + obj = carlfw_desc_to_entry(desc); + + memcpy(&tmp, obj, sizeof(tmp)); + obj = realloc(obj, new_len + sizeof(struct carlfw_list_entry_head)); + if (obj == NULL) + return ERR_PTR(-ENOMEM); + + list_replace(&tmp.h.list, &obj->h.list); + + desc = carlfw_entry_to_desc(obj); + desc->length = le16_to_cpu(new_len); + fw->desc_list_len += len; + + return desc; +} + +void *carlfw_desc_next(struct carlfw *fw, + struct carl9170fw_desc_head *pos) +{ + struct carlfw_list_entry *entry; + + if (!pos) + entry = (struct carlfw_list_entry *) &fw->desc_list; + else + entry = carlfw_desc_to_entry(pos); + + if (list_at_tail(entry, &fw->desc_list, h.list)) + return NULL; + + entry = (struct carlfw_list_entry *) entry->h.list.next; + + return carlfw_entry_to_desc(entry); +} + +static int carlfw_parse_descs(struct carlfw *fw, + struct carl9170fw_otus_desc *otus_desc) +{ + const struct carl9170fw_desc_head *iter = NULL; + int err; + + carl9170fw_for_each_hdr(iter, &otus_desc->head) { + err = carlfw_desc_add_tail(fw, iter); + if (err) + return err; + } + /* LAST is added automatically by carlfw_store */ + + return err; +} + +#if BYTE_ORDER == LITTLE_ENDIAN +#define CRCPOLY_LE 0xedb88320 + +/* copied from the linux kernel */ +static uint32_t crc32_le(uint32_t crc, unsigned char const *p, size_t len) +{ + int i; + while (len--) { + crc ^= *p++; + for (i = 0; i < 8; i++) + crc = (crc >> 1) ^ ((crc & 1) ? CRCPOLY_LE : 0); + } + return crc; +} +#else +#error "this tool does not work with a big endian host yet!" +#endif + +static int carlfw_check_crc32s(struct carlfw *fw) +{ + struct carl9170fw_chk_desc *chk_desc; + struct carlfw_list_entry *iter; + unsigned int elen; + uint32_t crc32; + + chk_desc = carlfw_find_desc(fw, (uint8_t *) CHK_MAGIC, + sizeof(*chk_desc), + CARL9170FW_CHK_DESC_CUR_VER); + if (!chk_desc) + return -ENODATA; + + crc32 = crc32_le(~0, (void *) fw->fw.data, fw->fw.len); + if (crc32 != le32_to_cpu(chk_desc->fw_crc32)) + return -EINVAL; + + carlfw_walk_descs(iter, fw) { + elen = le16_to_cpu(iter->head.length); + + if (carl9170fw_desc_cmp(&iter->head, (uint8_t *) CHK_MAGIC, + sizeof(*chk_desc), + CARL9170FW_CHK_DESC_CUR_VER)) + continue; + + crc32 = crc32_le(crc32, (void *) &iter->head, elen); + } + + if (crc32 != le32_to_cpu(chk_desc->hdr_crc32)) + return -EINVAL; + + return 0; +} + +struct carlfw *carlfw_load(const char *basename) +{ + char filename[256]; + struct carlfw *fw; + struct carl9170fw_otus_desc *otus_desc; + struct carl9170fw_last_desc *last_desc; + struct carlfw_file *hdr_file; + unsigned long fin, diff, off, rem; + int err; + + fw = calloc(1, sizeof(*fw)); + if (!fw) + return ERR_PTR(-ENOMEM); + + init_list_head(&fw->desc_list); + + err = __carlfw_load(&fw->fw, basename, "r"); + if (err) + goto err_out; + + if (fw->hdr.name) + hdr_file = &fw->hdr; + else + hdr_file = &fw->fw; + + otus_desc = __carlfw_find_desc(hdr_file, (uint8_t *) OTUS_MAGIC, + sizeof(*otus_desc), + CARL9170FW_OTUS_DESC_CUR_VER); + last_desc = __carlfw_find_desc(hdr_file, (uint8_t *) LAST_MAGIC, + sizeof(*last_desc), + CARL9170FW_LAST_DESC_CUR_VER); + + if (!otus_desc || !last_desc || + (unsigned long) otus_desc > (unsigned long) last_desc) { + err = -ENODATA; + goto err_out; + } + + err = carlfw_parse_descs(fw, otus_desc); + if (err) + goto err_out; + + fin = (unsigned long)last_desc + sizeof(*last_desc); + diff = fin - (unsigned long)otus_desc; + rem = hdr_file->len - (fin - (unsigned long) hdr_file->data); + + if (rem) { + off = (unsigned long)otus_desc - (unsigned long)hdr_file->data; + memmove(&hdr_file->data[off], + ((uint8_t *)last_desc) + sizeof(*last_desc), rem); + } + + hdr_file->len -= diff; + hdr_file->data = realloc(hdr_file->data, hdr_file->len); + if (!hdr_file->data && hdr_file->len) { + err = -ENOMEM; + goto err_out; + } + + err = carlfw_check_crc32s(fw); + if (err && err != -ENODATA) + goto err_out; + + return fw; + +err_out: + carlfw_release(fw); + return ERR_PTR(err); +} + +static int carlfw_apply_checksums(struct carlfw *fw) +{ + struct carlfw_list_entry *iter; + struct carl9170fw_chk_desc tmp = { + CARL9170FW_FILL_DESC(CHK_MAGIC, sizeof(tmp), + CARL9170FW_CHK_DESC_MIN_VER, + CARL9170FW_CHK_DESC_CUR_VER) }; + struct carl9170fw_chk_desc *chk_desc = NULL; + int err = 0; + unsigned int len = 0, elen, max_len; + uint32_t crc32; + + chk_desc = carlfw_find_desc(fw, (uint8_t *) CHK_MAGIC, + sizeof(*chk_desc), + CARL9170FW_CHK_DESC_CUR_VER); + if (chk_desc) { + carlfw_desc_del(fw, &chk_desc->head); + chk_desc = NULL; + } + + max_len = fw->desc_list_len; + + crc32 = crc32_le(~0, (void *) fw->fw.data, fw->fw.len); + tmp.fw_crc32 = cpu_to_le32(crc32); + + /* + * NOTE: + * + * The descriptor checksum is seeded with the firmware's crc32. + * This neat trick ensures that the driver can check whenever + * descriptor actually belongs to the firmware, or not. + */ + + carlfw_walk_descs(iter, fw) { + elen = le16_to_cpu(iter->head.length); + + if (max_len < len + elen) + return -EMSGSIZE; + + crc32 = crc32_le(crc32, (void *) &iter->head, elen); + len += elen; + } + + tmp.hdr_crc32 = cpu_to_le32(crc32); + + err = carlfw_desc_add_tail(fw, &tmp.head); + + return err; +} + +int carlfw_store(struct carlfw *fw) +{ + struct carl9170fw_last_desc last_desc = { + CARL9170FW_FILL_DESC(LAST_MAGIC, sizeof(last_desc), + CARL9170FW_LAST_DESC_MIN_VER, + CARL9170FW_LAST_DESC_CUR_VER) }; + + struct carlfw_list_entry *iter; + FILE *fh; + int err, elen; + + err = carlfw_apply_checksums(fw); + if (err) + return err; + + fh = fopen(fw->fw.name, "w"); + if (!fh) + return -errno; + + err = fwrite(fw->fw.data, fw->fw.len, 1, fh); + if (err != 1) { + err = -errno; + goto close_out; + } + + if (fw->hdr.name) { + fclose(fh); + + fh = fopen(fw->hdr.name, "w"); + } + + carlfw_walk_descs(iter, fw) { + elen = le16_to_cpu(iter->head.length); + + if (elen > CARL9170FW_DESC_MAX_LENGTH) { + err = -E2BIG; + goto close_out; + } + + err = fwrite(iter->data, elen, 1, fh); + if (err != 1) { + err = -ferror(fh); + goto close_out; + } + } + + err = fwrite(&last_desc, sizeof(last_desc), 1, fh); + if (err != 1) { + err = -ferror(fh); + goto close_out; + } + + err = 0; + +close_out: + fclose(fh); + return err; +} + +void *carlfw_mod_tailroom(struct carlfw *fw, ssize_t len) +{ + size_t new_len; + void *buf; + + new_len = fw->fw.len + len; + + if (!carl9170fw_size_check(new_len)) + return ERR_PTR(-EINVAL); + + buf = realloc(fw->fw.data, new_len); + if (buf == NULL) + return ERR_PTR(-ENOMEM); + + fw->fw.len = new_len; + fw->fw.data = buf; + return &fw->fw.data[new_len - len]; +} + +void *carlfw_mod_headroom(struct carlfw *fw, ssize_t len) +{ + size_t new_len; + void *ptr; + + new_len = fw->fw.len + len; + if (!carl9170fw_size_check(new_len)) + return ERR_PTR(-EINVAL); + + if (len < 0) + memmove(fw->fw.data, &fw->fw.data[len], new_len); + + ptr = carlfw_mod_tailroom(fw, len); + if (IS_ERR_OR_NULL(ptr)) + return ptr; + + if (len > 0) + memmove(&fw->fw.data[len], &fw->fw.data[0], new_len - len); + + return fw->fw.data; +} + +void *carlfw_get_fw(struct carlfw *fw, size_t *len) +{ + *len = fw->fw.len; + return fw->fw.data; +} + +unsigned int carlfw_get_descs_num(struct carlfw *fw) +{ + return fw->desc_list_entries; +} + +unsigned int carlfw_get_descs_size(struct carlfw *fw) +{ + return fw->desc_list_len; +} |