bcmclm: new tool for reading Broadcom's CLM data
authorRafał Miłecki <rafal@milecki.pl>
Wed, 21 Jun 2023 07:29:00 +0000 (09:29 +0200)
committerRafał Miłecki <rafal@milecki.pl>
Thu, 22 Jun 2023 10:46:44 +0000 (12:46 +0200)
Broadcom developed CLM format for devices vendors to provide regulatory
configuration independently of executable firmware. They contain info
about countries, channels and rates.

CLM data can be packaged with chipset info as CLM BLOB and uploaded to
FullMAC firmware for complete channel support and maximum transmission
power.

CLM binary format is a bit tricky. It consists of a lot of binary
structs with all kind of numbers and flags. In-file addresses of those
structs and then stored in parent structs.

This tool provides initial support for reading CLM data content. It
parses the root struct and follows info lower level structs.

Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
CMakeLists.txt
src/bcmclm.c [new file with mode: 0644]

index 41c8da44bc68c98a22eac36bc472e575f65415ca..c1750d478c953061c5e1021a2cbc814c475daae2 100644 (file)
@@ -33,6 +33,7 @@ FW_UTIL(avm-wasp-checksum "" --std=gnu99 "")
 FW_UTIL(bcm4908asus "" "" "")
 FW_UTIL(bcm4908kernel "" "" "")
 FW_UTIL(bcmblob "" "" "")
+FW_UTIL(bcmclm "" "" "")
 FW_UTIL(buffalo-enc src/buffalo-lib.c "" "")
 FW_UTIL(buffalo-tag src/buffalo-lib.c "" "")
 FW_UTIL(buffalo-tftp src/buffalo-lib.c "" "")
diff --git a/src/bcmclm.c b/src/bcmclm.c
new file mode 100644 (file)
index 0000000..ac8eacf
--- /dev/null
@@ -0,0 +1,337 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2023 Rafał Miłecki <rafal@milecki.pl>
+ */
+
+#include <byteswap.h>
+#include <endian.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#if !defined(__BYTE_ORDER)
+#error "Unknown byte order"
+#endif
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+#define cpu_to_le32(x) bswap_32(x)
+#define le32_to_cpu(x) bswap_32(x)
+#define cpu_to_be32(x) (x)
+#define be32_to_cpu(x) (x)
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+#define cpu_to_le32(x) (x)
+#define le32_to_cpu(x) (x)
+#define cpu_to_be32(x) bswap_32(x)
+#define be32_to_cpu(x) bswap_32(x)
+#else
+#error "Unsupported endianness"
+#endif
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
+
+#define BCMCLM_MAGIC           "CLM DATA"
+
+/* Raw data */
+
+struct bcmclm_header {
+       char magic[8];
+       uint32_t unk0;
+       uint8_t unk1[2];
+       char api[20];
+       char compiler[10];
+       uint32_t virtual_header_address;
+       uint32_t lookup_table_address;
+       char clm_import_ver[30];
+       char manufacturer[22];
+};
+
+struct bcmclm_lookup_table {
+       uint32_t offset0;
+       uint32_t offset1;
+       uint32_t offset2;
+       uint32_t offset3;
+       uint32_t offset4;
+       uint32_t offset5;
+       uint32_t offset6;
+       uint32_t offset7;
+       uint32_t offset8;
+       uint32_t offset9;
+       uint32_t offset10;
+       uint32_t offset11;
+       uint32_t offset12;
+       uint32_t offset13;
+       uint32_t offset14;
+       uint32_t offset15;
+       uint32_t offset16;
+       uint32_t offset17;
+       uint32_t offset18;
+       uint32_t offset19;
+       uint32_t offset20;
+       uint32_t offset21;
+       uint32_t offset22;
+       uint32_t offset23;
+       uint32_t offset_creation_date;
+       uint32_t offset25;
+       uint32_t offset26;
+       uint32_t offset27;
+       uint32_t offset28;
+       uint32_t offset29;
+       uint32_t offset30;
+       uint32_t offset31;
+       uint32_t offset32;
+       uint32_t offset33;
+       uint32_t offset34;
+       uint32_t offset35;
+       uint32_t offset36;
+       uint32_t offset37;
+       uint32_t offset38;
+       uint32_t offset39;
+       uint32_t offset40;
+       uint32_t offset41;
+       uint32_t offset42;
+       uint32_t offset43;
+       uint32_t offset44;
+       uint32_t offset45;
+       uint32_t offset46;
+       uint32_t offset47;
+};
+
+/* Parsed info */
+
+struct bcmclm_info {
+       struct bcmclm_header header;
+       struct bcmclm_lookup_table lookup_table;
+       size_t file_size;
+       size_t clm_offset;
+       size_t offsets_fixup;
+};
+
+static inline size_t bcmclm_min(size_t x, size_t y)
+{
+       return x < y ? x : y;
+}
+
+/**************************************************
+ * Helpers
+ **************************************************/
+
+static FILE *bcmclm_open(const char *pathname, const char *mode)
+{
+       struct stat st;
+
+       if (pathname)
+               return fopen(pathname, mode);
+
+       if (isatty(fileno(stdin))) {
+               fprintf(stderr, "Reading from TTY stdin is unsupported\n");
+               return NULL;
+       }
+
+       if (fstat(fileno(stdin), &st)) {
+               fprintf(stderr, "Failed to fstat stdin: %d\n", -errno);
+               return NULL;
+       }
+
+       if (S_ISFIFO(st.st_mode)) {
+               fprintf(stderr, "Reading from pipe stdin is unsupported\n");
+               return NULL;
+       }
+
+       return stdin;
+}
+
+static void bcmclm_close(FILE *fp)
+{
+       if (fp != stdin)
+               fclose(fp);
+}
+
+/**************************************************
+ * Existing CLM parser
+ **************************************************/
+
+static int bcmclm_search(FILE *fp, struct bcmclm_info *info)
+{
+       uint8_t buf[1024];
+       size_t offset = 0;
+       size_t bytes;
+       int i;
+
+       while ((bytes = fread(buf, 1, sizeof(buf), fp)) == sizeof(buf)) {
+               for (i = 0; i < bytes - 12; i += 4) {
+                       uint32_t unk = le32_to_cpu(*(uint32_t *)(&buf[i + 8]));
+
+                       if (!memcmp(&buf[i], BCMCLM_MAGIC, 8) && !(unk & 0xff00ffff)) {
+                               info->clm_offset = offset + i;
+
+                               printf("Found CLM at offset 0x%zx\n", info->clm_offset);
+                               printf("\n");
+
+                               return 0;
+                       }
+               }
+
+               offset += bytes;
+       }
+
+       return -ENOENT;
+}
+
+static int bcmclm_parse(FILE *fp, struct bcmclm_info *info)
+{
+       struct bcmclm_header *header = &info->header;
+       struct bcmclm_lookup_table *lookup_table = &info->lookup_table;
+       struct stat st;
+       int err = 0;
+
+       /* File size */
+
+       if (fstat(fileno(fp), &st)) {
+               err = -errno;
+               fprintf(stderr, "Failed to fstat: %d\n", err);
+               return err;
+       }
+       info->file_size = st.st_size;
+
+       /* Header */
+
+       fseek(fp, info->clm_offset, SEEK_SET);
+
+       if (fread(header, 1, sizeof(*header), fp) != sizeof(*header)) {
+               fprintf(stderr, "Failed to read CLM header\n");
+               return -EIO;
+       }
+
+       if (strncmp(header->magic, BCMCLM_MAGIC, 8)) {
+               fprintf(stderr, "Invalid CLM header magic\n");
+               return -EPROTO;
+       }
+
+       info->offsets_fixup = info->clm_offset - le32_to_cpu(header->virtual_header_address);
+
+       /* Lookup table */
+
+       fseek(fp, le32_to_cpu(info->header.lookup_table_address) + info->offsets_fixup, SEEK_SET);
+
+       if (fread(lookup_table, 1, sizeof(*lookup_table), fp) != sizeof(*lookup_table)) {
+               fprintf(stderr, "Failed to read lookup table\n");
+               return -EIO;
+       }
+
+       return 0;
+}
+
+/**************************************************
+ * Info
+ **************************************************/
+
+static void bcmclm_print_lookup_data(FILE *fp, struct bcmclm_info *info)
+{
+       uint8_t buf[64];
+       size_t bytes;
+
+       if (info->lookup_table.offset_creation_date) {
+               printf("\n");
+
+               fseek(fp, le32_to_cpu(info->lookup_table.offset_creation_date) + info->offsets_fixup, SEEK_SET);
+
+               bytes = fread(buf, 1, sizeof(buf), fp);
+               if (bytes) {
+                       printf("Creation date: %s\n", buf);
+               }
+       }
+}
+
+static int bcmclm_info(int argc, char **argv)
+{
+       struct bcmclm_info info = {};
+       const char *pathname = NULL;
+       int search = 0;
+       FILE *fp;
+       int c;
+       int err = 0;
+
+       while ((c = getopt(argc, argv, "i:s")) != -1) {
+               switch (c) {
+               case 'i':
+                       pathname = optarg;
+                       break;
+               case 's':
+                       search = 1;
+                       break;
+               }
+       }
+
+       fp = bcmclm_open(pathname, "r");
+       if (!fp) {
+               fprintf(stderr, "Failed to open CLM\n");
+               err = -EACCES;
+               goto out;
+       }
+
+       if (search) {
+               err = bcmclm_search(fp, &info);
+               if (err) {
+                       fprintf(stderr, "Failed to find CLM in input file\n");
+                       goto err_close;
+               }
+       }
+
+       err = bcmclm_parse(fp, &info);
+       if (err) {
+               fprintf(stderr, "Failed to parse CLM\n");
+               goto err_close;
+       }
+
+       printf("API: %s\n", info.header.api);
+       printf("Compiler: %s\n", info.header.compiler);
+       printf("clm_import_ver: %s\n", info.header.clm_import_ver);
+       printf("Manufacturer: %s\n", info.header.manufacturer);
+       printf("\n");
+       printf("Virtual header address: 0x%08x (real: 0x%zx)\n", le32_to_cpu(info.header.virtual_header_address), le32_to_cpu(info.header.virtual_header_address) + info.offsets_fixup);
+       printf("Virtual lookup table address: 0x%08x (real: 0x%zx)\n", le32_to_cpu(info.header.lookup_table_address), le32_to_cpu(info.header.lookup_table_address) + info.offsets_fixup);
+
+       bcmclm_print_lookup_data(fp, &info);
+
+err_close:
+       bcmclm_close(fp);
+out:
+       return err;
+}
+
+/**************************************************
+ * Start
+ **************************************************/
+
+static void usage()
+{
+       printf("Usage:\n");
+       printf("\n");
+       printf("Info about CLM:\n");
+       printf("\tbcmclm info <options>\n");
+       printf("\t-i <file>\t\t\t\t\tinput CLM\n");
+       printf("\t-s\t\t\t\t\tsearch for CLM data in bigger file\n");
+       printf("\n");
+       printf("Examples:\n");
+       printf("\tbcmclm info -i x.clm\n");
+       printf("\tbcmclm info -s -i brcmfmac4366c-pcie.bin\n");
+}
+
+int main(int argc, char **argv)
+{
+       if (argc > 1) {
+               optind++;
+               if (!strcmp(argv[1], "info"))
+                       return bcmclm_info(argc, argv);
+       }
+
+       usage();
+
+       return 0;
+}