// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2023 Rafał Miłecki */ #include #include #include #include #include #include #include #include #include #include #include #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 channels_2ghz; uint32_t channels_5ghz; 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; }; struct bcmclm_channels_lookup { uint32_t num; uint32_t offset; }; struct bcmclm_channels_range { uint8_t first; uint8_t last; uint8_t unknown; }; /* 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_channels(FILE *fp, struct bcmclm_info *info, const char *band, size_t num, size_t offset) { struct bcmclm_channels_range *ranges; size_t read; int i; ranges = calloc(num, sizeof(struct bcmclm_channels_range)); if (!ranges) { return; } fseek(fp, offset + info->offsets_fixup, SEEK_SET); read = fread(ranges, sizeof(struct bcmclm_channels_range), num, fp); if (read != num) { goto out; } for (i = 0; i < num; i++) { printf("%3s GHz band channels: %d - %d\n", band, le32_to_cpu(ranges[i].first), le32_to_cpu(ranges[i].last)); } out: free(ranges); } static void bcmclm_print_lookup_data(FILE *fp, struct bcmclm_info *info) { uint8_t buf[64]; size_t bytes; if (info->lookup_table.channels_2ghz) { struct bcmclm_channels_lookup channels; fseek(fp, le32_to_cpu(info->lookup_table.channels_2ghz) + info->offsets_fixup, SEEK_SET); bytes = fread(&channels, 1, sizeof(channels), fp); if (bytes == sizeof(channels)) { bcmclm_print_channels(fp, info, "2.4", le32_to_cpu(channels.num), le32_to_cpu(channels.offset)); } } if (info->lookup_table.channels_5ghz) { struct bcmclm_channels_lookup channels; fseek(fp, le32_to_cpu(info->lookup_table.channels_5ghz) + info->offsets_fixup, SEEK_SET); bytes = fread(&channels, 1, sizeof(channels), fp); if (bytes == sizeof(channels)) { bcmclm_print_channels(fp, info, "5", le32_to_cpu(channels.num), le32_to_cpu(channels.offset)); } } if (info->lookup_table.offset_creation_date) { 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); printf("\n"); 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 \n"); printf("\t-i \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; }