/*
* Program to link against Agere FW images, and dump contents to
* binary files for loading directly by linux drivers.
*
* Copyright (C) 2008 David Kilroy
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*/
/* Output format (LE numbers)
*
* vers [6] The text HFW and 3 ASCII digits indicating version
* headersize [2] Size of header inc vers, headersize and length
* entry point [4] NIC address of entry point
* blocks [4] Number of blocks (n)
* blk_offset [4] Offset to block data
* pdr_offset [4] Offset to PDR data
* pri_offset [4] Offset to primary plug data
* cpt_offset [4] Offset to compatibility data
* signature [arb] firmware signature
* Block_1
* ..
* Block_n
* Block_term
* pda_1
* ..
* pda_n
* pda_term
* pri_1
* ..
* pri_n
* pri_term
* compat_1
* ..
* compat_n
* compat_term
*
* Where Block_n is:
* addr [4] NIC address to program data
* length [2] Number of bytes of data to program
* data [arbitrary] Data to program
*
* block term is:
* 0xFFFFFFFF [4] BLOCK_END identifier
* 0x0000 [2] zero length
*
* pda_n and pri_n are:
* id [4] (Primary) PDA identifier
* addr [4] Address to program (Primary) PDA
* len [4] Number of bytes to program
*
* pda_term and pri_n are:
* 0x00000000 [4] PDI_END
* 0x00000000 [4]
* 0x00000000 [4]
*
* compat_n is:
* size [2] Length of LTV - ((n_bytes/2) - 1)
* code [2] LTV code - 0xFD21 (FW compatibility range)
* 0xFD22 (Modem I/F compatibility range)
* 0xFD23 (Controller I/F compatibility range)
* role [2] Who this restriction applies to?
* 0x00 - 'Supplier'
* 0x01 - 'Actor'
* id [2]
* spec_1 Specifications
* ...
* spec_20
*
* spec_n is:
* variant [2]
* bottom [2]
* top [2]
*
* There is more information available in the driver. In particular
* whether the block is supposed to be programmed to NV or volatile,
* and various flags.
*
* Apart from the header, the output format is compatible with the
* spectrum_cs image. The header is arbitrary.
*
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "dhf.h"
#define AP_SUFFIX "_ap_fw.bin"
#define STA_SUFFIX "_sta_fw.bin"
#define VERSION "HFW000"
/* 0xAABB to 0xBBAA */
#define swap_bytes_16(value) \
((((value) >> 8) & 0xFF) | \
(((value) & 0xFF) << 8))
/* 0xAABBCCDD to 0xDDCCBBAA */
#define reverse_bytes_32(value) \
((((value) >> 24) & 0x0000FF) | \
(((value) >> 8) & 0x00FF00) | \
(((value) << 8) & 0xFF0000) | \
(((value) & 0xFF) << 24))
/* 0xAABBCCDD to 0xBBAADDCC */
#define swap_bytes_32(value) \
((((value) >> 8) & 0x00FF00FF) | \
(((value) << 8) & 0xFF00FF00))
/* 0xAABBCCDD to 0xCCDDAABB */
#define swap_words_32(value) \
((((value) >> 16) & 0x0000FFFF) | \
(((value) << 16) & 0xFFFF0000))
/* address 0 1 2 3 */
/* Pure LE stores 0x12345678 as 0x78 0x56 0x34 0x12 */
/* Pure BE stores 0x12345678 as 0x12 0x34 0x56 0x78 */
/* BEW+LEB stores 0x12345678 as 0x34 0x12 0x78 0x56 */
/* LEW+BEB stores 0x12345678 as 0x56 0x78 0x12 0x34 */
int host_bytes_in_word_be = 0;
int host_words_in_dword_be = 0;
#define host_to_le16(value) \
(host_bytes_in_word_be ? swap_bytes_16(value) : (value))
#define host_to_le32(value) \
(host_words_in_dword_be ? \
(host_bytes_in_word_be ? reverse_bytes_32(value) \
: swap_bytes_32(value)) : \
(host_bytes_in_word_be ? swap_words_32(value) : (value)))
#define le16_to_host(value) \
(host_bytes_in_word_be ? swap_bytes_16(value) : (value))
#define le32_to_host(value) \
(host_words_in_dword_be ? \
(host_bytes_in_word_be ? reverse_bytes_32(value) \
: swap_bytes_32(value)) : \
(host_bytes_in_word_be ? swap_words_32(value) : (value)))
/* Use C99 exact width types */
typedef uint32_t u32;
typedef uint16_t u16;
typedef uint8_t u8;
/* Checking endianess at runtime because performance isn't an issue,
* and I'd rather not add a configure step since this is slotting into the
* Agere source code. */
void check_endianess(void) {
union {
u32 dword;
u16 word[2];
u8 byte[4];
} data;
data.dword = 0x12345678;
if (data.word[0] == 0x1234) {
host_words_in_dword_be = 1;
}
else if (data.word[0] == 0x5678) {
host_words_in_dword_be = 0;
} else {
fprintf(stderr, "Can't determine endianess of host!\n");
exit(1);
}
data.word[0] = 0x1234;
if (data.byte[0] == 0x12) {
host_bytes_in_word_be = 1;
} else if (data.byte[0] == 0x34) {
host_bytes_in_word_be = 0;
} else {
fprintf(stderr, "Can't determine endianess of host!\n");
}
if (host_bytes_in_word_be == host_words_in_dword_be) {
fprintf (stdout, "Detected %s host\n",
host_bytes_in_word_be ? "big endian" : "little endian");
} else {
fprintf (stdout, "Detected host with mixed endianess\n");
}
return;
}
size_t count_blocks(memimage *image)
{
#if __wl_lkm < 718
# define MEMBLOCK memblock
# define BLKSIZE p->size
# define IF_HAVE_SEGMENT
#else
# define MEMBLOCK CFG_PROG_STRCT
# define BLKSIZE p->len
# define IF_HAVE_SEGMENT if (p->segment_size)
#endif
MEMBLOCK *p = image->codep;
size_t count = 0;
while (BLKSIZE)
{
/* Ignore zero data segments which will not be written */
IF_HAVE_SEGMENT
{
count++;
}
p++;
}
return count;
}
size_t count_pdr(plugrecord *r)
{
size_t count = 0;
if (r)
{
while (r->code)
{
count++;
r++;
}
}
return count;
}
size_t acc_block_size(memimage *image)
{
#if __wl_lkm < 718
# define MEMBLOCK memblock
# define BLKSIZE p->size
# define SEGSIZE p->size
# define IF_HAVE_SEGMENT
#else
# define MEMBLOCK CFG_PROG_STRCT
# define BLKSIZE p->len
# define SEGSIZE p->segment_size
# define IF_HAVE_SEGMENT if (p->segment_size)
#endif
MEMBLOCK *p = image->codep;
size_t len = 0;
while (BLKSIZE)
{
/* Ignore zero data segments which will not be written */
IF_HAVE_SEGMENT
{
len += SEGSIZE;
}
p++;
}
return len;
}
void dump_blocks(FILE* f, memimage *image)
{
#if __wl_lkm < 718
# define MEMBLOCK memblock
# define BLKSIZE p->size
# define SEGSIZE p->size
# define NICADDR p->addr
# define SEGDATA &(p->data[4]) /* Avoid initial CRC */
# define IF_HAVE_SEGMENT
#else
# define MEMBLOCK CFG_PROG_STRCT
# define BLKSIZE p->len
# define SEGSIZE p->segment_size
# define NICADDR p->nic_addr
# define SEGDATA p->host_addr
# define IF_HAVE_SEGMENT if (p->segment_size)
#endif
MEMBLOCK *p = image->codep;
u8 block_hdr[sizeof(NICADDR) + sizeof(SEGSIZE)];
u32 *addr = (u32 *) &block_hdr[0];
u16 *size = (u16 *) &block_hdr[sizeof(NICADDR)];
while (BLKSIZE)
{
IF_HAVE_SEGMENT
{
/* There is data to program in this block */
*addr = host_to_le32(NICADDR);
*size = host_to_le16(SEGSIZE);
fwrite (&block_hdr, 1, sizeof(block_hdr), f);
fwrite (SEGDATA, 1, SEGSIZE, f);
}
p++;
}
*addr = host_to_le32(0xFFFFFFFFu); /* Agree with spectrum BLOCK_END */
*size = host_to_le16(0u);
fwrite (&block_hdr, 1, sizeof(block_hdr), f);
return;
}
void dump_pdr(FILE *f, plugrecord *r)
{
u8 pdr[sizeof(r->code) + sizeof(r->addr) + sizeof(r->len)];
u32 *code = (u32*) &pdr[0];
u32 *addr = (u32*) &pdr[sizeof(r->code)];
u32 *len = (u32*) &pdr[sizeof(r->code) + sizeof(r->addr)];
if (!r)
goto terminate;
while (r->code)
{
*code = host_to_le32(r->code);
*addr = host_to_le32(r->addr);
*len = host_to_le32(r->len);
fwrite(&pdr, 1, sizeof(pdr), f);
r++;
}
terminate:
/* Terminate the PDR list */
*code = 0;
*addr = 0;
*len = 0;
fwrite(&pdr, 1, sizeof(pdr), f);
return;
}
void dump_compat(FILE *f, CFG_RANGE20_STRCT *c)
{
#if __wl_lkm < 718
#define VARIANT_STRUCT variant
#define VARIANT_NO number
#else
#define VARIANT_STRUCT var_rec
#define VARIANT_NO variant
#endif
u8 hdr[sizeof(c->id) + sizeof(c->typ) + sizeof(c->role) + sizeof(c->id)];
u8 spec[sizeof(c->VARIANT_STRUCT[0])];
u16 *len = (u16*) &hdr[0];
u16 *typ = (u16*) &hdr[sizeof(c->len)];
u16 *role = (u16*) &hdr[sizeof(c->len) + sizeof(c->typ)];
u16 *id = (u16*) &hdr[sizeof(c->len) + sizeof(c->typ) + sizeof(c->role)];
u16 *variant = (u16*) &spec[0];
u16 *bottom = (u16*) &spec[sizeof(c->VARIANT_STRUCT[0].VARIANT_NO)];
u16 *top = (u16*) &spec[sizeof(c->VARIANT_STRUCT[0].VARIANT_NO) + sizeof(c->VARIANT_STRUCT[0].bottom)];
int i;
while(c->len)
{
*len = host_to_le16(c->len);
*typ = host_to_le16(c->typ);
*role = host_to_le16(c->role);
*id = host_to_le16(c->id);
fwrite(&hdr, 1, sizeof(hdr), f);
for (i = 0; i < sizeof(c->VARIANT_STRUCT)/sizeof(c->VARIANT_STRUCT[0]); i++)
{
*variant = host_to_le16(c->VARIANT_STRUCT[i].VARIANT_NO);
*bottom = host_to_le16(c->VARIANT_STRUCT[i].bottom);
*top = host_to_le16(c->VARIANT_STRUCT[i].top);
fwrite(&spec, 1, sizeof(spec), f);
}
c++;
}
/* sentinel */
memset(&hdr[0], 0, sizeof(hdr));
memset(&spec[0], 0, sizeof(spec));
fwrite(&hdr, 1, sizeof(hdr), f);
for (i = 0; i < sizeof(c->VARIANT_STRUCT)/sizeof(c->VARIANT_STRUCT[0]); i++)
{
fwrite(&spec, 1, sizeof(spec), f);
}
return;
}
void dump_image(FILE* f, memimage *image)
{
#if __wl_lkm < 722
#define PDA pdaplug
#define PRI priplug
#else
#define PDA place_holder_1
#define PRI place_holder_2
#endif
u32 image_header[6];
u32 blocks = count_blocks(image);
u32 blk_offset = 0; /* Immediately after header */
u32 pdr_offset = (acc_block_size(image) +
((blocks + 1) * (sizeof(u32) + sizeof(u16))));
u32 pri_offset = pdr_offset +
((count_pdr(image->PDA) + 1) * sizeof(u32) * 3);
u32 cpt_offset = pri_offset +
((count_pdr(image->PRI) + 1) * sizeof(u32) * 3);
u16 headersize = ((sizeof(VERSION)-1) +
sizeof(u16) +
(sizeof(u32)*6)
#if __wl_lkm >= 718
+ sizeof(image->signature)
#endif
);
u32 *ptr = &image_header[0];
fwrite (VERSION, 1, sizeof(VERSION)-1, f);
headersize = host_to_le16(headersize);
fwrite (&headersize, 1, sizeof(headersize), f);
*ptr = host_to_le32(image->execution);
ptr++;
*ptr = host_to_le32(blocks);
ptr++;
*ptr = host_to_le32(blk_offset);
ptr++;
*ptr = host_to_le32(pdr_offset);
ptr++;
*ptr = host_to_le32(pri_offset);
ptr++;
*ptr = host_to_le32(cpt_offset);
ptr++;
fwrite (&image_header, 1, sizeof(image_header), f);
#if __wl_lkm >= 718
fwrite (&image->signature, 1, sizeof(image->signature), f);
#endif
dump_blocks(f, image);
dump_pdr(f, image->PDA);
dump_pdr(f, image->PRI);
dump_compat(f, image->compat);
return;
}
#if __wl_lkm < 722
extern memimage ap;
extern memimage station;
#else
extern memimage fw_image;
#define ap fw_image
#define station fw_image
#endif
int main (int argc, char** argv)
{
char *ap_filename;
char *sta_filename;
FILE *ap_file;
FILE *sta_file;
size_t len;
int rc = 0;
if (argc < 2)
{
printf("Please specify a root filename.\n"
"%s will be appended for primary firmware\n"
"%s will be appended for secondary firmaware\n",
AP_SUFFIX, STA_SUFFIX);
return 1;
}
check_endianess();
len = strlen(argv[1]);
if (ap.identity->comp_id != COMP_ID_FW_AP)
goto sta;
ap_filename = malloc(len + sizeof(AP_SUFFIX) + 1);
if (!ap_filename)
{
fprintf(stderr, "Out of memory\n");
return 1;
}
strncpy(ap_filename, argv[1], len);
ap_filename[len] = 0;
strcat(ap_filename, AP_SUFFIX);
ap_file = fopen(ap_filename, "w");
if (!ap_file)
{
fprintf(stderr, "Can't open %s for writing\n", ap_filename);
rc = 1;
}
else
{
dump_image(ap_file, &ap);
fclose(ap_file);
fprintf (stdout, "Written %s\n", ap_filename);
}
free(ap_filename);
sta:
if (station.identity->comp_id != COMP_ID_FW_STA)
goto out;
sta_filename = malloc(len + sizeof(STA_SUFFIX) + 1);
if (!sta_filename)
{
fprintf(stderr, "Out of memory\n");
return 1;
}
strncpy(sta_filename, argv[1], len);
sta_filename[len] = 0;
strcat(sta_filename,STA_SUFFIX);
sta_file = fopen(sta_filename,"w");
if (!sta_file)
{
fprintf(stderr, "Can't open %s for writing\n", sta_filename);
rc = 1;
}
else
{
dump_image(sta_file, &station);
fclose(sta_file);
fprintf (stdout, "Written %s\n", sta_filename);
}
free(sta_filename);
out:
return rc;
}