You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
630 lines
13 KiB
630 lines
13 KiB
/* |
|
* 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; |
|
}
|
|
|