/* * Copyright 2010-2011 Christian Lamparter * * 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 #include #include #include #include #include #include #include #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; }