diff --git a/CMakeLists.txt b/CMakeLists.txt index cd38975..06d3bc5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,13 +9,16 @@ project( LANGUAGES C ASM_MASM VERSION ${VERSION}) +# CMake modules +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") + if(MSVC) set(LIBCPUID_SHARED OFF) else() set(LIBCPUID_SHARED ON) endif() option(BUILD_SHARED_LIBS "Build shared lib" ${LIBCPUID_SHARED}) - +option(LIBCPUID_DRIVERS "Enable kernel drivers" ON) option(LIBCPUID_TESTS "Enable building tests" OFF) set(CMAKE_CXX_STANDARD 11) @@ -47,8 +50,6 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") endif() # Global variables -list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") - if(UNIX) include(GNUInstallDirs) set(prefix "${CMAKE_INSTALL_PREFIX}") @@ -64,6 +65,9 @@ endif(UNIX) # Include subdirectories add_subdirectory(libcpuid) add_subdirectory(cpuid_tool) +if(LIBCPUID_DRIVERS) + add_subdirectory(drivers) +endif() if(LIBCPUID_TESTS) add_subdirectory(tests) endif() diff --git a/cmake/FindLinuxKernelHeaders.cmake b/cmake/FindLinuxKernelHeaders.cmake new file mode 100644 index 0000000..4ce1586 --- /dev/null +++ b/cmake/FindLinuxKernelHeaders.cmake @@ -0,0 +1,24 @@ +# Find the kernel release +execute_process( + COMMAND uname -r + OUTPUT_VARIABLE KERNEL_RELEASE + OUTPUT_STRIP_TRAILING_WHITESPACE +) +message(STATUS "Kernel release: ${KERNEL_RELEASE}") + +# Find the headers +set(KERNELHEADERS_DIR "/usr/lib/modules/${KERNEL_RELEASE}/build") +if(IS_DIRECTORY "${KERNELHEADERS_DIR}") + message(STATUS "Kernel headers: ${KERNELHEADERS_DIR}") + file(GLOB KERNELHEADERS_ARCH_INCLUDE_DIR LIST_DIRECTORIES true "${KERNELHEADERS_DIR}/arch/*/include") + set(KERNELHEADERS_INCLUDE_DIRS + "${KERNELHEADERS_DIR}/include" + "${KERNELHEADERS_ARCH_INCLUDE_DIR}" + CACHE PATH "Kernel headers include dirs" + ) + set(KERNELHEADERS_FOUND 1 CACHE STRING "Set to 1 if kernel headers were found") +else() + set(KERNELHEADERS_FOUND 0 CACHE STRING "Set to 1 if kernel headers were found") +endif() + +mark_as_advanced(KERNELHEADERS_FOUND) diff --git a/drivers/.gitignore b/drivers/.gitignore new file mode 100644 index 0000000..4c53661 --- /dev/null +++ b/drivers/.gitignore @@ -0,0 +1,8 @@ +!*/*/Makefile.in +*.cmd +*.o.d +*.ko +*.mod +*.mod.c +Module.symvers +modules.order diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt new file mode 100644 index 0000000..42c0dd1 --- /dev/null +++ b/drivers/CMakeLists.txt @@ -0,0 +1,4 @@ +# Include "arm" directory only for ARM CPUs +if(("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "^armv.*") OR ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "aarch64")) + add_subdirectory(arm) +endif() diff --git a/drivers/arm/CMakeLists.txt b/drivers/arm/CMakeLists.txt new file mode 100644 index 0000000..8a819db --- /dev/null +++ b/drivers/arm/CMakeLists.txt @@ -0,0 +1,8 @@ +option(LIBCPUID_DRIVER_DEBUG "Debug kernel module" OFF) +if(LIBCPUID_DRIVER_DEBUG) + add_definitions(-DLIBCPUID_DRIVER_DEBUG) +endif(LIBCPUID_DRIVER_DEBUG) + +if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") + add_subdirectory(linux) +endif() diff --git a/drivers/arm/linux/CMakeLists.txt b/drivers/arm/linux/CMakeLists.txt new file mode 100644 index 0000000..876702c --- /dev/null +++ b/drivers/arm/linux/CMakeLists.txt @@ -0,0 +1,38 @@ +set(DRIVER_NAME "cpuid") +option(LIBCPUID_DRIVER_ARM_LINUX_DKMS "Use DKMS for CPUID kernel module for ARM" ON) + +if(LIBCPUID_DRIVER_ARM_LINUX_DKMS) + message(STATUS "Deploying DKMS configuration for CPUID kernel module...") + set(LIBCPUID_SRC_DIR "/usr/src/${CMAKE_PROJECT_NAME}-${PROJECT_VERSION}") + + configure_file(dkms.conf.in "${CMAKE_CURRENT_BINARY_DIR}/dkms.conf") + configure_file(Makefile.in "${CMAKE_CURRENT_BINARY_DIR}/Makefile_dkms") + install(FILES + cpuid.c + "${CMAKE_SOURCE_DIR}/libcpuid/libcpuid_arm_driver.h" + "${CMAKE_CURRENT_BINARY_DIR}/dkms.conf" + DESTINATION "${LIBCPUID_SRC_DIR}/" + ) + install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/Makefile_dkms" + DESTINATION "${LIBCPUID_SRC_DIR}" + RENAME "Makefile" + ) +else(LIBCPUID_DRIVER_ARM_LINUX_DKMS) + find_package(LinuxKernelHeaders REQUIRED) + + set(DRIVER_SOURCE "${DRIVER_NAME}.c") + set(DRIVER_OBJECT "${DRIVER_NAME}.o") + set(DRIVER_MODULE "${DRIVER_NAME}.ko") + set(KBUILD_CMD "${CMAKE_MAKE_PROGRAM}" EXTRA_CFLAGS="-I${CMAKE_SOURCE_DIR}/libcpuid" -C "${KERNELHEADERS_DIR}" "src=${CMAKE_CURRENT_SOURCE_DIR}" "M=${CMAKE_CURRENT_BINARY_DIR}") + + add_custom_command(OUTPUT "${DRIVER_MODULE}" + COMMAND ${KBUILD_CMD} modules + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + DEPENDS "${DRIVER_SOURCE}" + COMMENT "Building CPUID kernel module for ARM..." + VERBATIM + ) + + add_custom_target(driver-arm-cpuid ALL DEPENDS "${DRIVER_MODULE}") +endif(LIBCPUID_DRIVER_ARM_LINUX_DKMS) diff --git a/drivers/arm/linux/Makefile.in b/drivers/arm/linux/Makefile.in new file mode 100644 index 0000000..d84b15c --- /dev/null +++ b/drivers/arm/linux/Makefile.in @@ -0,0 +1,9 @@ +obj-m += @DRIVER_NAME@.o + +all: modules + +modules: + @$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules + +clean: + @$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean diff --git a/drivers/arm/linux/cpuid.c b/drivers/arm/linux/cpuid.c new file mode 100644 index 0000000..80036cb --- /dev/null +++ b/drivers/arm/linux/cpuid.c @@ -0,0 +1,266 @@ +/* + * Copyright 2024 Veselin Georgiev, + * anrieffNOSPAM @ mgail_DOT.com (convert to gmail) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* This kernel module is inpired on msr: https://github.com/torvalds/linux/blob/master/arch/x86/kernel/msr.c */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libcpuid_arm_driver.h" + + +#ifdef LIBCPUID_DRIVER_DEBUG +# define DPRINTF(format, ...) pr_info(format, __VA_ARGS__) +#else +# define DPRINTF(...) +#endif + +#if defined(__arm__) +# define cpuid_read_sysreg(aarch32, aarch64, value) asm volatile("mrc " aarch32 : "=r" (value)) +#elif defined(__aarch64__) +# define cpuid_read_sysreg(aarch32, aarch64, value) asm volatile("mrs %0, " aarch64 : "=r" (value)) +#else +# error This platform is not supported by this kernel module +#endif + +static enum cpuhp_state cpuhp_cpuid_state; + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0)) +static char *cpuid_devnode(struct device *dev, umode_t *mode) +#else +static char *cpuid_devnode(const struct device *dev, umode_t *mode) +#endif /* Linux 6.2 */ +{ + return kasprintf(GFP_KERNEL, "cpu/%u/cpuid", MINOR(dev->devt)); +} + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)) +static struct class *cpuid_class; +#else +static const struct class cpuid_class = { + .name = "cpuid", + .devnode = cpuid_devnode, +}; +#endif /* Linux 6.6 */ + +static void __read_reg_on_cpu(void *info) +{ + struct read_reg_t *read_reg = info; + + DPRINTF("[cpuid,%d]: operating on request %d\n", __LINE__, read_reg->request); + read_reg->value_64b = 0; + switch(read_reg->request) + { + case REQ_MIDR: cpuid_read_sysreg("p15, 0, %0, c0, c0, 0", AARCH64_REG_MIDR_EL1, read_reg->value_64b); break; + case REQ_MPIDR: cpuid_read_sysreg("p15, 0, %0, c0, c0, 5", AARCH64_REG_MPIDR_EL1, read_reg->value_64b); break; + case REQ_REVIDR: cpuid_read_sysreg("p15, 0, %0, c0, c0, 6", AARCH64_REG_REVIDR_EL1, read_reg->value_64b); break; + case REQ_ID_AFR0: cpuid_read_sysreg("p15, 0, %0, c0, c1, 3", AARCH64_REG_ID_AFR0, read_reg->value_32b); break; + case REQ_ID_DFR0: cpuid_read_sysreg("p15, 0, %0, c0, c1, 2", AARCH64_REG_ID_DFR0, read_reg->value_32b); break; + case REQ_ID_DFR1: cpuid_read_sysreg("p15, 0, %0, c0, c3, 5", AARCH64_REG_ID_DFR1, read_reg->value_32b); break; + case REQ_ID_ISAR0: cpuid_read_sysreg("p15, 0, %0, c0, c2, 0", AARCH64_REG_ID_ISAR0, read_reg->value_32b); break; + case REQ_ID_ISAR1: cpuid_read_sysreg("p15, 0, %0, c0, c2, 1", AARCH64_REG_ID_ISAR1, read_reg->value_32b); break; + case REQ_ID_ISAR2: cpuid_read_sysreg("p15, 0, %0, c0, c2, 2", AARCH64_REG_ID_ISAR2, read_reg->value_32b); break; + case REQ_ID_ISAR3: cpuid_read_sysreg("p15, 0, %0, c0, c2, 3", AARCH64_REG_ID_ISAR3, read_reg->value_32b); break; + case REQ_ID_ISAR4: cpuid_read_sysreg("p15, 0, %0, c0, c2, 4", AARCH64_REG_ID_ISAR4, read_reg->value_32b); break; + case REQ_ID_ISAR5: cpuid_read_sysreg("p15, 0, %0, c0, c2, 5", AARCH64_REG_ID_ISAR5, read_reg->value_32b); break; + case REQ_ID_ISAR6: cpuid_read_sysreg("p15, 0, %0, c0, c2, 7", AARCH64_REG_ID_ISAR6, read_reg->value_32b); break; + case REQ_ID_MMFR0: cpuid_read_sysreg("p15, 0, %0, c0, c1, 4", AARCH64_REG_ID_MMFR0, read_reg->value_32b); break; + case REQ_ID_MMFR1: cpuid_read_sysreg("p15, 0, %0, c0, c1, 5", AARCH64_REG_ID_MMFR1, read_reg->value_32b); break; + case REQ_ID_MMFR2: cpuid_read_sysreg("p15, 0, %0, c0, c1, 6", AARCH64_REG_ID_MMFR2, read_reg->value_32b); break; + case REQ_ID_MMFR3: cpuid_read_sysreg("p15, 0, %0, c0, c1, 7", AARCH64_REG_ID_MMFR3, read_reg->value_32b); break; + case REQ_ID_MMFR4: cpuid_read_sysreg("p15, 0, %0, c0, c2, 6", AARCH64_REG_ID_MMFR4, read_reg->value_32b); break; + case REQ_ID_MMFR5: cpuid_read_sysreg("p15, 0, %0, c0, c3, 6", AARCH64_REG_ID_MMFR5, read_reg->value_32b); break; + case REQ_ID_PFR0: cpuid_read_sysreg("p15, 0, %0, c0, c1, 0", AARCH64_REG_ID_PFR0, read_reg->value_32b); break; + case REQ_ID_PFR1: cpuid_read_sysreg("p15, 0, %0, c0, c1, 1", AARCH64_REG_ID_PFR1, read_reg->value_32b); break; + case REQ_ID_PFR2: cpuid_read_sysreg("p15, 0, %0, c0, c3, 4", AARCH64_REG_ID_PFR2, read_reg->value_32b); break; +#if defined(__aarch64__) + case REQ_ID_AA64AFR0: cpuid_read_sysreg(NULL, AARCH64_REG_ID_AA64AFR0_EL1, read_reg->value_64b); break; + case REQ_ID_AA64AFR1: cpuid_read_sysreg(NULL, AARCH64_REG_ID_AA64AFR1_EL1, read_reg->value_64b); break; + case REQ_ID_AA64DFR0: cpuid_read_sysreg(NULL, AARCH64_REG_ID_AA64DFR0_EL1, read_reg->value_64b); break; + case REQ_ID_AA64DFR1: cpuid_read_sysreg(NULL, AARCH64_REG_ID_AA64DFR1_EL1, read_reg->value_64b); break; + case REQ_ID_AA64ISAR0: cpuid_read_sysreg(NULL, AARCH64_REG_ID_AA64ISAR0_EL1, read_reg->value_64b); break; + case REQ_ID_AA64ISAR1: cpuid_read_sysreg(NULL, AARCH64_REG_ID_AA64ISAR1_EL1, read_reg->value_64b); break; + case REQ_ID_AA64ISAR2: cpuid_read_sysreg(NULL, AARCH64_REG_ID_AA64ISAR2_EL1, read_reg->value_64b); break; + case REQ_ID_AA64MMFR0: cpuid_read_sysreg(NULL, AARCH64_REG_ID_AA64MMFR0_EL1, read_reg->value_64b); break; + case REQ_ID_AA64MMFR1: cpuid_read_sysreg(NULL, AARCH64_REG_ID_AA64MMFR1_EL1, read_reg->value_64b); break; + case REQ_ID_AA64MMFR2: cpuid_read_sysreg(NULL, AARCH64_REG_ID_AA64MMFR2_EL1, read_reg->value_64b); break; + case REQ_ID_AA64MMFR3: cpuid_read_sysreg(NULL, AARCH64_REG_ID_AA64MMFR3_EL1, read_reg->value_64b); break; + case REQ_ID_AA64MMFR4: cpuid_read_sysreg(NULL, AARCH64_REG_ID_AA64MMFR4_EL1, read_reg->value_64b); break; + case REQ_ID_AA64PFR0: cpuid_read_sysreg(NULL, AARCH64_REG_ID_AA64PFR0_EL1, read_reg->value_64b); break; + case REQ_ID_AA64PFR1: cpuid_read_sysreg(NULL, AARCH64_REG_ID_AA64PFR1_EL1, read_reg->value_64b); break; + case REQ_ID_AA64PFR2: cpuid_read_sysreg(NULL, AARCH64_REG_ID_AA64PFR2_EL1, read_reg->value_64b); break; + case REQ_ID_AA64SMFR0: cpuid_read_sysreg(NULL, AARCH64_REG_ID_AA64SMFR0_EL1, read_reg->value_64b); break; + case REQ_ID_AA64ZFR0: cpuid_read_sysreg(NULL, AARCH64_REG_ID_AA64ZFR0_EL1, read_reg->value_64b); break; +#endif /* __aarch64__ */ + default: + read_reg->request = REQ_INVALID; + pr_warn("[cpuid,%d]: unknown operation requested: %d", __LINE__, read_reg->request); + break; + } + + DPRINTF("[cpuid,%d]: set value 0x%016llX\n", __LINE__, read_reg->value_64b); +} + +static long cpuid_ioctl(struct file *file, unsigned int ioc, unsigned long arg) +{ + u32 __user *uregs = (u32 __user *)arg; + int err = 0; + struct read_reg_t read_reg; + const int cpu = iminor(file_inode(file)); + + DPRINTF("[cpuid,%d]: received ioctl %u for cpu number %d\n", __LINE__, ioc, cpu); + switch (ioc) { + case ARM_IOC_READ_REG: + if (!(file->f_mode & FMODE_READ)) { + err = -EBADF; + break; + } + if (copy_from_user(&read_reg, uregs, sizeof(read_reg))) { + err = -EFAULT; + break; + } + err = smp_call_function_single(cpu, __read_reg_on_cpu, &read_reg, 1); + if (err) + break; + if (copy_to_user(uregs, &read_reg, sizeof(read_reg))) + err = -EFAULT; + break; + + default: + err = -ENOTTY; + break; + } + + return err; +} + +/* + * File operations we support + */ +static const struct file_operations cpuid_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = cpuid_ioctl, + .compat_ioctl = cpuid_ioctl, +}; + +static int cpuid_device_create(unsigned int cpu) +{ + struct device *dev; + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)) + dev = device_create(cpuid_class, NULL, MKDEV(CPUID_MAJOR, cpu), NULL, "cpuid%d", cpu); +#else + dev = device_create(&cpuid_class, NULL, MKDEV(CPUID_MAJOR, cpu), NULL, "cpuid%d", cpu); +#endif /* Linux 6.6 */ + + return PTR_ERR_OR_ZERO(dev); +} + +static int cpuid_device_destroy(unsigned int cpu) +{ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)) + device_destroy(cpuid_class, MKDEV(CPUID_MAJOR, cpu)); +#else + device_destroy(&cpuid_class, MKDEV(CPUID_MAJOR, cpu)); +#endif + return 0; +} + +static int __init cpuid_init(void) +{ + int err; + + DPRINTF("[cpuid,%d]: load module\n", __LINE__); + if (__register_chrdev(CPUID_MAJOR, 0, NR_CPUS, "cpu/cpuid", &cpuid_fops)) { + pr_err("unable to get major %d for cpuid\n", CPUID_MAJOR); + return -EBUSY; + } +#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)) +# if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0)) + cpuid_class = class_create(THIS_MODULE, "cpuid"); +# else + cpuid_class = class_create("cpuid"); +# endif /* Linux 6.4 */ + if (IS_ERR(cpuid_class)) { + err = PTR_ERR(cpuid_class); + goto out_chrdev; + } + cpuid_class->devnode = cpuid_devnode; +#else + err = class_register(&cpuid_class); + if (err) + goto out_chrdev; +#endif /* Linux 6.6 */ + +#if defined(__aarch64__) + err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "arm64/cpuid:online", cpuid_device_create, cpuid_device_destroy); +#else + err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "arm/cpuid:online", cpuid_device_create, cpuid_device_destroy); +#endif + if (err < 0) + goto out_class; + cpuhp_cpuid_state = err; + return 0; + +out_class: +#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)) + class_destroy(cpuid_class); +#else + class_unregister(&cpuid_class); +#endif /* Linux 6.6 */ +out_chrdev: + __unregister_chrdev(CPUID_MAJOR, 0, NR_CPUS, "cpu/cpuid"); + return err; +} +module_init(cpuid_init); + +static void __exit cpuid_exit(void) +{ + DPRINTF("[cpuid,%d]: unload module\n", __LINE__); + cpuhp_remove_state(cpuhp_cpuid_state); +#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)) + class_destroy(cpuid_class); +#else + class_unregister(&cpuid_class); +#endif /* Linux 6.6 */ + __unregister_chrdev(CPUID_MAJOR, 0, NR_CPUS, "cpu/cpuid"); +} +module_exit(cpuid_exit) + +MODULE_AUTHOR("The Tumultuous Unicorn Of Darkness"); +MODULE_DESCRIPTION("ARM registers driver for libcpuid"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_VERSION("0.1"); diff --git a/drivers/arm/linux/dkms.conf.in b/drivers/arm/linux/dkms.conf.in new file mode 100644 index 0000000..6e738b3 --- /dev/null +++ b/drivers/arm/linux/dkms.conf.in @@ -0,0 +1,7 @@ +PACKAGE_NAME="@CMAKE_PROJECT_NAME@" +PACKAGE_VERSION="@PROJECT_VERSION@" +MAKE="make" +CLEAN="make clean" +BUILT_MODULE_NAME[0]="@DRIVER_NAME@" +DEST_MODULE_LOCATION[0]="/kernel/drivers/misc/@DRIVER_NAME@" +AUTOINSTALL="yes"