# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (C) 2026 David (Corey) Herrera
# ft_tt_core — per-core targeting, health probing and the recovery ladder.
#
#   make PHASE1B=1
#   ./build/ft_tt_core status all
#
# PHASE1B=1 is mandatory. Without it every device call returns FT_TT_EPERM before touching
# the NoC — the fence exists because a wrong NoC coordinate hangs the chip and costs a
# physical power cycle (spec §7.3). A diagnostic tool is exactly the thing that must not
# be built with the fence quietly bypassed by default.
#
# ---------------------------------------------------------------------------
# WHY THIS LINKS TWO FILES OUT OF tests/
# ---------------------------------------------------------------------------
# ft_tt_hardware_session.c owns the firmware boot sequence, and ft_tt_phase2_l1_layout.c
# derives the L1 map from where the firmware images landed. Both are labelled test
# scaffolding, and both are exactly what an operational tool needs. Copying the boot
# sequence into tools/ would produce a SECOND, subtly different boot sequence — and every
# ordering constraint in it is load-bearing (docs/tt_launch_notes.md §6). They are linked,
# not duplicated. If a second consumer appears they belong in the backend proper.

CC      ?= cc
SRC_DIR := ..
TEST_DIR := ../tests
BUILD   := build

CFLAGS  := -std=c99 -O1 -g \
           -Wall -Wextra -Werror -pedantic \
           -Wshadow -Wconversion -Wstrict-prototypes -Wformat=2 \
           -fstack-protector-strong \
           -I$(SRC_DIR) -I$(TEST_DIR)

DEPFLAGS := -MMD -MP

# The regen targets compile nothing for the host and never open the card, so they are
# exempt: demanding PHASE1B=1 to regenerate a header would be a fence in front of the wrong
# door. `verify-programs` is exempt for the stronger reason that it must NEVER be built with
# the fence open — it compares bytes in this binary against files on disk and has no
# business being able to reach a card at all.
ifeq ($(filter regen-kernels regen-kernels-check regen-programs regen-programs-check \
              verify-programs explode-programs check-kernel-compile-time-args \
              check-program-directory \
              check-program-provenance,$(MAKECMDGOALS)),)
ifndef PHASE1B
$(error ft_tt_core drives the card: build with PHASE1B=1 or every device call is refused)
endif
endif
# Guarded, so the exempt targets above cannot pick the define up by accident. Every
# non-exempt goal has already been stopped by the $(error) unless PHASE1B is set, so this
# changes nothing for them.
ifdef PHASE1B
CFLAGS += -DFT_TT_PHASE1B_NOC_ENABLED
endif

BACKEND_SOURCES := $(SRC_DIR)/ft_tt_kmd.c \
                   $(SRC_DIR)/ft_tt_arch.c \
                   $(SRC_DIR)/ft_tt_arch_blackhole.c \
                   $(SRC_DIR)/ft_tt_device.c \
                   $(SRC_DIR)/ft_tt_tlb.c \
                   $(SRC_DIR)/ft_tt_coord.c \
                   $(SRC_DIR)/ft_tt_arc_telemetry.c \
                   $(SRC_DIR)/ft_tt_noc.c \
                   $(SRC_DIR)/ft_tt_buffer.c \
                   $(SRC_DIR)/ft_tt_elf.c \
                   $(SRC_DIR)/ft_tt_kernel_blob.c \
                   $(SRC_DIR)/ft_tt_udrv_claim.c \
                   $(SRC_DIR)/ft_tt_udrv_reset.c \
                   $(SRC_DIR)/ft_tt_udrv_pci_sysfs.c \
                   $(SRC_DIR)/ft_tt_udrv_tlb_encode.c \
                   $(SRC_DIR)/ft_tt_udrv_noc.c \
           $(SRC_DIR)/ft_tt_udrv_noc_bank.c \
                   $(SRC_DIR)/ft_tt_udrv_arc_msg.c \
                   $(SRC_DIR)/ft_tt_udrv_arc_probe.c \
                   $(SRC_DIR)/ft_tt_runtime.c \
                   $(SRC_DIR)/ft_tt_runtime_udrv.c \
                   $(SRC_DIR)/ft_tt_tensix_claim.c \
                   $(SRC_DIR)/ft_tt_launch.c \
                   $(SRC_DIR)/ft_tt_queue.c \
                   $(SRC_DIR)/ft_tt_core_status.c \
                   $(SRC_DIR)/ft_tt_core_readiness.c

SCAFFOLD_SOURCES := $(TEST_DIR)/ft_tt_hardware_session.c \
                    $(TEST_DIR)/ft_tt_phase2_l1_layout.c \
                    $(TEST_DIR)/ft_tt_program_directory.c

# ft_tt_core_status.c LEFT for tt/ on 2026-08-24, and it is in BACKEND_SOURCES above now.
# Pantheon's arming path became a second consumer of its verdict: it must not boot firmware
# onto a core that is still running, and duplicating this classification into the serve would
# have produced a second, subtly different reading of the same registers. What stayed here is
# the PRESENTATION — the three printers and the operational advice they carry, which is
# addressed to a person at a terminal.
TOOL_SOURCES := ft_tt_core_status_print.c \
                ft_tt_core_probe.c \
                ft_tt_core_recover.c \
                ft_tt_device_reset.c \
                ft_tt_card_lock.c \
                ft_tt_core.c

SOURCES := $(BACKEND_SOURCES) $(SCAFFOLD_SOURCES) $(TOOL_SOURCES)
OBJECTS := $(addprefix $(BUILD)/,$(notdir $(SOURCES:.c=.o)))
TARGET  := $(BUILD)/ft_tt_core

# ft_tt_power_request links NOTHING from the backend, deliberately. It opens the chardev,
# issues one SET_POWER_STATE, and reads sysfs; it never aims a TLB and never issues a NoC
# transaction, so linking the backend into it would only give it the ability to do damage it
# has no reason to be able to do.
POWER_TARGET := $(BUILD)/ft_tt_power_request

.PHONY: all clean rebuild explode-programs regen-kernels regen-kernels-check regen-programs \
        regen-programs-check verify-programs check-kernel-compile-time-args \
        check-program-directory \
        check-program-provenance

all: $(TARGET) $(POWER_TARGET)

# ---------------------------------------------------------------------------
# The one guard ft_tensix_compile_time_arg() does not have built in: it indexes a
# `static const` array with NO bounds check, and SFPI GCC 15.1.0 folds an out-of-range
# read to a literal 0 with no diagnostic at any -Warray-bounds level (measured on a second card host,
# 2026-08-18 -- see the script's own header). The argument count written beside each
# kernel's documented build line is the only thing that catches a short build, so this
# makes that count a real, enforced check instead of a comment someone has to remember to
# read.
#
#   make check-kernel-compile-time-args
#
# Needs nothing -- no card, no toolchain, pure comment/source audit. Runs on the Mac.
check-kernel-compile-time-args:
	python3 check_tensix_kernel_compile_time_arg_counts.py

# ---------------------------------------------------------------------------
# IS THE SHIPPING BLOB CURRENT WITH THE KERNEL SOURCES BESIDE IT?
#
# The mtime check that used to answer this is defeated by an `explode`, which rewrites
# every file and every timestamp: all 21 directories reported `current` while `programs/gdn`
# was five commits behind and the four `rtriad_*` readers had no cooperative multicast at
# all. This asks the question by CONTENT — each embedded image carries a hash of every
# in-tree file its compile opened — so nothing about timestamps can change the answer.
#
#   make check-program-provenance
#
# Needs nothing: no card, no toolchain, no program directories. Runs on the Mac, and it is
# the check to run BEFORE trusting any on-card result, not after.
#
# **IT DOES NOT SEE tt/programs/.** It holds the EMBEDDED BLOB against the sources, which is
# the right question for a serve — an unset `PN_TT_PROGRAM_DIR` loads the blob and nothing
# else. It is the wrong question for an ARM, because an arm that sets `PN_TT_PROGRAM_DIR`
# runs the exploded directory instead, and on 2026-08-19 that directory was 22 images behind
# the blob while this target printed `22 current, 0 stale`. See check-program-directory.
PROGRAMS_HEADER ?= ../ft_tt_programs.h

check-program-provenance:
	python3 check_tensix_program_provenance.py --header $(PROGRAMS_HEADER)

# ---------------------------------------------------------------------------
# IS THE EXPLODED DIRECTORY THE SAME PROGRAM THE BLOB CARRIES?
#
# A DIFFERENT QUESTION FROM check-program-provenance, and it was answered NO while that one
# was green. Measured 2026-08-19: `tt/programs/` held 22 images differing from the embedded
# blob across SIX programs — `gdn` (all five images), `probe`, and all four `rtriad_*`. Both
# consequences were silent and large:
#
#   - with the GDN family armed, `PN_TT_PROGRAM_DIR=tt/programs` REFUSED every descriptor
#     ("gdn descriptor 2 did not complete: error", one byte of output), twice, reproducibly,
#     on a card that ran the blob clean immediately before and after;
#   - the on-disk `rtriad` reader is the PRE-COLUMN-BLOCK matmul, so every arm ever taken
#     through a program directory paid 0.181 s/token more than the blob does — the size of
#     the entire column-block win, charged to whatever that arm happened to be testing.
#
# `verify-programs` could always answer this. It built the tool and then printed the command
# for you to run yourself, so nobody ran it. This runs it, and fails on a difference.
#
#   make check-program-directory
#
# Needs a toolchain but no card.
check-program-directory: verify-programs
	$(BUILD)/verify_tensix_program_blob $(PROGRAMS)

# ---------------------------------------------------------------------------
# The ONE entry point that rebuilds every embedded Tensix kernel and regenerates
# ../ft_tt_kernels.h. There was no such entry point, and that is how the committed header
# went stale: it shipped C++ ELFs from before the Foundry move for weeks. The script also
# refuses to finish quietly when the firmware in FT_TT_FIRMWARE_DIR is not what these
# kernels were linked against -- a kernel's .text sits at its own firmware's
# __fw_export_text_end, so a regen without the matching firmware is a wedged core.
#
#   make regen-kernels                     rebuild + write the header; FAIL if firmware moved
#   make regen-kernels INSTALL_FIRMWARE=1  ... and back up + install the new firmware
#   make regen-kernels-check               rebuild + diff only; writes nothing
#
# Needs the SFPI toolchain, i.e. the card host. OUT defaults to /tmp/ftk_regen.
OUT ?= /tmp/ftk_regen
INSTALL_FIRMWARE ?= 0

regen-kernels:
	INSTALL_FIRMWARE=$(INSTALL_FIRMWARE) bash regen_tensix_kernel_header.sh $(OUT)

regen-kernels-check:
	CHECK=1 bash regen_tensix_kernel_header.sh $(OUT)

# ---------------------------------------------------------------------------
# The PROGRAM blob: whole build_tensix_program.sh directories -- five kernel ELFs plus the
# five fw_*.elf they were linked against -- embedded into ../ft_tt_programs.h, so a binary
# carries its Tensix programs instead of reading them out of somebody's home directory.
# That hardcoded path (PN_TT_PROGRAM_ROOT_DEFAULT) is why the serve runs on one machine, and
# it is why rtriad_bfp8 could go missing for a day and cost 1.085x silently.
#
#   make regen-programs         regenerate ../ft_tt_programs.h from ../programs/*
#   make regen-programs-check   diff only; writes nothing
#   make verify-programs        build the byte-for-byte comparison tool
#
# Needs the program directories, i.e. the card host. The regen has NEVER been run for real; the
# mechanism is gated against synthetic fixtures by tests/Makefile.programblob.
PROGRAMS ?= ../programs

regen-programs:
	bash regen_tensix_program_header.sh

regen-programs-check:
	CHECK=1 bash regen_tensix_program_header.sh

# Whole-program link, into its own binary and with NO shared objects: every other object in
# $(BUILD) is compiled with the NoC fence OPEN, and mixing one of those into a tool that is
# supposed to be unable to reach the card would quietly give it that ability.
VERIFY_SOURCES := $(SRC_DIR)/ft_tt_kmd.c \
                  $(SRC_DIR)/ft_tt_arch.c \
                  $(SRC_DIR)/ft_tt_arch_blackhole.c \
                  $(SRC_DIR)/ft_tt_device.c \
                  $(SRC_DIR)/ft_tt_elf.c \
                  $(SRC_DIR)/ft_tt_program_blob.c \
                  verify_tensix_program_blob.c

verify-programs: | $(BUILD)
	rm -f $(BUILD)/verify_tensix_program_blob
	$(CC) $(CFLAGS) $(VERIFY_SOURCES) -o $(BUILD)/verify_tensix_program_blob
	@echo "built $(BUILD)/verify_tensix_program_blob — run it as:"
	@echo "    $(BUILD)/verify_tensix_program_blob $(PROGRAMS)"

# The blob back out as DIRECTORIES, so a machine with no tt/programs/ can still regenerate
# it after one kernel changes. Same whole-program link and the same reason: this tool must
# not be able to reach the card.
EXPLODE_SOURCES := $(SRC_DIR)/ft_tt_kmd.c \
                   $(SRC_DIR)/ft_tt_arch.c \
                   $(SRC_DIR)/ft_tt_arch_blackhole.c \
                   $(SRC_DIR)/ft_tt_device.c \
                   $(SRC_DIR)/ft_tt_elf.c \
                   $(SRC_DIR)/ft_tt_program_blob.c \
                   explode_tensix_program_blob.c

explode-programs: | $(BUILD)
	rm -f $(BUILD)/explode_tensix_program_blob
	$(CC) $(CFLAGS) $(EXPLODE_SOURCES) -o $(BUILD)/explode_tensix_program_blob
	@echo "built $(BUILD)/explode_tensix_program_blob — run it as:"
	@echo "    $(BUILD)/explode_tensix_program_blob <outdir> --verify --recipes <file>"

$(BUILD):
	mkdir -p $(BUILD)

$(BUILD)/%.o: $(SRC_DIR)/%.c | $(BUILD)
	$(CC) $(CFLAGS) $(DEPFLAGS) -c $< -o $@

$(BUILD)/%.o: $(TEST_DIR)/%.c | $(BUILD)
	$(CC) $(CFLAGS) $(DEPFLAGS) -c $< -o $@

$(BUILD)/%.o: %.c | $(BUILD)
	$(CC) $(CFLAGS) $(DEPFLAGS) -c $< -o $@

$(TARGET): $(OBJECTS)
	$(CC) $(CFLAGS) $(OBJECTS) -o $@ -lm

$(POWER_TARGET): $(BUILD)/ft_tt_power_request.o
	$(CC) $(CFLAGS) $< -o $@

# `rebuild` deletes the tree first. GNU make 3.81 compares mtimes at one-second
# granularity, so a source edited in the same second as its object is silently not
# rebuilt and you test the PREVIOUS binary (coding-best-practices §9). rsync -a preserving
# the Mac's mtimes has the same effect across machines.
rebuild: clean all

clean:
	rm -rf $(BUILD)

-include $(wildcard $(BUILD)/*.d)
