Foundry/tt/tools/verify_tensix_program_blob.c

169 lines
6.2 KiB
C

/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* Copyright (C) 2026 David (Corey) Herrera */
/**
* @file verify_tensix_program_blob.c
* @brief Compare the EMBEDDED program blob against the program directories on disk, byte
* for byte, and say which of the two is wrong when they differ.
*
* The generator produced `ft_tt_programs.h` from those directories. This tool reads the
* directories again, with plain `fread`, and `memcmp`s them against the arrays the
* compiler put in this binary. Nothing here shares an implementation with the emitter, so
* "the blob is the directories" is a comparison and not a restatement of how the blob was
* built.
*
* THIS IS THE TOOL THAT MAKES A REAL REGENERATION CHECKABLE. The blob can only be
* regenerated on the card host, where SFPI and the real program directories live. After that
* regeneration, this is what proves the committed header carries those exact bytes:
*
* make -f tools/Makefile verify-programs
* ./tools/build/verify_tensix_program_blob ../programs
*
* It also prints what the blob CONTAINS, because a comparison of an empty blob against a
* directory tree is trivially green and would otherwise read as a pass.
*
* @status Written on the Mac; exercised through tests/Makefile.programblob's fixtures. Has
* never been run against real SFPI program directories — that needs a bank session.
* @issues None
* @todo None
*/
#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ft_tt_device.h"
#include "ft_tt_memmap.h"
#include "ft_tt_program_blob.h"
#define MAX_IMAGE_BYTES (4u * 1024u * 1024u)
static const char *const k_kernel_file_names[FT_TT_PROC_COUNT] = {
"brisc.elf", "ncrisc.elf", "trisc0.elf", "trisc1.elf", "trisc2.elf"
};
static const char *const k_firmware_file_names[FT_TT_PROC_COUNT] = {
"fw_brisc.elf", "fw_ncrisc.elf", "fw_trisc0.elf", "fw_trisc1.elf", "fw_trisc2.elf"
};
/** Read a whole file. NULL on any failure, which is always a finding and never a skip. */
static unsigned char *read_whole_file(const char *path, size_t *out_size)
{
FILE *file = fopen(path, "rb");
unsigned char *buffer;
long size;
*out_size = 0;
if (!file)
return NULL;
if (fseek(file, 0, SEEK_END) != 0 || (size = ftell(file)) <= 0 ||
(size_t)size > MAX_IMAGE_BYTES || fseek(file, 0, SEEK_SET) != 0) {
fclose(file);
return NULL;
}
buffer = malloc((size_t)size);
if (!buffer) {
fclose(file);
return NULL;
}
if (fread(buffer, 1, (size_t)size, file) != (size_t)size) {
free(buffer);
fclose(file);
return NULL;
}
fclose(file);
*out_size = (size_t)size;
return buffer;
}
/** @return 0 when the file equals the embedded image, 1 otherwise (and it says why). */
static int compare_one(const char *root, const char *program, const char *file_name,
const ft_tt_program_blob_image *image)
{
char path[1024];
unsigned char *disk;
size_t size = 0;
if (snprintf(path, sizeof(path), "%s/%s/%s", root, program, file_name) < 0)
return 1;
disk = read_whole_file(path, &size);
if (!disk) {
printf(" %-16s MISSING or unreadable at %s\n", file_name, path);
return 1;
}
if (size != image->elf_size) {
printf(" %-16s DIFFERS: embedded %lu B, on disk %lu B\n", file_name,
(unsigned long)image->elf_size, (unsigned long)size);
free(disk);
return 1;
}
if (memcmp(disk, image->elf_data, size) != 0) {
printf(" %-16s DIFFERS: %lu bytes, embedded digest 0x%016llx, file digest "
"0x%016llx\n", file_name, (unsigned long)size,
(unsigned long long)image->elf_digest,
(unsigned long long)ft_tt_program_blob_digest(disk, size));
free(disk);
return 1;
}
printf(" %-16s matches (%lu B)\n", file_name, (unsigned long)size);
free(disk);
return 0;
}
static int verify_one_program(const char *root, const ft_tt_program_blob_entry *entry)
{
unsigned index;
int differences = 0;
printf(" %s (toolchain %s, %u kernel image(s), firmware set %s)\n", entry->name,
entry->toolchain, entry->image_count, entry->firmware->set_id);
if (ft_tt_program_blob_verify(entry) != FT_TT_OK) {
printf(" the EMBEDDED copy fails its own integrity check — this binary is "
"damaged, independently of what is on disk\n");
differences++;
}
for (index = 0; index < entry->image_count; index++) {
const ft_tt_program_blob_image *image = &entry->images[index];
differences += compare_one(root, entry->name, k_kernel_file_names[image->processor],
image);
}
for (index = 0; index < FT_TT_PROC_COUNT; index++)
differences += compare_one(root, entry->name, k_firmware_file_names[index],
&entry->firmware->images[index]);
return differences;
}
int main(int argc, char **argv)
{
unsigned count = ft_tt_program_blob_count();
unsigned index;
int differences = 0;
if (argc != 2) {
fprintf(stderr, "usage: %s <programs-root>\n\n"
"Compares every program embedded in this binary against\n"
"<programs-root>/<name>/ on disk, byte for byte.\n", argv[0]);
return 2;
}
/* Say what is in the blob BEFORE comparing anything. An empty blob compares clean
* against every directory tree in existence, and "no differences" would then be a
* statement about nothing. */
printf("blob: %s, %u program(s)\n", ft_tt_program_blob_generated_header(), count);
if (count == 0u) {
printf("REFUSED: this binary carries no embedded programs, so there is nothing to "
"compare. Regenerate with tools/regen_tensix_program_header.sh on the bank.\n");
return 1;
}
printf("against: %s\n\n", argv[1]);
for (index = 0; index < count; index++)
differences += verify_one_program(argv[1], ft_tt_program_blob_entry_at(index));
printf("\n%d difference(s)\n", differences);
return differences == 0 ? 0 : 1;
}