# SPDX-License-Identifier: GPL-2.0
#
# Makefile for PEAK-System LIN interfaces driver
#
# Copyright (C) 2001-2025 PEAK System-Technik GmbH <www.peak-system.com>
#
# This program 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 2 of the License, or
# (at your option) any later version.
#
# This program 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-1301 USA.
#
# Contact: <linux.peak@hms-networks.com>
# Author:  Stephane Grosjean <stephane.grosjean@hms-networks.com>
#

ifneq ($(KERNELRELEASE),)
INC_KBUILD=y
ifeq ($(DKMS_KERNEL_DIR),)
INC_KBUILD=only
endif
endif

ifneq ($(INC_KBUILD),)
include Kbuild
endif

ifneq ($(INC_KBUILD),only)

# Out-of-treee version:
# - drivers to build:
DRV_NAME = plin
DRV_NAME_UPPER := $(shell echo $(DRV_NAME) | tr a-z A-Z)

# - define here where is the Kernel to compile the module for, or let it empty
#   to (try to) automatically detect current one
KERNEL_DIR = $(DKMS_KERNEL_DIR)

# - define here a toolchain path, for cross-compilation and check CROSS_COMPILE
#   variable definition below
TOOLCHAIN_DIR =

# default is: USB interfaces support is included
USB = yes

# Check not-empty kernel dir if it really does exist
ifneq ($(KERNEL_DIR),)

ifneq ($(shell if test -d "$(KERNEL_DIR)"; then echo true; fi),true)
$(error Directory $(KERNEL_DIR) doesn't exist: can't build the $(DRV_NAME) module)
endif

else

define KERNEL_LOCATION_1
$(shell UR=`uname -r`; for d in \
	/usr/src/linux-headers-$$UR \
	/usr/src/linux-$$UR \
	/lib/modules/$$UR/build \
	/usr/src/linux \
	; do if [ -d $$d ]; then echo $$d; break; elif [ -h $$d ]; then readlink -f $$d; break; fi; done)
endef

ifeq ($(KERNEL_LOCATION_1),)
$(error Unable to automatically find the Kernel headers. These are mandatory to build the $(DRV_NAME) driver. Please set the KERNEL_DIR variable to the root directory of your own built Kernel)
else
KERNEL_DIR = $(KERNEL_LOCATION_1)
endif # KERNEL_LOCATION_1

endif # KERNEL_DIR

ifeq ($(KERNEL_VER),)
# Kernel version not defined: try to detect by ourselves, from the given
# KERNEL_DIR
# here are the list of file were UTS_RELEASE has been defined
define VERSION_FILES
	$(KERNEL_DIR)/include/generated/utsrelease.h
	$(KERNEL_DIR)/include/linux/utsrelease.h
endef
print-file-if-exists = $(shell if [ -f $(1) ]; then echo $(1); fi)
VERSION_FILE := $(foreach f,$(VERSION_FILES),$(call print-file-if-exists,$f))

KERNEL_VER := $(shell cpp -E -dM -I$(KERNEL_DIR)/include $(VERSION_FILE) | \
			  grep UTS_RELEASE | sed -e 's;[^"]*"\(.*\)";\1;g')
endif

ifneq ($(TOOLCHAIN_DIR),)
# Target (example)
export ARCH = arm
export CROSS_COMPILE = $(TOOLCHAIN_DIR)/$(ARCH)-linux-
endif

#DRV_CFLAGS=-D'DRV_NAME=\"$(DRV_NAME)\"'

ifeq ($(USE_SPARSE),y)
# Need 'sparse' tool (apt-get install sparse)
MAKE_OPTS += C=1 CF=-D__CHECK_ENDIAN__
endif

PWD := $(shell pwd)

PLIN_VERSION_FILE = $(DRV_NAME)_version.h

OBJS = $(DRV_NAME)_main.o $(DRV_NAME)_sysfs.o $(DRV_NAME)_chrdev.o
ifeq ($(USB),yes)
OBJS += $(DRV_NAME)_usb.o
else
DRV_CFLAGS=-DNO_USB
endif

# Linux does this
CC = $(CROSS_COMPILE)gcc

CC_VERSION = $(shell $(CC) -dumpversion)
CC_ARCH = $(shell $(CC) -dumpmachine)

#
# Get Linux OS release
#
-include $(DESTDIR)/etc/os-release
OS_RELEASE_ID = $(NAME)
ifeq ($(OS_RELEASE_ID),)
OS_RELEASE_ID = UNKNOWN_LINUX
endif
OS_RELEASE_VER = $(VERSION_ID)
ifeq ($(OS_RELEASE_VER),)
OS_RELEASE_VER = X.Y.Z
endif

# plin driver version
define plin_ver_string
$(shell awk '
($$1 == "#define" && ($$2 == "DRV_VER_MAJOR" || $$2 == "DRV_VER_MINOR")) {
	printf("%d.", $$3)
}
($$1 == "#define" && $$2 == "DRV_VER_SUBMINOR") {
	printf("%d", $$3)
}
($$1 == "#define" && $$2 == "DRV_VER_RC") {
	printf("-rc%d", $$3)
}
' $(PLIN_VERSION_FILE))
endef

ifneq ($(DKMS),no)
# dkms stuff
# Note: if dkms not installed then driver is manually installed under misc dir
DKMS_BIN = $(shell which dkms)

#DKMS_STDERR = /dev/null
DKMS_CONF = dkms.conf
DKMS_DRV = peak-lin-driver
DKMS_VER = 1.4.1
DKMS_STDERR = /tmp/$(DKMS_DRV)-$(DKMS_VER)-dkms.stderr
DKMS_TREE = /var/lib/dkms
endif

# DKMS module locations:
# $ find -L /var/lib/dkms -name plin.ko
# /var/lib/dkms/peak-lin-driver/1.0.0/source/plin.ko
# /var/lib/dkms/peak-lin-driver/1.0.0/5.13.0-21-generic/x86_64/module/plin.ko
# /var/lib/dkms/peak-lin-driver/kernel-5.13.0-21-generic-x86_64/module/plin.ko
# $ find /lib/modules/`uname -r`  -name plin.ko
# /lib/modules/5.13.0-21-generic/updates/dkms/plin.ko

# legacy install:
# MUST exist even if DKMS_BIN != ""
ifneq ($(DESTDIR),)
DEPMOD_OPTS = --basedir $(DESTDIR)
ifneq ($(KERNEL_VER),)
DEPMOD_OPTS += $(KERNEL_VER)
endif
endif

# used when dkms failed by legacy install
DEPMOD_BIN = depmod

# where the driver module file should be manually installed
INSTALL_ROOT = $(DESTDIR)/lib/modules/$(KERNEL_VER)
INSTALL_DIR = $(INSTALL_ROOT)/misc
INSTALL_TARGETS += $(DRV_NAME).ko

# Simulate in-kernel make
export CONFIG_PLIN=m

# needed since 6.15
ccflags-y := $(EXTRA_CFLAGS)

.PHONY: $(DKMS_CONF)

all:: $(DKMS_CONF) Kbuild
	@echo
	@echo "Running $(OS_RELEASE_ID) $(OS_RELEASE_VER),"
	@echo "building driver module $(DRV_NAME).ko from:"
	@echo "$(KERNEL_DIR) (Linux v$(KERNEL_VER)),"
	@echo "using $(CC_ARCH)-$(CC) version $(CC_VERSION)"
ifneq ($(TOOLCHAIN_DIR),)
	@echo "TOOLCHAIN_DIR=$(TOOLCHAIN_DIR)"
endif
	@echo
	$(MAKE) -C $(KERNEL_DIR) $(MAKE_OPTS) EXTRA_CFLAGS="$(DRV_CFLAGS)" M=$(PWD) modules

Kbuild:
	@echo "#" > $@
	@echo "# Makefile for the PEAK-System LIN driver module" >> $@
	@echo "#" >> $@
	@echo "obj-\$$(CONFIG_$(DRV_NAME_UPPER)) += $(DRV_NAME).o" >> $@
	@echo "$(DRV_NAME)-y = $(OBJS)" >> $@

.PHONY: debug-version debug_version
debug-version: debug_version
debug_version:
	@echo
	@echo "Building debug version of the driver:"
	@echo
	$(MAKE) DRV_CFLAGS=-DDEBUG

.PHONY: with-traces with_traces
with-traces: with_traces
with_traces:
	@echo
	@echo "Building driver with traces:"
	@echo
	$(MAKE) DRV_CFLAGS=-DDEBUG_TRACE

.PHONY: with-chrdev-traces with_chrdev_traces
with-chrdev-traces: with_chrdev_traces
with_chrdev_traces:
	@echo
	@echo "Building driver with chrdev traces (only):"
	@echo
	$(MAKE) DRV_CFLAGS=-DDEBUG_CHRDEV_TRACE

.PHONY: with-usb-traces with_usb_traces
with-usb-traces: with_usb_traces
with_usb_traces:
	@echo
	@echo "Building driver with usb traces (only):"
	@echo
	$(MAKE) DRV_CFLAGS=-DDEBUG_USB_TRACE

# Need root privileges
.PHONY: pre_install install_driver install_api install_udev

ifeq ($(KERNEL_VER),)
install:
	@echo "Installation failed: not able to find kernel version from Kernel directory $(KERNEL_DIR)."
	exit 2

unsinstall:
	@echo "Uninstallation failed: not able to find kernel version from Kernel directory $(KERNEL_DIR)."
	exit 2
else

install: pre_install install_driver install_api install_udev
pre_install:
	@echo
	@echo "Installing driver module $(DRV_NAME).ko for linux $(KERNEL_VER):"
	@echo
	@-rmmod $(DRV_NAME) 2> /dev/null || true
# 	@-rm -f $(INSTALL_DIR)/$(DRV_NAME).ko

ifneq ($(DKMS_BIN),)

$(DKMS_CONF):
	@echo "PACKAGE_NAME=\"$(DKMS_DRV)\"" > $(DKMS_CONF)
	@echo "PACKAGE_VERSION=\"$(DKMS_VER)\"" >> $(DKMS_CONF)
	@echo "CLEAN=\"make clean\"" >> $(DKMS_CONF)
ifeq ($(DRV_CFLAGS),)
	@echo "MAKE[0]=\"make DKMS_KERNEL_DIR=\$$kernel_source_dir $(MAKE_OPTS)\"" >> $(DKMS_CONF)
else
	@echo "MAKE[0]=\"make DKMS_KERNEL_DIR=\$$kernel_source_dir $(MAKE_OPTS) DRV_CFLAGS=$(DRV_CFLAGS)\"" >> $(DKMS_CONF)
endif
#	@echo "STRIP[0]=\"no\"" >> $(DKMS_CONF)
	@echo "BUILT_MODULE_NAME[0]=\"$(DRV_NAME)\"" >> $(DKMS_CONF)
	@echo "BUILT_MODULE_LOCATION[0]=\".\"" >> $(DKMS_CONF)
	@echo "DEST_MODULE_LOCATION[0]=\"/updates\"" >> $(DKMS_CONF)
	@echo "AUTOINSTALL=\"yes\"" >> $(DKMS_CONF)

install_driver: $(INSTALL_TARGETS)
# MUST exist before dkms add
	[ -h $(DESTDIR)/usr/src/$(DKMS_DRV)-$(DKMS_VER) ] || \
		ln -s $(PWD) $(DESTDIR)/usr/src/$(DKMS_DRV)-$(DKMS_VER)

# Add driver (if it isn't)
	[ -d $(DESTDIR)$(DKMS_TREE)/$(DKMS_DRV)/$(DKMS_VER) ] || \
		$(DKMS_BIN) add $(DKMS_DRV)/$(DKMS_VER)

# Finally, install it
	$(DKMS_BIN) install --force $(DKMS_DRV)/$(DKMS_VER) -k $(KERNEL_VER)

dkms_status:
	@echo "Getting dkms status for $(DKMS_DRV) $(DKMS_VER) for $(KERNEL_VER):" && echo
	@$(DKMS_BIN) status -m $(DKMS_DRV) -v $(DKMS_VER) -k $(KERNEL_VER) 2> $(DKMS_STDERR) | grep -q -e "^$(DKMS_DRV), $(DKMS_VER), $(KERNEL_VER), .*: installed$$" && [ $$? -eq 0 ] && echo "Ok" || echo "NOk"

else

install_driver: $(INSTALL_TARGETS)
	mkdir -p $(INSTALL_DIR)
	cp -f $(DRV_NAME).ko $(INSTALL_DIR)
	echo "- Building dependencies..."
	$(DEPMOD_BIN) $(DEPMOD_OPTS)

dkms_status:
	@echo
	@echo "dkms binary not found on this system. Is dkms installed?"
	@echo
endif

install_api:
	@echo "- Installing driver API header file under $(DESTDIR)/usr/include..."
	mkdir -p $(DESTDIR)/usr/include
	cp -f plin.h $(DESTDIR)/usr/include

ifneq ($(UDEV),no)
UDEV_RULES_DIR=$(DESTDIR)/etc/udev/rules.d
UDEV_RULES=45-$(DRV_NAME).rules

# Default Udev rules (no symlink)
$(UDEV_RULES):
	@echo "ACTION==\"add\", KERNEL==\"$(DRV_NAME)*\", MODE=\"0666\"" > $(UDEV_RULES)

install_udev: $(UDEV_RULES)
	cp -f $(UDEV_RULES) $(UDEV_RULES_DIR)
	@echo "- Reloading Udev rules..."
	udevadm control --reload
else
install_udev:
endif

.PHONY: uninstall uninstall_driver uninstall_udev uninstall_api
uninstall: uninstall_api uninstall_udev uninstall_driver

uninstall_api:
	-rm -f $(DESTDIR)/usr/include/plin.h

uninstall_udev:
ifneq ($(UDEV_RULES),)
	-@rm -f $(UDEV_RULES_DIR)/$(UDEV_RULES)
endif

uninstall_driver:
	@echo
	@echo "Removing driver module $(DRV_NAME).ko from linux $(KERNEL_VER):"
	@echo
	-rmmod $(DRV_NAME) 2> /dev/null || true
	if [ -f $(INSTALL_DIR)/$(DRV_NAME).ko ]; then\
		rm -f $(INSTALL_DIR)/$(DRV_NAME).ko;\
		$(DEPMOD_BIN) $(DEPMOD_OPTS);\
	fi
ifneq ($(DKMS_BIN),)
	$(DKMS_BIN) remove $(DKMS_DRV)/$(DKMS_VER) --all
	-rm -f $(DESTDIR)/usr/src/$(DKMS_DRV)-$(DKMS_VER)
# Note: /var/lib/dkms/$(DRV_NAME) should no more exist
	-rm -rf $(DESTDIR)/var/lib/dkms/$(DKMS_DRV)
	-rm -f $(DKMS_STDERR)
endif

endif # KERNEL_VER==""

.PHONY: clean clean_tmp
clean: clean_tmp
	-rm -f *.ko

clean_tmp:
	-rm -rf *.o *.mod.c .*.*.cmd *.mod *.dwo .*.o
	-rm -f modules.* Module.*
	-rm -rf .tmp_versions
	-rm -f Kbuild

.PHONY: help
help:
	@echo
	@echo "Makefile help"
	@echo
	@echo "Target               Description"
	@echo
	@echo "<none>|all           Build the module against a linux kernel version:"
	@echo "                     - current kernel: $(KERNEL_VER)"
	@echo "                     - headers used: $(KERNEL_DIR)"
	@echo "help                 Display this help"
	@echo "install              Install the driver and all its components. If dkms is"
	@echo "                     installed then the driver is added to the modules list it"
	@echo "                     handles."
	@echo "uninstall            Uninstall the driver and all its components"
	@echo
	@echo "with-chrdev-traces"
	@echo "with-usb-traces"
	@echo "with-traces          Build the driver with (chrdev/usb/all) traces"
	@echo "debug-version        Build the driver with full debug messages"
	@echo
endif # INC_KBUILD!==only
