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