+/* baytrail_sst_elf_firmware_convert - convert elf SST firmwares to new format
+ *
+ * Copyright (C) 2015 Antonio Ospite <ao2@ao2.it>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <endian.h>
+
+#include <gelf.h>
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+/* SSP2 address */
+#define SST_BYT_IRAM_ADDRESS 0xff2c0000
+#define SST_BYT_DRAM_ADDRESS 0xff300000
+#define SST_BYT_DDR_ADDRESS 0xc0000000
+
+struct sst_ram_region {
+ const char *name;
+ uint32_t id;
+ uint32_t start_address;
+};
+
+static struct sst_ram_region ram_regions[] = {
+ { "IRAM", 1, SST_BYT_IRAM_ADDRESS },
+ { "DRAM", 2, SST_BYT_DRAM_ADDRESS },
+ { "DDR", 5, SST_BYT_DDR_ADDRESS },
+};
+
+static inline void fwrite_le32(uint32_t x, FILE *file)
+{
+ uint32_t tmp = htole32(x);
+ fwrite(&tmp, sizeof(tmp), 1, file);
+}
+
+static struct sst_ram_region *phdr_to_sst_ram_region(GElf_Phdr *phdr)
+{
+ if (!phdr)
+ return NULL;
+
+ if (phdr->p_type == PT_LOAD && phdr->p_filesz != 0) {
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(ram_regions); i++) {
+ struct sst_ram_region *r = &ram_regions[i];
+
+ if ((phdr->p_vaddr & r->start_address) == r->start_address)
+ return r;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * TODO: introduce data structures to represent the firmware headers and
+ * separate the firmware contruction from saving it to a file.
+ *
+ * This way the elf file can be scanned just once.
+ */
+static int convert_elf_to_sst_firmware(Elf *elf, FILE *output_file)
+{
+ int ret;
+ size_t num_phdrs = 0;
+ unsigned int phdr_index;
+ unsigned int num_sst_blocks;
+ unsigned int sst_blocks_total_size;
+ char *data;
+ size_t data_size = 0;
+
+ ret = elf_getphdrnum(elf, &num_phdrs);
+ if (ret < 0) {
+ fprintf(stderr, "Cannot get number of program headers: %s\n",
+ elf_errmsg(elf_errno()));
+ exit(EXIT_FAILURE);
+ }
+
+ /*
+ * Perform a first scan just to see how many SST blocks there are and
+ * how much space they take..
+ *
+ * Knowing this info before writing the firmware allows to write the
+ * firmware incrementally without going back to amend the headers to
+ * fill the number of blocks and the module and file sizes.
+ */
+ num_sst_blocks = 0;
+ sst_blocks_total_size = 0;
+ for (phdr_index = 0; phdr_index < num_phdrs; ++phdr_index) {
+ void *retp;
+ GElf_Phdr phdr;
+ struct sst_ram_region *r;
+
+ retp = gelf_getphdr(elf, phdr_index, &phdr);
+ if (retp != &phdr) {
+ fprintf(stderr, "gelf_getphdr() failed: %s.",
+ elf_errmsg(elf_errno()));
+ exit(EXIT_FAILURE);
+ }
+
+ r = phdr_to_sst_ram_region(&phdr);
+ if (r) {
+ unsigned int pre_padding = 0;
+ num_sst_blocks++;
+ sst_blocks_total_size += phdr.p_filesz;
+
+#if 0
+ if ((phdr.p_vaddr % 16) != 0) {
+ pre_padding = 16 - (phdr.p_vaddr % 16);
+ fprintf(stderr, "pre_padding: %d\n", pre_padding);
+ sst_blocks_total_size += pre_padding;
+ }
+#endif
+
+ /* align to multiple of 4 */
+ if (((phdr.p_filesz + pre_padding) % 4) != 0) {
+ unsigned int post_padding = 4 - ((phdr.p_filesz + pre_padding) % 4);
+ fprintf(stderr, "filesz: %ld\n", phdr.p_filesz);
+ fprintf(stderr, "post_padding: %d\n", post_padding);
+ sst_blocks_total_size += post_padding;
+ }
+ }
+ }
+
+ fprintf(stderr, "Number of SST blocks: %d\n", num_sst_blocks);
+ fprintf(stderr, "SST blocks total size: %d\n", sst_blocks_total_size);
+
+ data = elf_rawfile(elf, &data_size);
+ if (data == NULL) {
+ fprintf(stderr, "Cannot get raw file contents: %s\n",
+ elf_errmsg(elf_errno()));
+ exit(EXIT_FAILURE);
+ }
+
+ /* start writing the SST firmware file */
+ fwrite("$SST\n", 1, 4, output_file);
+
+ /* file size: add blocks and module headers sizes */
+ fwrite_le32(sst_blocks_total_size + num_sst_blocks * 16 + 20, output_file);
+
+ /* num modules */
+ fwrite_le32(1, output_file);
+
+ /* file format */
+ fwrite_le32(256, output_file);
+
+ /* reserved */
+ fwrite_le32(0, output_file);
+ fwrite_le32(0, output_file);
+ fwrite_le32(0, output_file);
+ fwrite_le32(0, output_file);
+
+ /* start writing the SST module */
+ fwrite("$SST\n", 1, 4, output_file);
+
+ /* module size: add blocks headers sizes */
+ fwrite_le32(sst_blocks_total_size + num_sst_blocks * 16, output_file);
+
+ /* num blocks */
+ fwrite_le32(num_sst_blocks, output_file);
+
+ /* block type */
+ fwrite_le32(65535, output_file);
+
+ /* entry point */
+ fwrite_le32(0, output_file);
+
+ for (phdr_index = 0; phdr_index < num_phdrs; ++phdr_index) {
+ void *retp;
+ GElf_Phdr phdr;
+ struct sst_ram_region *r;
+
+ retp = gelf_getphdr(elf, phdr_index, &phdr);
+ if (retp != &phdr) {
+ fprintf(stderr, "gelf_getphdr() failed: %s.",
+ elf_errmsg(elf_errno()));
+ return -EINVAL;
+ }
+
+ r = phdr_to_sst_ram_region(&phdr);
+ if (r) {
+ uint32_t block_size;
+ uint32_t ram_offset;
+ unsigned int pre_padding = 0;
+ unsigned int post_padding = 0;
+ const uint8_t zero = 0;
+ unsigned int i;
+
+#if 0
+ if ((phdr.p_vaddr % 16) != 0)
+ pre_padding = 16 - (phdr.p_vaddr % 16);
+#endif
+
+ if (((phdr.p_filesz + pre_padding) % 4) != 0)
+ post_padding = 4 - ((phdr.p_filesz + pre_padding) % 4);
+
+ ram_offset = (phdr.p_vaddr & ~r->start_address) - pre_padding;
+ block_size = phdr.p_filesz + pre_padding + post_padding;
+
+ fprintf(stderr, "%s, id: %d, offset: 0x%08x\n", r->name, r->id, ram_offset);
+ fprintf(stderr, "vaddr: 0x%08lx paddr: 0x%08lx\n", phdr.p_vaddr, phdr.p_paddr);
+ fprintf(stderr, "alignment: %ld\n", phdr.p_align);
+ fprintf(stderr, "block_size: %d\n", block_size);
+
+ /* ram type */
+ fwrite_le32(r->id, output_file);
+
+ /* block size */
+ fwrite_le32(block_size, output_file);
+
+ /* ram offset */
+ fwrite_le32(ram_offset, output_file);
+
+ /* reserved */
+ fwrite_le32(0, output_file);
+
+ /* pre padding */
+ for (i = 0; i < pre_padding; i++)
+ fwrite(&zero, sizeof(zero), 1, output_file);
+
+ /* data */
+ fwrite(data + phdr.p_offset, phdr.p_filesz, 1, output_file);
+
+ /* post padding */
+ for (i = 0; i < post_padding; i++)
+ fwrite(&zero, sizeof(zero), 1, output_file);
+ }
+ }
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ int fd;
+ Elf *elf;
+ unsigned int version;
+ Elf_Kind kind;
+ FILE *output_file;
+ int ret;
+
+ if (argc != 3) {
+ fprintf(stderr, "usage: %s <elf_file> <output_file>\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ version = elf_version(EV_CURRENT);
+ if (version == EV_NONE) {
+ fprintf(stderr, "Cannot initialize ELF library: %s\n",
+ elf_errmsg(elf_errno()));
+ exit(EXIT_FAILURE);
+ }
+
+ fd = open(argv[1], O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "Cannot open \"%s\": %s", argv[1],
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ elf = elf_begin(fd, ELF_C_READ, NULL);
+ if (elf == NULL) {
+ fprintf(stderr, "Cannot initialize ELF descriptor: %s.",
+ elf_errmsg(elf_errno()));
+ exit(EXIT_FAILURE);
+ }
+
+ kind = elf_kind(elf);
+ if (kind != ELF_K_ELF) {
+ fprintf(stderr, "\"%s\" is not an ELF file.\n", argv[1]);
+ exit(EXIT_FAILURE);
+ }
+
+ output_file = fopen(argv[2], "wb");
+ if (output_file == NULL) {
+ fprintf(stderr, "Cannot open output file \"%s\": %s", argv[1],
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ ret = convert_elf_to_sst_firmware(elf, output_file);
+ if (ret < 0) {
+ fprintf(stderr, "Cannot convert elf to SST firmware file\n");
+ exit(EXIT_FAILURE);
+ }
+
+ elf_end(elf);
+ close(fd);
+
+ exit(EXIT_SUCCESS);
+}