# Linux kernel-native Makefile ("Kbuild") for libwolfssl.ko
#
# Copyright (C) 2006-2025 wolfSSL Inc.
#
# This file is part of wolfSSL.
#
# wolfSSL 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.
#
# wolfSSL 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA

.ONESHELL:
SHELL=bash

ifeq "$(KERNEL_ARCH)" "x86"
    KERNEL_ARCH_X86 := yes
else ifeq "$(KERNEL_ARCH)" "x86_64"
    KERNEL_ARCH_X86 := yes
else
    KERNEL_ARCH_X86 := no
endif

ifeq "$(WOLFSSL_OBJ_FILES)" ""
    $(error $$WOLFSSL_OBJ_FILES is unset.)
endif

ifeq "$(WOLFSSL_CFLAGS)" ""
    $(error $$WOLFSSL_CFLAGS is unset.)
endif

WOLFSSL_CFLAGS += -ffreestanding -Wframe-larger-than=$(MAX_STACK_FRAME_SIZE) -isystem $(shell $(CC) -print-file-name=include)

ifeq "$(KERNEL_ARCH)" "aarch64"
    WOLFSSL_CFLAGS += -mno-outline-atomics
else ifeq "$(KERNEL_ARCH)" "arm64"
    WOLFSSL_CFLAGS += -mno-outline-atomics
else ifeq "$(KERNEL_ARCH)" "arm"
    # avoids R_ARM_THM_JUMP11 relocations, including a stubborn tail recursion
    # optimization from wc_sp_cmp to wc_sp_cmp_mag:
    WOLFSSL_CFLAGS += -fno-optimize-sibling-calls -Os
endif

obj-m := libwolfssl.o

WOLFSSL_OBJ_TARGETS := $(patsubst %, $(obj)/%, $(WOLFSSL_OBJ_FILES))

ifeq "$(ENABLED_LINUXKM_PIE)" "yes"
    WOLFCRYPT_PIE_FILES := $(patsubst %, $(obj)/%, $(WOLFCRYPT_PIE_FILES))
endif

$(obj)/linuxkm/module_exports.o: $(WOLFSSL_OBJ_TARGETS)

ifndef KERNEL_THREAD_STACK_SIZE
    ifdef CROSS_COMPILE
        KERNEL_THREAD_STACK_SIZE=16384
    endif
endif

# this mechanism only works in kernel 5.x+ (fallback to hardcoded value)
ifndef KERNEL_THREAD_STACK_SIZE
    hostprogs := linuxkm/get_thread_size
    always-y := $(hostprogs)
endif

HOST_EXTRACFLAGS += $(NOSTDINC_FLAGS) $(LINUXINCLUDE) $(KBUILD_CFLAGS) -static -fno-omit-frame-pointer

# "-mindirect-branch=keep -mfunction-return=keep" to avoid "undefined reference
#  to `__x86_return_thunk'" on CONFIG_RETHUNK kernels (5.19.0-rc7)
ifeq "$(KERNEL_ARCH_X86)" "yes"
  HOST_EXTRACFLAGS += -mindirect-branch=keep -mfunction-return=keep
endif

# this rule is needed to get build to succeed in 4.x (get_thread_size still doesn't get built)
$(obj)/linuxkm/get_thread_size: $(src)/linuxkm/get_thread_size.c

ifndef KERNEL_THREAD_STACK_SIZE
    $(WOLFSSL_OBJ_TARGETS): | $(obj)/linuxkm/get_thread_size
    KERNEL_THREAD_STACK_SIZE=$(shell test -x $(obj)/linuxkm/get_thread_size && $(obj)/linuxkm/get_thread_size || echo 16384)
endif
MAX_STACK_FRAME_SIZE=$(shell echo $$(( $(KERNEL_THREAD_STACK_SIZE) / 4)))

libwolfssl-y := $(WOLFSSL_OBJ_FILES) linuxkm/module_hooks.o linuxkm/module_exports.o

ifeq "$(FIPS_OPTEST)" "1"
libwolfssl-y += linuxkm/optest-140-3/linuxkm_optest_wrapper.o
endif

WOLFSSL_CFLAGS_NO_VECTOR_INSNS := $(CFLAGS_SIMD_DISABLE) $(CFLAGS_FPU_DISABLE)
ifeq "$(ENABLED_ASM)" "yes"
    WOLFSSL_CFLAGS_YES_VECTOR_INSNS := $(CFLAGS_SIMD_ENABLE) $(CFLAGS_FPU_DISABLE) $(CFLAGS_AUTO_VECTORIZE_DISABLE)
else
    WOLFSSL_CFLAGS_YES_VECTOR_INSNS := $(WOLFSSL_CFLAGS_NO_VECTOR_INSNS)
endif

ccflags-y = $(WOLFSSL_CFLAGS) $(WOLFSSL_CFLAGS_NO_VECTOR_INSNS)

ifeq "$(ENABLED_LINUXKM_PIE)" "yes"
    # note, we need -fno-stack-protector to avoid references to
    # "__stack_chk_fail" from the wolfCrypt container.
    PIE_FLAGS := -fPIE -fno-stack-protector -fno-toplevel-reorder
    # the kernel sanitizers generate external references to
    # __ubsan_handle_out_of_bounds(), __ubsan_handle_shift_out_of_bounds(), etc.
    KASAN_SANITIZE := n
    UBSAN_SANITIZE := n
    ifeq "$(KERNEL_ARCH_X86)" "yes"
	PIE_FLAGS += -mcmodel=small

	# eliminate external references to __x86_return_thunk and
	# __x86_indirect_thunk_foo implementations.  _all_ references must be
	# eliminated, not just those in PIE objects, otherwise some kernels will
	# false-positively complain about unpatched thunks.
	ifeq "$(CONFIG_MITIGATION_RETPOLINE)" "y"
	    PIE_SUPPORT_FLAGS += -mfunction-return=thunk-inline
	else
	    PIE_SUPPORT_FLAGS += -mfunction-return=keep
	endif
	ifeq "$(CONFIG_MITIGATION_RETHUNK)" "y"
	    PIE_SUPPORT_FLAGS +=  -mindirect-branch=thunk-inline
	else
	    PIE_SUPPORT_FLAGS += -mindirect-branch=keep
	endif

	OBJECT_FILES_NON_STANDARD := y
    endif
    ifeq "$(KERNEL_ARCH)" "arm"
        PIE_FLAGS += -fno-unwind-tables
    endif
    ifeq "$(KERNEL_ARCH)" "mips"
        PIE_FLAGS += -mabicalls
    endif
    ccflags-y += $(PIE_SUPPORT_FLAGS)
    $(WOLFCRYPT_PIE_FILES): ccflags-y += $(PIE_FLAGS)
    $(WOLFCRYPT_PIE_FILES): ccflags-remove-y += -pg
    ifdef FORCE_GLOBAL_OBJTOOL_OFF
        undefine CONFIG_OBJTOOL
    endif
endif

ifdef KERNEL_EXTRA_CFLAGS_REMOVE
    ccflags-remove-y += $(KERNEL_EXTRA_CFLAGS_REMOVE)
endif

$(obj)/libwolfssl.mod.o: ccflags-y := $(PIE_SUPPORT_FLAGS)
$(obj)/wolfcrypt/test/test.o: ccflags-y += -DNO_MAIN_DRIVER -DWOLFSSL_NO_OPTIONS_H
$(obj)/wolfcrypt/src/aes.o: ccflags-y := $(WOLFSSL_CFLAGS) $(WOLFSSL_CFLAGS_YES_VECTOR_INSNS) $(PIE_FLAGS) $(PIE_SUPPORT_FLAGS)
$(obj)/wolfcrypt/benchmark/benchmark.o: ccflags-y := $(WOLFSSL_CFLAGS) $(CFLAGS_FPU_ENABLE) $(CFLAGS_SIMD_ENABLE) $(PIE_SUPPORT_FLAGS) -DNO_MAIN_FUNCTION -DWOLFSSL_NO_OPTIONS_H
$(obj)/wolfcrypt/benchmark/benchmark.o: asflags-y := $(WOLFSSL_ASFLAGS) $(ASFLAGS_FPU_ENABLE_SIMD_DISABLE)

asflags-y := $(WOLFSSL_ASFLAGS) $(ASFLAGS_FPUSIMD_DISABLE)

# vectorized implementations that are kernel-safe are listed here.
# these are known kernel-compatible, but need the vector instructions enabled in the assembler,
# and most of them still irritate objtool.
$(obj)/wolfcrypt/src/aes_asm.o: asflags-y := $(WOLFSSL_ASFLAGS) $(ASFLAGS_FPU_DISABLE_SIMD_ENABLE)
$(obj)/wolfcrypt/src/aes_asm.o: OBJECT_FILES_NON_STANDARD := y
$(obj)/wolfcrypt/src/aes_gcm_asm.o: asflags-y := $(WOLFSSL_ASFLAGS) $(ASFLAGS_FPU_DISABLE_SIMD_ENABLE)
$(obj)/wolfcrypt/src/aes_gcm_asm.o: OBJECT_FILES_NON_STANDARD := y
$(obj)/wolfcrypt/src/aes_xts_asm.o: asflags-y := $(WOLFSSL_ASFLAGS) $(ASFLAGS_FPU_DISABLE_SIMD_ENABLE)
$(obj)/wolfcrypt/src/aes_xts_asm.o: OBJECT_FILES_NON_STANDARD := y
$(obj)/wolfcrypt/src/sp_x86_64_asm.o: asflags-y := $(WOLFSSL_ASFLAGS) $(ASFLAGS_FPU_DISABLE_SIMD_ENABLE)
$(obj)/wolfcrypt/src/sp_x86_64_asm.o: OBJECT_FILES_NON_STANDARD := y
$(obj)/wolfcrypt/src/sha256_asm.o: asflags-y := $(WOLFSSL_ASFLAGS) $(ASFLAGS_FPU_DISABLE_SIMD_ENABLE)
$(obj)/wolfcrypt/src/sha256_asm.o: OBJECT_FILES_NON_STANDARD := y
$(obj)/wolfcrypt/src/sha512_asm.o: asflags-y := $(WOLFSSL_ASFLAGS) $(ASFLAGS_FPU_DISABLE_SIMD_ENABLE)
$(obj)/wolfcrypt/src/sha512_asm.o: OBJECT_FILES_NON_STANDARD := y
$(obj)/wolfcrypt/src/sha3_asm.o: asflags-y := $(WOLFSSL_ASFLAGS) $(ASFLAGS_FPU_DISABLE_SIMD_ENABLE)
$(obj)/wolfcrypt/src/sha3_asm.o: OBJECT_FILES_NON_STANDARD := y
$(obj)/wolfcrypt/src/chacha_asm.o: asflags-y := $(WOLFSSL_ASFLAGS) $(ASFLAGS_FPU_DISABLE_SIMD_ENABLE)
$(obj)/wolfcrypt/src/chacha_asm.o: OBJECT_FILES_NON_STANDARD := y
$(obj)/wolfcrypt/src/poly1305_asm.o: asflags-y := $(WOLFSSL_ASFLAGS) $(ASFLAGS_FPU_DISABLE_SIMD_ENABLE)
$(obj)/wolfcrypt/src/poly1305_asm.o: OBJECT_FILES_NON_STANDARD := y
$(obj)/wolfcrypt/src/wc_mlkem_asm.o: asflags-y := $(WOLFSSL_ASFLAGS) $(ASFLAGS_FPU_DISABLE_SIMD_ENABLE)
$(obj)/wolfcrypt/src/wc_mlkem_asm.o: OBJECT_FILES_NON_STANDARD := y
$(obj)/wolfcrypt/src/wc_mldsa_asm.o: asflags-y := $(WOLFSSL_ASFLAGS) $(ASFLAGS_FPU_DISABLE_SIMD_ENABLE)
$(obj)/wolfcrypt/src/wc_mldsa_asm.o: OBJECT_FILES_NON_STANDARD := y

ifndef READELF
    READELF := readelf
endif

ifeq "$(ENABLED_LINUXKM_PIE)" "yes"

ldflags-y += -T $(src)/wolfcrypt.lds

ifndef NM
    NM := nm
endif

ifndef OBJCOPY
    OBJCOPY := objcopy
endif

RENAME_PIE_TEXT_AND_DATA_SECTIONS := \
	if [[ "$(quiet)" != "silent_" ]]; then \
	    echo -n '  Checking wolfCrypt for unresolved symbols and forbidden relocations... '; \
	fi; \
	cd "$(obj)" || exit $$?; \
	$(LD) -relocatable -o wolfcrypt_test_link.o $(WOLFCRYPT_PIE_FILES) || exit $$?; \
	undefined=$$($(NM) --undefined-only wolfcrypt_test_link.o) || exit $$?; \
	GOT_relocs=$$($(READELF) --relocs --wide wolfcrypt_test_link.o | grep -E '^[^ ]+ +[^ ]+ +[^ ]*GOT[^ ]* ') || [ $$? = 1 ] || exit 2; \
	rm wolfcrypt_test_link.o; \
	if [ -n "$$undefined" ]; then \
	    echo "wolfCrypt container has unresolved symbols:" 1>&2; \
	    echo "$$undefined" 1>&2; \
	    exit 1; \
	fi; \
	if [ -n "$$GOT_relocs" ]; then \
	    echo "wolfCrypt container has GOT relocations (non-local function address used as operand?):" 1>&2; \
	    echo "$$GOT_relocs" 1>&2; \
	    exit 1; \
	fi; \
	if [[ "$(quiet)" != "silent_" ]]; then \
	    echo 'OK.'; \
	fi; \
	cd "$(obj)" || exit $$?; \
	for file in $(WOLFCRYPT_PIE_FILES); do				\
	    $(OBJCOPY) $$($(READELF) --sections --wide "$$file"	|	\
	      $(AWK) ' 							\
	      {								\
	          if (match($$0, "^ *\\[ *[0-9]+\\] +\\.(text|rodata|data|bss)(\\.[^ ]+)? ", a)) \
		  {							\
		      printf("--rename-section .%s%s=.%s_wolfcrypt ",	\
			     a[1], a[2], a[1]);				\
		  }							\
		  else if (match($$0, "^ *\\[ *[0-9]+\\] +\\.([^ ]+)\\.(text|rodata|data|bss) ", a)) \
		  {							\
		      printf("--rename-section .%s.%s=.%s_wolfcrypt ", a[1], a[2], a[2]); \
		  }							\
	      }') "$$file" || exit $$?;					\
	done; 								\
	{ $(READELF) --sections --syms --wide $(WOLFCRYPT_PIE_FILES) |	\
	$(AWK) -v obj="$(obj)" '					\
	    /^File:/ {							\
		phase = 0;						\
		delete wolfcrypt_data_sections;				\
		delete wolfcrypt_text_sections;				\
		delete other_sections;					\
		if (substr($$2, 1, length(obj)) == obj) {		\
		    curfile = substr($$2, length(obj) + 2);		\
		} else {						\
		    curfile=$$2;					\
		}							\
		next;							\
	    }								\
	    /^Section Headers:/ {					\
		phase = 1;						\
		next;							\
	    }								\
	    /^Symbol table / {						\
		phase = 2;						\
		next;							\
	    }								\
	    {								\
		if (phase == 1) {					\
		    if (match($$0, "^ *\\[ *([0-9]+)\\] +([^ ]+) ", a)) {\
			switch (a[2]) {					\
			case ".text_wolfcrypt":				\
			{						\
			    wolfcrypt_text_sections[a[1]] = a[2];	\
			    next;					\
			}						\
			case /^\.(data|rodata|bss)_wolfcrypt$$/:	\
			{						\
			    wolfcrypt_data_sections[a[1]] = a[2];	\
			    next;					\
			}						\
			default:					\
			{						\
			    other_sections[a[1]] = a[2];		\
			}						\
			}						\
			next;						\
		    }							\
		    next;						\
		}							\
		else if (phase == 2) {					\
		    if ($$4 == "FUNC") {				\
			if (! ($$7 in wolfcrypt_text_sections)) {	\
			    print curfile ": " $$4 " " $$8 " " other_sections[$$7] >"/dev/stderr"; \
			    ++warnings;					\
			}						\
			next;						\
		    }							\
		    else if ($$4 == "OBJECT") {				\
			if (! ($$7 in wolfcrypt_data_sections)) {	\
			    if ((other_sections[$$7] == ".printk_index") || \
				(($$8 ~ /^_entry\.[0-9]+$$|^kernel_read_file_str$$/) &&		\
				 (other_sections[$$7] == ".data.rel.ro.local"))) \
				next;					\
			    print curfile ": " $$4 " " $$8 " " other_sections[$$7] >"/dev/stderr"; \
			    ++warnings;					\
			}						\
			next;						\
		    }							\
		}							\
	    }								\
	    END {							\
		if (warnings) {						\
		    exit(1);						\
		} else {						\
		    exit(0);						\
	    }}'; } || \
	{ echo 'Error: symbol(s) missed by containerization.' >&2; exit 1; }; \
	if [[ "$(quiet)" != "silent_" ]]; then \
	    echo '  wolfCrypt .{text,data,rodata,bss} sections containerized to .{text,data,rodata}_wolfcrypt'; \
	fi
endif

# auto-generate the exported symbol list, leveraging the WOLFSSL_API visibility tags.
# exclude symbols that don't match wc_* or wolf*.
EXPORT_SYMBOL := EXPORT_SYMBOL_NS_GPL
$(obj)/linuxkm/module_exports.c: $(src)/module_exports.c.template $(WOLFSSL_OBJ_TARGETS) $(obj)/linuxkm/module_hooks.o
	@$(RENAME_PIE_TEXT_AND_DATA_SECTIONS)
	@cp $< $@ || exit $$?
	if [[ "$${VERSION}" -gt 6 || ("$${VERSION}" -eq 6 && "$${PATCHLEVEL}" -ge 13) ]]; then
	    # use ASCII octal escape to avoid syntax disruption in the awk script.
	    ns='\042WOLFSSL\042'
	else
	    ns='WOLFSSL'
	fi
	$(READELF) --symbols --wide $(filter %.o,$^) |
		$(AWK) '/^ *[0-9]+: / {
		  if ($$8 !~ /^(wc_|wolf|WOLF|TLSX_)/){next;}
		  if (($$4 == "FUNC") && ($$5 == "GLOBAL") && ($$6 == "DEFAULT")) {
		    print "$(EXPORT_SYMBOL)(" $$8 ", '"$$ns"');";
		  }
		}' >> $@ || exit $$?
	echo -e "#ifndef NO_CRYPT_TEST\n$(EXPORT_SYMBOL)(wolfcrypt_test, $${ns});\n#endif" >> $@

clean-files := linuxkm src wolfcrypt
