summaryrefslogtreecommitdiffstats
path: root/carl9170fw/tools/lib/carlfw.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--carl9170fw/tools/lib/carlfw.c630
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;
+}