init: lkm

Signed-off-by: AuxXxilium <info@auxxxilium.tech>
This commit is contained in:
AuxXxilium 1970-01-01 00:00:00 +00:00
commit d288da5003
114 changed files with 16462 additions and 0 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,3 @@
# These are supported funding model platforms
github: AuxXxilium

294
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,294 @@
#
# Copyright (C) 2023 AuxXxilium <https://github.com/AuxXxilium> and Ing <https://github.com/wjz304>
#
# This is free software, licensed under the MIT License.
# See /LICENSE for more information.
#
# # 注意:所有 include 组合会在 exclude 之后处理。 这允许你使用 include 添加回以前排除的组合。
# version: [ 6.2, 7.1, 7.2 ]
# platform: [ apollolake, broadwell, broadwellnk, bromolow, denverton, epyc7002, geminilake, purley, r1000, v1000 ]
# exclude:
# - version: 7.1
# platform: broadwell
# include:
# - version: "7.2"
# platform: "broadwell"
name: Build LKMs
on:
workflow_dispatch:
inputs:
version:
description: "format %y.%-m.$i or auto"
required: false
type: string
prerelease:
description: "pre release"
default: false
type: boolean
jobs:
build:
strategy:
matrix:
include:
- version: 7.1
platform: apollolake
parm: "dev-v7 prod-v7"
- version: 7.1
platform: broadwell
parm: "dev-v7 prod-v7"
- version: 7.1
platform: broadwellnk
parm: "dev-v7 prod-v7"
- version: 7.1
platform: broadwellnkv2
parm: "dev-v7 prod-v7"
- version: 7.1
platform: broadwellntbap
parm: "dev-v7 prod-v7"
- version: 7.1
platform: denverton
parm: "dev-v7 prod-v7"
- version: 7.1
platform: epyc7002
parm: "dev-v7 prod-v7"
- version: 7.1
platform: geminilake
parm: "dev-v7 prod-v7"
- version: 7.1
platform: purley
parm: "dev-v7 prod-v7"
- version: 7.1
platform: r1000
parm: "dev-v7 prod-v7"
- version: 7.1
platform: v1000
parm: "dev-v7 prod-v7"
#- version: 7.1
# platform: kvmx64
# parm: "dev-v7 prod-v7"
- version: 7.2
platform: apollolake
parm: "dev-v7 prod-v7"
- version: 7.2
platform: broadwell
parm: "dev-v7 prod-v7"
- version: 7.2
platform: broadwellnk
parm: "dev-v7 prod-v7"
- version: 7.2
platform: broadwellnkv2
parm: "dev-v7 prod-v7"
- version: 7.2
platform: broadwellntbap
parm: "dev-v7 prod-v7"
- version: 7.2
platform: denverton
parm: "dev-v7 prod-v7"
- version: 7.2
platform: epyc7002
parm: "dev-v7 prod-v7"
- version: 7.2
platform: geminilake
parm: "dev-v7 prod-v7"
- version: 7.2
platform: purley
parm: "dev-v7 prod-v7"
- version: 7.2
platform: r1000
parm: "dev-v7 prod-v7"
- version: 7.2
platform: v1000
parm: "dev-v7 prod-v7"
#- version: 7.2
# platform: kvmx64
# parm: "dev-v7 prod-v7"
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@main
- name: Init Env
run: |
git config --global user.email "info@auxxxilium.tech"
git config --global user.name "AuxXxilium"
sudo timedatectl set-timezone "Europe/Berlin"
- name: Get EnvDeploy
run: |
ROOT_PATH=${{ github.workspace }}
git clone https://github.com/SynologyOpenSource/pkgscripts-ng.git ${ROOT_PATH}/pkgscripts-ng
cd ${ROOT_PATH}/pkgscripts-ng
# if version == 6.2, checkout 6.2.4
git checkout DSM${{ matrix.version }}`[ "${{ matrix.version }}" = "6.2" ] && echo ".4"`
sudo ./EnvDeploy -v ${{ matrix.version }}`[ "${{ matrix.version }}" = "6.2" ] && echo ".4"` -l # Get Available platforms
sudo ./EnvDeploy -q -v ${{ matrix.version }} -p ${{ matrix.platform }}
# Fault tolerance of pkgscripts-ng
if [[ "${{ matrix.platform }}" == "broadwellntbap" && "${{ matrix.version }}" == "7.1" ]]; then
sed -i '/ broadwellnk BROADWELLNK/a\ broadwellntbap BROADWELLNTBAP linux-4.4.x Intel Broadwell with ntb kernel config in AP mode' ${ROOT_PATH}/pkgscripts-ng/include/platforms
fi
#ENV
mkdir -p ${ROOT_PATH}/source
ENV_PATH=${ROOT_PATH}/build_env/ds.${{ matrix.platform }}-${{ matrix.version }}
sudo cp -al ${ROOT_PATH}/pkgscripts-ng ${ENV_PATH}/
sudo chroot ${ENV_PATH} << "EOF"
cd pkgscripts
version=${{ matrix.version }}; [ ${version:0:1} -gt 6 ] && sed -i 's/print(" ".join(kernels))/pass #&/' ProjectDepends.py
sed -i '/PLATFORM_FAMILY/a\\techo "PRODUCT=$PRODUCT" >> $file\n\techo "KSRC=$KERNEL_SEARCH_PATH" >> $file\n\techo "LINUX_SRC=$KERNEL_SEARCH_PATH" >> $file' include/build
./SynoBuild -c -p ${{ matrix.platform }}
while read -r line; do if [ ${line:0:1} != "#" ]; then export ${line%%=*}="${line#*=}"; fi; done < /env${BUILD_ARCH}.mak
if [ -f "${KSRC}/Makefile" ]; then
# gcc issue "unrecognized command-line option '--param=allow-store-data-races=0'".
[ "${{ matrix.version }}" == "7.2" ] && sed -i 's/--param=allow-store-data-races=0/--allow-store-data-races/g' ${KSRC}/Makefile
VERSION=`cat ${KSRC}/Makefile | grep ^VERSION | awk -F' ' '{print $3}'`
PATCHLEVEL=`cat ${KSRC}/Makefile | grep ^PATCHLEVEL | awk -F' ' '{print $3}'`
SUBLEVEL=`cat ${KSRC}/Makefile | grep ^SUBLEVEL | awk -F' ' '{print $3}'`
[ -f "/env32.mak" ] && echo "KVER=${VERSION}.${PATCHLEVEL}.${SUBLEVEL}" >> /env32.mak
[ -f "/env64.mak" ] && echo "KVER=${VERSION}.${PATCHLEVEL}.${SUBLEVEL}" >> /env64.mak
CCVER=`$CC --version | head -n 1 | awk -F' ' '{print $3}'`
[ -f "/env32.mak" ] && echo "CCVER=${CCVER}" >> /env32.mak
[ -f "/env64.mak" ] && echo "CCVER=${CCVER}" >> /env64.mak
fi
EOF
[ -f ${ENV_PATH}/env64.mak ] && ENV_FILE=${ENV_PATH}/env64.mak || ([ -f ${ENV_PATH}/env32.mak ] && ENV_FILE=${ENV_PATH}/env32.mak)
if [ -n "${ENV_FILE}" ]; then
KVER=`grep 'KVER=' ${ENV_FILE} | awk -F'=' '{print $2}'`
CCVER=`grep 'CCVER=' ${ENV_FILE} | awk -F'=' '{print $2}'`
[ -n "${KVER}" ] && echo "KVER=${KVER}" >> $GITHUB_ENV
[ -n "${CCVER}" ] && echo "CCVER=${CCVER}" >> $GITHUB_ENV
fi
- name: Make LKMs
run: |
sudo chroot build_env/ds.${{ matrix.platform }}-${{ matrix.version }} << "EOF"
sed -i 's/^CFLAGS=/#CFLAGS=/g; s/^CXXFLAGS=/#CXXFLAGS=/g' /env${BUILD_ARCH}.mak
while read -r line; do if [ ${line:0:1} != "#" ]; then export ${line%%=*}="${line#*=}"; fi; done < /env${BUILD_ARCH}.mak
mkdir -p /source/output
repo=${{ github.server_url }}/${{ github.repository }}
# 5.10.55 Temporary use of https://github.com/XPEnology-Community/redpill-lkm5
# And currently only USB OK, @jim3ma is adapting to 7.2.
[ "${KVER:0:1}" = "5" ] && repo=https://github.com/AuxXxilium/redpill-lkm5
git clone -c http.sslVerify=false ${repo} /source/input
cd /source/input
[ -z "`grep 'env.mak' Makefile`" ] && sed -i '1 i include /env.mak' Makefile
array=(${{ matrix.parm }})
for a in ${array[@]}
do
make ${a}
if [ -f redpill.ko ]; then
strip -g redpill.ko # Discard symbols from object files.
RPKOVER=$(modinfo --field=vermagic redpill.ko | awk '{print $1}')
gzip redpill.ko
if [ "${{ matrix.platform }}" = "epyc7002" ]; then
mv -f ./redpill.ko.gz /source/output/rp-${{ matrix.platform }}-${{ matrix.version }}-${RPKOVER/+/}-`echo ${a} | awk -F'-' '{print $1}'`.ko.gz
else
mv -f ./redpill.ko.gz /source/output/rp-${{ matrix.platform }}-${RPKOVER/+/}-`echo ${a} | awk -F'-' '{print $1}'`.ko.gz
fi
else
echo "error"
fi
make clean
done
ls -al /source/output
EOF
ROOT_PATH=${{ github.workspace }}
mkdir -p ${ROOT_PATH}/source
sudo cp -a ${ROOT_PATH}/build_env/ds.${{ matrix.platform }}-${{ matrix.version }}/source/output ${ROOT_PATH}/source/
- name: Upload to Artifacts
uses: actions/upload-artifact@v4
with:
name: rp-lkms-${{ matrix.platform }}-${{ matrix.version }}
path: |
${{ github.workspace }}/source/output/*
- name: Clean
run: |
sudo rm -rf ${{ github.workspace }}/build_env/ds.${{ matrix.platform }}-${{ matrix.version }}/source/*
release:
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout
uses: actions/checkout@main
with:
fetch-depth: 0
- name: Changelog
uses: Bullrich/generate-release-changelog@master
id: Changelog
env:
REPO: ${{ github.repository }}
- name: Init Env
run: |
git config --global user.email "info@auxxxilium.tech"
git config --global user.name "AuxXxilium"
sudo timedatectl set-timezone "Europe/Berlin"
- name: Download from Artifacts
uses: actions/download-artifact@v4
with:
path: output
pattern: rp-lkms-*
merge-multiple: true
- name: Calculate Version
run: |
# Calculate Version
VERSION=""
if [ -n "${{ inputs.version }}" ]; then
VERSION="${{ inputs.version }}"
else
LATEST_TAG="$(curl -skL "https://api.github.com/repos/${{ github.repository }}/releases/latest" | jq -r ".tag_name" 2>/dev/null)"
if [[ -n "${LATEST_TAG}" && "`echo ${LATEST_TAG} | cut -d '.' -f 1,2`" = "`date +'%y.%-m.%-d'`" ]]; then # format %y.%-m.$i
VERSION="`echo ${LATEST_TAG} | awk -F '.' '{$3=$3+1}1' OFS='.'`"
else
VERSION="`date +'%y.%-m.%-d'`"
fi
fi
if [ -n "${VERSION}" ]; then
# Modify Source File
echo "Version: ${VERSION}"
echo "${VERSION}" >VERSION
echo "${VERSION}" >"./output/VERSION"
echo "VERSION=${VERSION}" >> $GITHUB_ENV
fi
- name: Zip Lkms
if: success() && env.VERSION != ''
run: |
VERSION="${{ env.VERSION }}"
zip -9 rp-lkms.zip -j output/*
- name: Release
if: success() && env.VERSION != ''
uses: ncipollo/release-action@v1
with:
tag: ${{ env.VERSION }}
prerelease: ${{ inputs.prerelease }}
allowUpdates: true
body: |
${{ steps.Changelog.outputs.changelog }}
artifacts: |
rp-lkms.zip

63
.gitignore vendored Executable file
View File

@ -0,0 +1,63 @@
/output
test*.sh
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
email.patch
linux-*/
.idea/
.vscode/
_compile.sh
./rp-lkms/
rp-lkms/

783
CMakeLists.txt Executable file
View File

@ -0,0 +1,783 @@
# This CMakeLists file is for usage with CLion (and maybe other) IDEs ONLY. Do NOT attempt to build the project with
# CMake as it will fail (kernel build process is tailored for Makefile while CLion's support for Makefile is... meh)
cmake_minimum_required(VERSION 3.0)
project(redpill C)
set(CMAKE_C_STANDARD 11)
include_directories(compat/toolkit/include)
add_definitions(-DLINUX_VERSION_CODE=199273)
include_directories(../linux-3.10.x-bromolow-25426/include)
include_directories(../linux-3.10.x-bromolow-25426/arch/x86/include)
include_directories(../linux-3.10.x-bromolow-25426/arch/x86/include/uapi)
add_definitions(-DCONFIG_SYNO_X86_SERIAL_PORT_SWAP)
#add_definitions(-DLINUX_VERSION_CODE=263227)
#include_directories(../linux-4.4.x-apollolake-25426/include)
#include_directories(../linux-4.4.x-apollolake-25426/arch/x86/include)
#include_directories(../linux-4.4.x-apollolake-25426/arch/x86/include/uapi)
# Custom options in our makefile
add_definitions(-DDBG_EXECVE)
# RP custom definitions
add_definitions(-DRP_MODULE_TARGET_VER=6)
# Generic options
add_definitions(-D__KERNEL__)
add_definitions(-DMODULE)
add_definitions(-DKBUILD_MODNAME=\"dummy\")
# bromolow + bromowell ones (minus CONFIG_SYNO_* which are replaced with "MY_ABC_HERE" in the kernel anyway)
add_definitions(-DCONFIG_64BIT)
add_definitions(-DCONFIG_X86_64)
add_definitions(-DCONFIG_X86)
add_definitions(-DCONFIG_INSTRUCTION_DECODER)
add_definitions(-DCONFIG_LOCKDEP_SUPPORT)
add_definitions(-DCONFIG_STACKTRACE_SUPPORT)
add_definitions(-DCONFIG_HAVE_LATENCYTOP_SUPPORT)
add_definitions(-DCONFIG_MMU)
add_definitions(-DCONFIG_NEED_DMA_MAP_STATE)
add_definitions(-DCONFIG_NEED_SG_DMA_LENGTH)
add_definitions(-DCONFIG_GENERIC_ISA_DMA)
add_definitions(-DCONFIG_GENERIC_BUG)
add_definitions(-DCONFIG_GENERIC_BUG_RELATIVE_POINTERS)
add_definitions(-DCONFIG_GENERIC_HWEIGHT)
add_definitions(-DCONFIG_ARCH_MAY_HAVE_PC_FDC)
add_definitions(-DCONFIG_RWSEM_XCHGADD_ALGORITHM)
add_definitions(-DCONFIG_GENERIC_CALIBRATE_DELAY)
add_definitions(-DCONFIG_ARCH_HAS_CPU_RELAX)
add_definitions(-DCONFIG_ARCH_HAS_CACHE_LINE_SIZE)
add_definitions(-DCONFIG_ARCH_HAS_CPU_AUTOPROBE)
add_definitions(-DCONFIG_HAVE_SETUP_PER_CPU_AREA)
add_definitions(-DCONFIG_NEED_PER_CPU_EMBED_FIRST_CHUNK)
add_definitions(-DCONFIG_NEED_PER_CPU_PAGE_FIRST_CHUNK)
add_definitions(-DCONFIG_ARCH_HIBERNATION_POSSIBLE)
add_definitions(-DCONFIG_ARCH_SUSPEND_POSSIBLE)
add_definitions(-DCONFIG_ZONE_DMA32)
add_definitions(-DCONFIG_AUDIT_ARCH)
add_definitions(-DCONFIG_ARCH_SUPPORTS_OPTIMIZED_INLINING)
add_definitions(-DCONFIG_ARCH_SUPPORTS_DEBUG_PAGEALLOC)
add_definitions(-DCONFIG_HAVE_INTEL_TXT)
add_definitions(-DCONFIG_X86_64_SMP)
add_definitions(-DCONFIG_X86_HT)
add_definitions(-DCONFIG_ARCH_CPU_PROBE_RELEASE)
add_definitions(-DCONFIG_ARCH_SUPPORTS_UPROBES)
add_definitions(-DCONFIG_IRQ_WORK)
add_definitions(-DCONFIG_BUILDTIME_EXTABLE_SORT)
add_definitions(-DCONFIG_HAVE_KERNEL_GZIP)
add_definitions(-DCONFIG_HAVE_KERNEL_BZIP2)
add_definitions(-DCONFIG_HAVE_KERNEL_LZMA)
add_definitions(-DCONFIG_HAVE_KERNEL_XZ)
add_definitions(-DCONFIG_HAVE_KERNEL_LZO)
add_definitions(-DCONFIG_KERNEL_LZMA)
add_definitions(-DCONFIG_SWAP)
add_definitions(-DCONFIG_SYSVIPC)
add_definitions(-DCONFIG_SYSVIPC_SYSCTL)
add_definitions(-DCONFIG_POSIX_MQUEUE)
add_definitions(-DCONFIG_POSIX_MQUEUE_SYSCTL)
add_definitions(-DCONFIG_AUDIT)
add_definitions(-DCONFIG_HAVE_GENERIC_HARDIRQS)
add_definitions(-DCONFIG_GENERIC_HARDIRQS)
add_definitions(-DCONFIG_GENERIC_IRQ_PROBE)
add_definitions(-DCONFIG_GENERIC_IRQ_SHOW)
add_definitions(-DCONFIG_GENERIC_PENDING_IRQ)
add_definitions(-DCONFIG_IRQ_DOMAIN)
add_definitions(-DCONFIG_IRQ_FORCED_THREADING)
add_definitions(-DCONFIG_SPARSE_IRQ)
add_definitions(-DCONFIG_CLOCKSOURCE_WATCHDOG)
add_definitions(-DCONFIG_ARCH_CLOCKSOURCE_DATA)
add_definitions(-DCONFIG_GENERIC_TIME_VSYSCALL)
add_definitions(-DCONFIG_GENERIC_CLOCKEVENTS)
add_definitions(-DCONFIG_GENERIC_CLOCKEVENTS_BUILD)
add_definitions(-DCONFIG_GENERIC_CLOCKEVENTS_BROADCAST)
add_definitions(-DCONFIG_GENERIC_CLOCKEVENTS_MIN_ADJUST)
add_definitions(-DCONFIG_GENERIC_CMOS_UPDATE)
add_definitions(-DCONFIG_TICK_ONESHOT)
add_definitions(-DCONFIG_NO_HZ_COMMON)
add_definitions(-DCONFIG_NO_HZ_IDLE)
add_definitions(-DCONFIG_NO_HZ)
add_definitions(-DCONFIG_HIGH_RES_TIMERS)
add_definitions(-DCONFIG_TICK_CPU_ACCOUNTING)
add_definitions(-DCONFIG_TASKSTATS)
add_definitions(-DCONFIG_TASK_DELAY_ACCT)
add_definitions(-DCONFIG_TASK_XACCT)
add_definitions(-DCONFIG_TASK_IO_ACCOUNTING)
add_definitions(-DCONFIG_TREE_RCU)
add_definitions(-DCONFIG_RCU_STALL_COMMON)
add_definitions(-DCONFIG_RCU_FAST_NO_HZ)
add_definitions(-DCONFIG_HAVE_UNSTABLE_SCHED_CLOCK)
add_definitions(-DCONFIG_ARCH_SUPPORTS_NUMA_BALANCING)
add_definitions(-DCONFIG_ARCH_WANTS_PROT_NUMA_PROT_NONE)
add_definitions(-DCONFIG_CGROUPS)
add_definitions(-DCONFIG_CGROUP_FREEZER)
add_definitions(-DCONFIG_CGROUP_DEVICE)
add_definitions(-DCONFIG_CPUSETS)
add_definitions(-DCONFIG_CGROUP_CPUACCT)
add_definitions(-DCONFIG_RESOURCE_COUNTERS)
add_definitions(-DCONFIG_MEMCG)
add_definitions(-DCONFIG_MEMCG_SWAP)
add_definitions(-DCONFIG_MEMCG_SWAP_ENABLED)
add_definitions(-DCONFIG_CGROUP_SCHED)
add_definitions(-DCONFIG_FAIR_GROUP_SCHED)
add_definitions(-DCONFIG_BLK_CGROUP)
add_definitions(-DCONFIG_NAMESPACES)
add_definitions(-DCONFIG_UTS_NS)
add_definitions(-DCONFIG_IPC_NS)
add_definitions(-DCONFIG_PID_NS)
add_definitions(-DCONFIG_NET_NS)
add_definitions(-DCONFIG_UIDGID_CONVERTED)
add_definitions(-DCONFIG_MM_OWNER)
add_definitions(-DCONFIG_BLK_DEV_INITRD)
add_definitions(-DCONFIG_RD_GZIP)
add_definitions(-DCONFIG_RD_LZMA)
add_definitions(-DCONFIG_SYSCTL)
add_definitions(-DCONFIG_ANON_INODES)
add_definitions(-DCONFIG_HAVE_UID16)
add_definitions(-DCONFIG_SYSCTL_EXCEPTION_TRACE)
add_definitions(-DCONFIG_HOTPLUG)
add_definitions(-DCONFIG_HAVE_PCSPKR_PLATFORM)
add_definitions(-DCONFIG_EXPERT)
add_definitions(-DCONFIG_UID16)
add_definitions(-DCONFIG_SYSCTL_SYSCALL)
add_definitions(-DCONFIG_KALLSYMS)
add_definitions(-DCONFIG_PRINTK)
add_definitions(-DCONFIG_BUG)
add_definitions(-DCONFIG_ELF_CORE)
add_definitions(-DCONFIG_BASE_FULL)
add_definitions(-DCONFIG_FUTEX)
add_definitions(-DCONFIG_EPOLL)
add_definitions(-DCONFIG_SIGNALFD)
add_definitions(-DCONFIG_TIMERFD)
add_definitions(-DCONFIG_EVENTFD)
add_definitions(-DCONFIG_SHMEM)
add_definitions(-DCONFIG_AIO)
add_definitions(-DCONFIG_PCI_QUIRKS)
add_definitions(-DCONFIG_EMBEDDED)
add_definitions(-DCONFIG_HAVE_PERF_EVENTS)
add_definitions(-DCONFIG_PERF_EVENTS)
add_definitions(-DCONFIG_VM_EVENT_COUNTERS)
add_definitions(-DCONFIG_SLAB)
add_definitions(-DCONFIG_HAVE_OPROFILE)
add_definitions(-DCONFIG_OPROFILE_NMI_TIMER)
add_definitions(-DCONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS)
add_definitions(-DCONFIG_ARCH_USE_BUILTIN_BSWAP)
add_definitions(-DCONFIG_USER_RETURN_NOTIFIER)
add_definitions(-DCONFIG_HAVE_IOREMAP_PROT)
add_definitions(-DCONFIG_HAVE_KPROBES)
add_definitions(-DCONFIG_HAVE_KRETPROBES)
add_definitions(-DCONFIG_HAVE_OPTPROBES)
add_definitions(-DCONFIG_HAVE_KPROBES_ON_FTRACE)
add_definitions(-DCONFIG_HAVE_ARCH_TRACEHOOK)
add_definitions(-DCONFIG_HAVE_DMA_ATTRS)
add_definitions(-DCONFIG_USE_GENERIC_SMP_HELPERS)
add_definitions(-DCONFIG_GENERIC_SMP_IDLE_THREAD)
add_definitions(-DCONFIG_HAVE_REGS_AND_STACK_ACCESS_API)
add_definitions(-DCONFIG_HAVE_DMA_API_DEBUG)
add_definitions(-DCONFIG_HAVE_HW_BREAKPOINT)
add_definitions(-DCONFIG_HAVE_MIXED_BREAKPOINTS_REGS)
add_definitions(-DCONFIG_HAVE_USER_RETURN_NOTIFIER)
add_definitions(-DCONFIG_HAVE_PERF_EVENTS_NMI)
add_definitions(-DCONFIG_HAVE_PERF_REGS)
add_definitions(-DCONFIG_HAVE_PERF_USER_STACK_DUMP)
add_definitions(-DCONFIG_HAVE_ARCH_JUMP_LABEL)
add_definitions(-DCONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG)
add_definitions(-DCONFIG_HAVE_CMPXCHG_LOCAL)
add_definitions(-DCONFIG_HAVE_CMPXCHG_DOUBLE)
add_definitions(-DCONFIG_ARCH_WANT_COMPAT_IPC_PARSE_VERSION)
add_definitions(-DCONFIG_ARCH_WANT_OLD_COMPAT_IPC)
add_definitions(-DCONFIG_HAVE_ARCH_SECCOMP_FILTER)
add_definitions(-DCONFIG_HAVE_CONTEXT_TRACKING)
add_definitions(-DCONFIG_HAVE_IRQ_TIME_ACCOUNTING)
add_definitions(-DCONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE)
add_definitions(-DCONFIG_MODULES_USE_ELF_RELA)
add_definitions(-DCONFIG_OLD_SIGSUSPEND3)
add_definitions(-DCONFIG_COMPAT_OLD_SIGACTION)
add_definitions(-DCONFIG_SLABINFO)
add_definitions(-DCONFIG_RT_MUTEXES)
add_definitions(-DCONFIG_MODULES)
add_definitions(-DCONFIG_MODULE_UNLOAD)
add_definitions(-DCONFIG_X86_X2APIC) # broadwell only
add_definitions(-DCONFIG_MODULE_FORCE_UNLOAD)
add_definitions(-DCONFIG_MODULE_SIG)
add_definitions(-DCONFIG_MODULE_SIG_SHA384)
add_definitions(-DCONFIG_STOP_MACHINE)
add_definitions(-DCONFIG_BLOCK)
add_definitions(-DCONFIG_BLK_DEV_BSG)
add_definitions(-DCONFIG_BLK_DEV_BSGLIB)
add_definitions(-DCONFIG_BLK_DEV_INTEGRITY)
add_definitions(-DCONFIG_PARTITION_ADVANCED)
add_definitions(-DCONFIG_MAC_PARTITION)
add_definitions(-DCONFIG_MSDOS_PARTITION)
add_definitions(-DCONFIG_EFI_PARTITION)
add_definitions(-DCONFIG_BLOCK_COMPAT)
add_definitions(-DCONFIG_IOSCHED_NOOP)
add_definitions(-DCONFIG_IOSCHED_DEADLINE)
add_definitions(-DCONFIG_IOSCHED_CFQ)
add_definitions(-DCONFIG_DEFAULT_CFQ)
add_definitions(-DCONFIG_PREEMPT_NOTIFIERS)
add_definitions(-DCONFIG_ASN1)
add_definitions(-DCONFIG_INLINE_SPIN_UNLOCK_IRQ)
add_definitions(-DCONFIG_INLINE_READ_UNLOCK)
add_definitions(-DCONFIG_INLINE_READ_UNLOCK_IRQ)
add_definitions(-DCONFIG_INLINE_WRITE_UNLOCK)
add_definitions(-DCONFIG_INLINE_WRITE_UNLOCK_IRQ)
add_definitions(-DCONFIG_ARCH_SUPPORTS_ATOMIC_RMW)
add_definitions(-DCONFIG_MUTEX_SPIN_ON_OWNER)
add_definitions(-DCONFIG_FREEZER)
add_definitions(-DCONFIG_ZONE_DMA)
add_definitions(-DCONFIG_SMP)
add_definitions(-DCONFIG_X86_MPPARSE)
add_definitions(-DCONFIG_RETPOLINE)
add_definitions(-DCONFIG_X86_SUPPORTS_MEMORY_FAILURE)
add_definitions(-DCONFIG_NO_BOOTMEM)
add_definitions(-DCONFIG_GENERIC_CPU)
add_definitions(-DCONFIG_X86_TSC)
add_definitions(-DCONFIG_X86_CMPXCHG64)
add_definitions(-DCONFIG_X86_CMOV)
add_definitions(-DCONFIG_X86_DEBUGCTLMSR)
add_definitions(-DCONFIG_PROCESSOR_SELECT)
add_definitions(-DCONFIG_CPU_SUP_INTEL)
add_definitions(-DCONFIG_HPET_TIMER)
add_definitions(-DCONFIG_HPET_EMULATE_RTC)
add_definitions(-DCONFIG_DMI)
add_definitions(-DCONFIG_SWIOTLB)
add_definitions(-DCONFIG_IOMMU_HELPER)
add_definitions(-DCONFIG_SCHED_SMT)
add_definitions(-DCONFIG_SCHED_MC)
add_definitions(-DCONFIG_PREEMPT_NONE)
add_definitions(-DCONFIG_X86_LOCAL_APIC)
add_definitions(-DCONFIG_X86_IO_APIC)
add_definitions(-DCONFIG_X86_MCE)
add_definitions(-DCONFIG_X86_MCE_INTEL)
add_definitions(-DCONFIG_X86_MCE_THRESHOLD)
add_definitions(-DCONFIG_X86_THERMAL_VECTOR)
add_definitions(-DCONFIG_X86_MSR)
add_definitions(-DCONFIG_X86_CPUID)
add_definitions(-DCONFIG_ARCH_PHYS_ADDR_T_64BIT)
add_definitions(-DCONFIG_ARCH_DMA_ADDR_T_64BIT)
add_definitions(-DCONFIG_DIRECT_GBPAGES)
add_definitions(-DCONFIG_ARCH_SPARSEMEM_ENABLE)
add_definitions(-DCONFIG_ARCH_SPARSEMEM_DEFAULT)
add_definitions(-DCONFIG_ARCH_SELECT_MEMORY_MODEL)
add_definitions(-DCONFIG_ARCH_PROC_KCORE_TEXT)
add_definitions(-DCONFIG_SELECT_MEMORY_MODEL)
add_definitions(-DCONFIG_SPARSEMEM_MANUAL)
add_definitions(-DCONFIG_SPARSEMEM)
add_definitions(-DCONFIG_HAVE_MEMORY_PRESENT)
add_definitions(-DCONFIG_SPARSEMEM_EXTREME)
add_definitions(-DCONFIG_SPARSEMEM_VMEMMAP_ENABLE)
add_definitions(-DCONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER)
add_definitions(-DCONFIG_SPARSEMEM_VMEMMAP)
add_definitions(-DCONFIG_HAVE_MEMBLOCK)
add_definitions(-DCONFIG_HAVE_MEMBLOCK_NODE_MAP)
add_definitions(-DCONFIG_ARCH_DISCARD_MEMBLOCK)
add_definitions(-DCONFIG_PAGEFLAGS_EXTENDED)
add_definitions(-DCONFIG_COMPACTION)
add_definitions(-DCONFIG_MIGRATION)
add_definitions(-DCONFIG_PHYS_ADDR_T_64BIT)
add_definitions(-DCONFIG_BOUNCE)
add_definitions(-DCONFIG_NEED_BOUNCE_POOL)
add_definitions(-DCONFIG_VIRT_TO_BUS)
add_definitions(-DCONFIG_MMU_NOTIFIER)
add_definitions(-DCONFIG_KSM)
add_definitions(-DCONFIG_ARCH_SUPPORTS_MEMORY_FAILURE)
add_definitions(-DCONFIG_CROSS_MEMORY_ATTACH)
add_definitions(-DCONFIG_MTRR)
add_definitions(-DCONFIG_ARCH_RANDOM)
add_definitions(-DCONFIG_X86_SMAP)
add_definitions(-DCONFIG_EFI)
add_definitions(-DCONFIG_HZ_1000)
add_definitions(-DCONFIG_HZ=1000)
add_definitions(-DCONFIG_SCHED_HRTICK)
add_definitions(-DCONFIG_KEXEC)
add_definitions(-DCONFIG_CRASH_DUMP)
add_definitions(-DCONFIG_RELOCATABLE)
add_definitions(-DCONFIG_HOTPLUG_CPU)
add_definitions(-DCONFIG_ARCH_ENABLE_MEMORY_HOTPLUG)
add_definitions(-DCONFIG_ARCH_HIBERNATION_HEADER)
add_definitions(-DCONFIG_HIBERNATE_CALLBACKS)
add_definitions(-DCONFIG_HIBERNATION)
add_definitions(-DCONFIG_PM_SLEEP)
add_definitions(-DCONFIG_PM_SLEEP_SMP)
add_definitions(-DCONFIG_PM)
add_definitions(-DCONFIG_ACPI)
add_definitions(-DCONFIG_ACPI_SLEEP)
add_definitions(-DCONFIG_ACPI_DOCK)
add_definitions(-DCONFIG_ACPI_I2C)
add_definitions(-DCONFIG_ACPI_HOTPLUG_CPU)
add_definitions(-DCONFIG_X86_PM_TIMER)
add_definitions(-DCONFIG_ACPI_CONTAINER)
add_definitions(-DCONFIG_CPU_FREQ)
add_definitions(-DCONFIG_CPU_FREQ_GOV_COMMON)
add_definitions(-DCONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE)
add_definitions(-DCONFIG_CPU_FREQ_GOV_USERSPACE)
add_definitions(-DCONFIG_CPU_IDLE)
add_definitions(-DCONFIG_CPU_IDLE_GOV_LADDER)
add_definitions(-DCONFIG_CPU_IDLE_GOV_MENU)
add_definitions(-DCONFIG_PCI)
add_definitions(-DCONFIG_PCI_DIRECT)
add_definitions(-DCONFIG_PCI_MMCONFIG)
add_definitions(-DCONFIG_PCI_DOMAINS)
add_definitions(-DCONFIG_PCIEPORTBUS)
add_definitions(-DCONFIG_PCIEAER)
add_definitions(-DCONFIG_PCIE_ECRC)
add_definitions(-DCONFIG_PCIEASPM)
add_definitions(-DCONFIG_PCIEASPM_PERFORMANCE)
add_definitions(-DCONFIG_ARCH_SUPPORTS_MSI)
add_definitions(-DCONFIG_PCI_MSI)
add_definitions(-DCONFIG_HT_IRQ)
add_definitions(-DCONFIG_PCI_ATS)
add_definitions(-DCONFIG_PCI_IOV)
add_definitions(-DCONFIG_PCI_IOAPIC)
add_definitions(-DCONFIG_PCI_LABEL)
add_definitions(-DCONFIG_ISA_DMA_API)
add_definitions(-DCONFIG_BINFMT_ELF)
add_definitions(-DCONFIG_COMPAT_BINFMT_ELF)
add_definitions(-DCONFIG_ARCH_BINFMT_ELF_RANDOMIZE_PIE)
add_definitions(-DCONFIG_BINFMT_SCRIPT)
add_definitions(-DCONFIG_BINFMT_MISC)
add_definitions(-DCONFIG_COREDUMP)
add_definitions(-DCONFIG_IA32_EMULATION)
add_definitions(-DCONFIG_IA32_AOUT)
add_definitions(-DCONFIG_COMPAT)
add_definitions(-DCONFIG_COMPAT_FOR_U64_ALIGNMENT)
add_definitions(-DCONFIG_SYSVIPC_COMPAT)
add_definitions(-DCONFIG_KEYS_COMPAT)
add_definitions(-DCONFIG_HAVE_TEXT_POKE_SMP)
add_definitions(-DCONFIG_X86_DEV_DMA_OPS)
add_definitions(-DCONFIG_NET)
add_definitions(-DCONFIG_COMPAT_NETLINK_MESSAGES)
add_definitions(-DCONFIG_PACKET)
add_definitions(-DCONFIG_UNIX)
add_definitions(-DCONFIG_XFRM)
add_definitions(-DCONFIG_INET)
add_definitions(-DCONFIG_IP_MULTICAST)
add_definitions(-DCONFIG_IP_ADVANCED_ROUTER)
add_definitions(-DCONFIG_IP_MULTIPLE_TABLES)
add_definitions(-DCONFIG_IP_PNP)
add_definitions(-DCONFIG_IP_PNP_DHCP)
add_definitions(-DCONFIG_NET_IPGRE_BROADCAST)
add_definitions(-DCONFIG_SYN_COOKIES)
add_definitions(-DCONFIG_INET_LRO)
add_definitions(-DCONFIG_INET_DIAG)
add_definitions(-DCONFIG_INET_TCP_DIAG)
add_definitions(-DCONFIG_TCP_CONG_CUBIC)
add_definitions(-DCONFIG_IPV6_PRIVACY)
add_definitions(-DCONFIG_IPV6_ROUTER_PREF)
add_definitions(-DCONFIG_IPV6_OPTIMISTIC_DAD)
add_definitions(-DCONFIG_IPV6_SIT_6RD)
add_definitions(-DCONFIG_IPV6_NDISC_NODETYPE)
add_definitions(-DCONFIG_IPV6_MULTIPLE_TABLES)
add_definitions(-DCONFIG_IPV6_MROUTE)
add_definitions(-DCONFIG_IPV6_PIMSM_V2)
add_definitions(-DCONFIG_NETFILTER)
add_definitions(-DCONFIG_NETFILTER_ADVANCED)
add_definitions(-DCONFIG_BRIDGE_NETFILTER)
add_definitions(-DCONFIG_NF_CONNTRACK_MARK)
add_definitions(-DCONFIG_NF_CONNTRACK_PROCFS)
add_definitions(-DCONFIG_NF_NAT_NEEDED)
add_definitions(-DCONFIG_NF_CONNTRACK_PROC_COMPAT)
add_definitions(-DCONFIG_HAVE_NET_DSA)
add_definitions(-DCONFIG_NET_SCHED)
add_definitions(-DCONFIG_NET_CLS)
add_definitions(-DCONFIG_NET_SCH_FIFO)
add_definitions(-DCONFIG_DNS_RESOLVER)
add_definitions(-DCONFIG_RPS)
add_definitions(-DCONFIG_RFS_ACCEL)
add_definitions(-DCONFIG_XPS)
add_definitions(-DCONFIG_BQL)
add_definitions(-DCONFIG_FIB_RULES)
add_definitions(-DCONFIG_WIRELESS)
add_definitions(-DCONFIG_WIRELESS_EXT)
add_definitions(-DCONFIG_WEXT_CORE)
add_definitions(-DCONFIG_WEXT_PROC)
add_definitions(-DCONFIG_WEXT_PRIV)
add_definitions(-DCONFIG_HAVE_BPF_JIT)
add_definitions(-DCONFIG_DEVTMPFS)
add_definitions(-DCONFIG_STANDALONE)
add_definitions(-DCONFIG_PREVENT_FIRMWARE_BUILD)
add_definitions(-DCONFIG_FW_LOADER)
add_definitions(-DCONFIG_FW_LOADER_USER_HELPER)
add_definitions(-DCONFIG_GENERIC_CPU_VULNERABILITIES)
add_definitions(-DCONFIG_REGMAP)
add_definitions(-DCONFIG_REGMAP_I2C)
add_definitions(-DCONFIG_PNP)
add_definitions(-DCONFIG_PNPACPI)
add_definitions(-DCONFIG_BLK_DEV)
add_definitions(-DCONFIG_BLK_DEV_RAM)
add_definitions(-DCONFIG_ENCLOSURE_SERVICES)
add_definitions(-DCONFIG_HAVE_IDE)
add_definitions(-DCONFIG_SCSI_MOD)
add_definitions(-DCONFIG_RAID_ATTRS)
add_definitions(-DCONFIG_SCSI)
add_definitions(-DCONFIG_SCSI_DMA)
add_definitions(-DCONFIG_SCSI_PROC_FS)
add_definitions(-DCONFIG_BLK_DEV_SD)
add_definitions(-DCONFIG_SCSI_ENCLOSURE)
add_definitions(-DCONFIG_SCSI_MULTI_LUN)
add_definitions(-DCONFIG_SCSI_ISCSI_ATTRS)
add_definitions(-DCONFIG_SCSI_SAS_ATTRS)
add_definitions(-DCONFIG_SCSI_SAS_LIBSAS)
add_definitions(-DCONFIG_SCSI_SAS_ATA)
add_definitions(-DCONFIG_SCSI_SAS_HOST_SMP)
add_definitions(-DCONFIG_SCSI_LOWLEVEL)
add_definitions(-DCONFIG_SCSI_DH)
add_definitions(-DCONFIG_SCSI_DH_RDAC)
add_definitions(-DCONFIG_ATA)
add_definitions(-DCONFIG_ATA_VERBOSE_ERROR)
add_definitions(-DCONFIG_ATA_ACPI)
add_definitions(-DCONFIG_SATA_PMP)
add_definitions(-DCONFIG_SATA_AHCI)
add_definitions(-DCONFIG_SATA_SIL24)
add_definitions(-DCONFIG_ATA_SFF)
add_definitions(-DCONFIG_ATA_BMDMA)
add_definitions(-DCONFIG_SATA_MV)
add_definitions(-DCONFIG_MD)
add_definitions(-DCONFIG_BLK_DEV_MD)
add_definitions(-DCONFIG_MD_AUTODETECT)
add_definitions(-DCONFIG_MD_LINEAR)
add_definitions(-DCONFIG_MD_RAID0)
add_definitions(-DCONFIG_MD_RAID1)
add_definitions(-DCONFIG_MD_RAID10)
add_definitions(-DCONFIG_MD_RAID456)
add_definitions(-DCONFIG_BLK_DEV_DM_BUILTIN)
add_definitions(-DCONFIG_BLK_DEV_DM)
add_definitions(-DCONFIG_NETDEVICES)
add_definitions(-DCONFIG_NET_CORE)
add_definitions(-DCONFIG_ETHERNET)
add_definitions(-DCONFIG_NET_VENDOR_3COM)
add_definitions(-DCONFIG_NET_VENDOR_ADAPTEC)
add_definitions(-DCONFIG_NET_VENDOR_ALTEON)
add_definitions(-DCONFIG_NET_VENDOR_AMD)
add_definitions(-DCONFIG_NET_VENDOR_ATHEROS)
add_definitions(-DCONFIG_NET_CADENCE)
add_definitions(-DCONFIG_NET_VENDOR_BROADCOM)
add_definitions(-DCONFIG_BNX2X_SRIOV)
add_definitions(-DCONFIG_NET_VENDOR_BROCADE)
add_definitions(-DCONFIG_NET_VENDOR_CHELSIO)
add_definitions(-DCONFIG_NET_VENDOR_CISCO)
add_definitions(-DCONFIG_NET_VENDOR_DEC)
add_definitions(-DCONFIG_NET_VENDOR_DLINK)
add_definitions(-DCONFIG_NET_VENDOR_EMULEX)
add_definitions(-DCONFIG_NET_VENDOR_EXAR)
add_definitions(-DCONFIG_NET_VENDOR_HP)
add_definitions(-DCONFIG_NET_VENDOR_INTEL)
add_definitions(-DCONFIG_IGB_DCA)
add_definitions(-DCONFIG_NET_VENDOR_I825XX)
add_definitions(-DCONFIG_NET_VENDOR_MARVELL)
add_definitions(-DCONFIG_NET_VENDOR_MELLANOX)
add_definitions(-DCONFIG_NET_VENDOR_MICREL)
add_definitions(-DCONFIG_NET_VENDOR_MICROCHIP)
add_definitions(-DCONFIG_NET_VENDOR_MYRI)
add_definitions(-DCONFIG_NET_VENDOR_NATSEMI)
add_definitions(-DCONFIG_NET_VENDOR_8390)
add_definitions(-DCONFIG_NET_VENDOR_NVIDIA)
add_definitions(-DCONFIG_NET_VENDOR_OKI)
add_definitions(-DCONFIG_NET_VENDOR_QLOGIC)
add_definitions(-DCONFIG_NET_VENDOR_REALTEK)
add_definitions(-DCONFIG_NET_VENDOR_RDC)
add_definitions(-DCONFIG_NET_VENDOR_SEEQ)
add_definitions(-DCONFIG_NET_VENDOR_SILAN)
add_definitions(-DCONFIG_NET_VENDOR_SIS)
add_definitions(-DCONFIG_NET_VENDOR_SMSC)
add_definitions(-DCONFIG_NET_VENDOR_STMICRO)
add_definitions(-DCONFIG_NET_VENDOR_SUN)
add_definitions(-DCONFIG_NET_VENDOR_TEHUTI)
add_definitions(-DCONFIG_NET_VENDOR_TI)
add_definitions(-DCONFIG_NET_VENDOR_VIA)
add_definitions(-DCONFIG_NET_VENDOR_WIZNET)
add_definitions(-DCONFIG_WLAN)
add_definitions(-DCONFIG_INPUT)
add_definitions(-DCONFIG_SERIO)
add_definitions(-DCONFIG_SERIO_I8042)
add_definitions(-DCONFIG_SERIO_SERPORT)
add_definitions(-DCONFIG_SERIO_LIBPS2)
add_definitions(-DCONFIG_TTY)
add_definitions(-DCONFIG_VT)
add_definitions(-DCONFIG_CONSOLE_TRANSLATIONS)
add_definitions(-DCONFIG_VT_CONSOLE)
add_definitions(-DCONFIG_VT_CONSOLE_SLEEP)
add_definitions(-DCONFIG_HW_CONSOLE)
add_definitions(-DCONFIG_UNIX98_PTYS)
add_definitions(-DCONFIG_DEVPTS_MULTIPLE_INSTANCES)
add_definitions(-DCONFIG_LEGACY_PTYS)
add_definitions(-DCONFIG_SERIAL_NONSTANDARD)
add_definitions(-DCONFIG_DEVKMEM)
add_definitions(-DCONFIG_SERIAL_8250)
add_definitions(-DCONFIG_SERIAL_8250_DEPRECATED_OPTIONS)
add_definitions(-DCONFIG_SERIAL_8250_CONSOLE)
add_definitions(-DCONFIG_FIX_EARLYCON_MEM)
add_definitions(-DCONFIG_SERIAL_8250_DMA)
add_definitions(-DCONFIG_SERIAL_8250_PCI)
add_definitions(-DCONFIG_SERIAL_CORE)
add_definitions(-DCONFIG_SERIAL_CORE_CONSOLE)
add_definitions(-DCONFIG_DEVPORT)
add_definitions(-DCONFIG_I2C)
add_definitions(-DCONFIG_I2C_BOARDINFO)
add_definitions(-DCONFIG_I2C_COMPAT)
add_definitions(-DCONFIG_I2C_CHARDEV)
add_definitions(-DCONFIG_I2C_MUX)
add_definitions(-DCONFIG_I2C_HELPER_AUTO)
add_definitions(-DCONFIG_SPI)
add_definitions(-DCONFIG_SPI_MASTER)
add_definitions(-DCONFIG_SPI_BITBANG)
add_definitions(-DCONFIG_SPI_SPIDEV)
add_definitions(-DCONFIG_PPS)
add_definitions(-DCONFIG_PTP_1588_CLOCK)
add_definitions(-DCONFIG_ARCH_WANT_OPTIONAL_GPIOLIB)
add_definitions(-DCONFIG_GPIO_DEVRES)
add_definitions(-DCONFIG_HWMON)
add_definitions(-DCONFIG_HWMON_VID)
add_definitions(-DCONFIG_SENSORS_CORETEMP)
add_definitions(-DCONFIG_SENSORS_IT87)
add_definitions(-DCONFIG_THERMAL_HWMON)
add_definitions(-DCONFIG_THERMAL_DEFAULT_GOV_STEP_WISE)
add_definitions(-DCONFIG_THERMAL_GOV_STEP_WISE)
add_definitions(-DCONFIG_SSB_POSSIBLE)
add_definitions(-DCONFIG_SSB_SPROM)
add_definitions(-DCONFIG_SSB_PCIHOST_POSSIBLE)
add_definitions(-DCONFIG_SSB_PCIHOST)
add_definitions(-DCONFIG_SSB_DRIVER_PCICORE_POSSIBLE)
add_definitions(-DCONFIG_BCMA_POSSIBLE)
add_definitions(-DCONFIG_MFD_CORE)
add_definitions(-DCONFIG_LPC_ICH)
add_definitions(-DCONFIG_VGA_ARB)
add_definitions(-DCONFIG_BACKLIGHT_LCD_SUPPORT)
add_definitions(-DCONFIG_DUMMY_CONSOLE)
add_definitions(-DCONFIG_SOUND_OSS_CORE)
add_definitions(-DCONFIG_SOUND_OSS_CORE_PRECLAIM)
add_definitions(-DCONFIG_SND_OSSEMUL)
add_definitions(-DCONFIG_SND_SUPPORT_OLD_API)
add_definitions(-DCONFIG_SND_DMA_SGBUF)
add_definitions(-DCONFIG_SND_USB)
add_definitions(-DCONFIG_USB_HIDDEV)
add_definitions(-DCONFIG_USB_ARCH_HAS_OHCI)
add_definitions(-DCONFIG_USB_ARCH_HAS_EHCI)
add_definitions(-DCONFIG_USB_ARCH_HAS_XHCI)
add_definitions(-DCONFIG_USB_SUPPORT)
add_definitions(-DCONFIG_USB_ARCH_HAS_HCD)
add_definitions(-DCONFIG_USB_DEFAULT_PERSIST)
add_definitions(-DCONFIG_USB_ETRON_HCD_DEBUGGING)
add_definitions(-DCONFIG_USB_ETRON_HUB)
add_definitions(-DCONFIG_USB_EHCI_ROOT_HUB_TT)
add_definitions(-DCONFIG_USB_EHCI_TT_NEWSCHED)
add_definitions(-DCONFIG_NEW_LEDS)
add_definitions(-DCONFIG_LEDS_CLASS)
add_definitions(-DCONFIG_LEDS_TRIGGERS)
add_definitions(-DCONFIG_LEDS_TRIGGER_TIMER)
add_definitions(-DCONFIG_LEDS_TRIGGER_HEARTBEAT)
add_definitions(-DCONFIG_RTC_LIB)
add_definitions(-DCONFIG_RTC_CLASS)
add_definitions(-DCONFIG_RTC_HCTOSYS)
add_definitions(-DCONFIG_RTC_SYSTOHC)
add_definitions(-DCONFIG_RTC_INTF_SYSFS)
add_definitions(-DCONFIG_RTC_INTF_PROC)
add_definitions(-DCONFIG_RTC_INTF_DEV)
add_definitions(-DCONFIG_RTC_DRV_CMOS)
add_definitions(-DCONFIG_DMADEVICES)
add_definitions(-DCONFIG_DMA_ENGINE)
add_definitions(-DCONFIG_DMA_ACPI)
add_definitions(-DCONFIG_ASYNC_TX_DMA)
add_definitions(-DCONFIG_STAGING)
add_definitions(-DCONFIG_USBIP_DEBUG)
add_definitions(-DCONFIG_ZSMALLOC)
add_definitions(-DCONFIG_NET_VENDOR_SILICOM)
add_definitions(-DCONFIG_X86_PLATFORM_DEVICES)
add_definitions(-DCONFIG_CLKEVT_I8253)
add_definitions(-DCONFIG_CLKBLD_I8253)
add_definitions(-DCONFIG_IOMMU_API)
add_definitions(-DCONFIG_IOMMU_SUPPORT)
add_definitions(-DCONFIG_DMAR_TABLE)
add_definitions(-DCONFIG_INTEL_IOMMU)
add_definitions(-DCONFIG_INTEL_IOMMU_DEFAULT_ON)
add_definitions(-DCONFIG_INTEL_IOMMU_FLOPPY_WA)
add_definitions(-DCONFIG_IRQ_REMAP)
add_definitions(-DCONFIG_FIRMWARE_MEMMAP)
add_definitions(-DCONFIG_DMIID)
add_definitions(-DCONFIG_EFI_VARS)
add_definitions(-DCONFIG_EFI_VARS_PSTORE)
add_definitions(-DCONFIG_DCACHE_WORD_ACCESS)
add_definitions(-DCONFIG_EXT2_FS)
add_definitions(-DCONFIG_EXT2_FS_XATTR)
add_definitions(-DCONFIG_EXT3_FS)
add_definitions(-DCONFIG_EXT3_DEFAULTS_TO_ORDERED)
add_definitions(-DCONFIG_EXT3_FS_XATTR)
add_definitions(-DCONFIG_EXT4_FS)
add_definitions(-DCONFIG_JBD)
add_definitions(-DCONFIG_JBD2)
add_definitions(-DCONFIG_FS_MBCACHE)
add_definitions(-DCONFIG_FS_POSIX_ACL)
add_definitions(-DCONFIG_FILE_LOCKING)
add_definitions(-DCONFIG_FSNOTIFY)
add_definitions(-DCONFIG_DNOTIFY)
add_definitions(-DCONFIG_INOTIFY_USER)
add_definitions(-DCONFIG_QUOTA)
add_definitions(-DCONFIG_QUOTACTL)
add_definitions(-DCONFIG_QUOTACTL_COMPAT)
add_definitions(-DCONFIG_JOLIET)
add_definitions(-DCONFIG_ZISOFS)
add_definitions(-DCONFIG_UDF_NLS)
add_definitions(-DCONFIG_PROC_FS)
add_definitions(-DCONFIG_PROC_KCORE)
add_definitions(-DCONFIG_PROC_VMCORE)
add_definitions(-DCONFIG_PROC_SYSCTL)
add_definitions(-DCONFIG_PROC_PAGE_MONITOR)
add_definitions(-DCONFIG_SYSFS)
add_definitions(-DCONFIG_TMPFS)
add_definitions(-DCONFIG_CONFIGFS_FS)
add_definitions(-DCONFIG_MISC_FILESYSTEMS)
add_definitions(-DCONFIG_PSTORE)
add_definitions(-DCONFIG_AUFS_BRANCH_MAX_1023)
add_definitions(-DCONFIG_AUFS_SBILIST)
add_definitions(-DCONFIG_AUFS_FHSM)
add_definitions(-DCONFIG_AUFS_BR_RAMFS)
add_definitions(-DCONFIG_AUFS_BR_HFSPLUS)
add_definitions(-DCONFIG_AUFS_BDEV_LOOP)
add_definitions(-DCONFIG_NETWORK_FILESYSTEMS)
add_definitions(-DCONFIG_NFS_FS)
add_definitions(-DCONFIG_NFS_V2)
add_definitions(-DCONFIG_NFS_V3)
add_definitions(-DCONFIG_NFS_V4)
add_definitions(-DCONFIG_NFS_USE_KERNEL_DNS)
add_definitions(-DCONFIG_NFS_DEBUG)
add_definitions(-DCONFIG_NFSD_V3)
add_definitions(-DCONFIG_NFSD_V4)
add_definitions(-DCONFIG_LOCKD)
add_definitions(-DCONFIG_LOCKD_V4)
add_definitions(-DCONFIG_NFS_COMMON)
add_definitions(-DCONFIG_SUNRPC)
add_definitions(-DCONFIG_SUNRPC_GSS)
add_definitions(-DCONFIG_SUNRPC_DEBUG)
add_definitions(-DCONFIG_CIFS_DEBUG)
add_definitions(-DCONFIG_CIFS_SMB2)
add_definitions(-DCONFIG_NLS)
add_definitions(-DCONFIG_NLS_UTF8)
add_definitions(-DCONFIG_TRACE_IRQFLAGS_SUPPORT)
add_definitions(-DCONFIG_PRINTK_TIME)
add_definitions(-DCONFIG_ENABLE_WARN_DEPRECATED)
add_definitions(-DCONFIG_ENABLE_MUST_CHECK)
add_definitions(-DCONFIG_MAGIC_SYSRQ)
add_definitions(-DCONFIG_DEBUG_FS)
add_definitions(-DCONFIG_DEBUG_KERNEL)
add_definitions(-DCONFIG_LOCKUP_DETECTOR)
add_definitions(-DCONFIG_HARDLOCKUP_DETECTOR)
add_definitions(-DCONFIG_BOOTPARAM_HARDLOCKUP_PANIC)
add_definitions(-DCONFIG_DETECT_HUNG_TASK)
add_definitions(-DCONFIG_SCHED_DEBUG)
add_definitions(-DCONFIG_SCHEDSTATS)
add_definitions(-DCONFIG_HAVE_DEBUG_KMEMLEAK)
add_definitions(-DCONFIG_STACKTRACE)
add_definitions(-DCONFIG_DEBUG_BUGVERBOSE)
add_definitions(-DCONFIG_ARCH_WANT_FRAME_POINTERS)
add_definitions(-DCONFIG_ARCH_HAS_DEBUG_STRICT_USER_COPY_CHECKS)
add_definitions(-DCONFIG_USER_STACKTRACE_SUPPORT)
add_definitions(-DCONFIG_HAVE_FUNCTION_TRACER)
add_definitions(-DCONFIG_HAVE_FUNCTION_GRAPH_TRACER)
add_definitions(-DCONFIG_HAVE_FUNCTION_GRAPH_FP_TEST)
add_definitions(-DCONFIG_HAVE_FUNCTION_TRACE_MCOUNT_TEST)
add_definitions(-DCONFIG_HAVE_DYNAMIC_FTRACE)
add_definitions(-DCONFIG_HAVE_DYNAMIC_FTRACE_WITH_REGS)
add_definitions(-DCONFIG_HAVE_FTRACE_MCOUNT_RECORD)
add_definitions(-DCONFIG_HAVE_SYSCALL_TRACEPOINTS)
add_definitions(-DCONFIG_HAVE_FENTRY)
add_definitions(-DCONFIG_HAVE_C_RECORDMCOUNT)
add_definitions(-DCONFIG_TRACING_SUPPORT)
add_definitions(-DCONFIG_DYNAMIC_DEBUG) # bromolow only
add_definitions(-DCONFIG_HAVE_ARCH_KGDB)
add_definitions(-DCONFIG_HAVE_ARCH_KMEMCHECK)
add_definitions(-DCONFIG_X86_VERBOSE_BOOTUP)
add_definitions(-DCONFIG_EARLY_PRINTK)
add_definitions(-DCONFIG_DEBUG_RODATA)
add_definitions(-DCONFIG_DEBUG_RODATA_TEST)
add_definitions(-DCONFIG_HAVE_MMIOTRACE_SUPPORT)
add_definitions(-DCONFIG_IO_DELAY_0X80)
add_definitions(-DCONFIG_OPTIMIZE_INLINING)
add_definitions(-DCONFIG_KEYS)
add_definitions(-DCONFIG_KEYS_DEBUG_PROC_KEYS)
add_definitions(-DCONFIG_SECURITY)
add_definitions(-DCONFIG_SECURITYFS)
add_definitions(-DCONFIG_SECURITY_NETWORK)
add_definitions(-DCONFIG_KAISER)
add_definitions(-DCONFIG_SECURITY_PATH)
add_definitions(-DCONFIG_SECURITY_APPARMOR)
add_definitions(-DCONFIG_SECURITY_APPARMOR_COMPAT_24)
add_definitions(-DCONFIG_DEFAULT_SECURITY_APPARMOR)
add_definitions(-DCONFIG_XOR_BLOCKS)
add_definitions(-DCONFIG_ASYNC_CORE)
add_definitions(-DCONFIG_ASYNC_MEMCPY)
add_definitions(-DCONFIG_ASYNC_XOR)
add_definitions(-DCONFIG_ASYNC_PQ)
add_definitions(-DCONFIG_ASYNC_RAID6_RECOV)
add_definitions(-DCONFIG_CRYPTO)
add_definitions(-DCONFIG_CRYPTO_ALGAPI)
add_definitions(-DCONFIG_CRYPTO_ALGAPI2)
add_definitions(-DCONFIG_CRYPTO_AEAD2)
add_definitions(-DCONFIG_CRYPTO_BLKCIPHER2)
add_definitions(-DCONFIG_CRYPTO_HASH)
add_definitions(-DCONFIG_CRYPTO_HASH2)
add_definitions(-DCONFIG_CRYPTO_RNG2)
add_definitions(-DCONFIG_CRYPTO_PCOMP2)
add_definitions(-DCONFIG_CRYPTO_MANAGER2)
add_definitions(-DCONFIG_CRYPTO_MANAGER_DISABLE_TESTS)
add_definitions(-DCONFIG_CRYPTO_WORKQUEUE)
add_definitions(-DCONFIG_CRYPTO_CRC32C)
add_definitions(-DCONFIG_CRYPTO_CRCT10DIF)
add_definitions(-DCONFIG_CRYPTO_SHA512)
add_definitions(-DCONFIG_CRYPTO_AES)
add_definitions(-DCONFIG_ASYMMETRIC_KEY_TYPE)
add_definitions(-DCONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE)
add_definitions(-DCONFIG_PUBLIC_KEY_ALGO_RSA)
add_definitions(-DCONFIG_X509_CERTIFICATE_PARSER)
add_definitions(-DCONFIG_HAVE_KVM)
add_definitions(-DCONFIG_HAVE_KVM_IRQCHIP)
add_definitions(-DCONFIG_HAVE_KVM_IRQ_ROUTING)
add_definitions(-DCONFIG_HAVE_KVM_EVENTFD)
add_definitions(-DCONFIG_KVM_APIC_ARCHITECTURE)
add_definitions(-DCONFIG_KVM_MMIO)
add_definitions(-DCONFIG_KVM_ASYNC_PF)
add_definitions(-DCONFIG_HAVE_KVM_MSI)
add_definitions(-DCONFIG_HAVE_KVM_CPU_RELAX_INTERCEPT)
add_definitions(-DCONFIG_VIRTUALIZATION)
add_definitions(-DCONFIG_KVM_DEVICE_ASSIGNMENT)
add_definitions(-DCONFIG_RAID6_PQ)
add_definitions(-DCONFIG_BITREVERSE)
add_definitions(-DCONFIG_GENERIC_STRNCPY_FROM_USER)
add_definitions(-DCONFIG_GENERIC_STRNLEN_USER)
add_definitions(-DCONFIG_GENERIC_FIND_FIRST_BIT)
add_definitions(-DCONFIG_GENERIC_PCI_IOMAP)
add_definitions(-DCONFIG_GENERIC_IOMAP)
add_definitions(-DCONFIG_GENERIC_IO)
add_definitions(-DCONFIG_CRC16)
add_definitions(-DCONFIG_CRC_T10DIF)
add_definitions(-DCONFIG_CRC32)
add_definitions(-DCONFIG_CRC32_SLICEBY8)
add_definitions(-DCONFIG_ZLIB_INFLATE)
add_definitions(-DCONFIG_LZO_COMPRESS)
add_definitions(-DCONFIG_LZO_DECOMPRESS)
add_definitions(-DCONFIG_DECOMPRESS_GZIP)
add_definitions(-DCONFIG_DECOMPRESS_LZMA)
add_definitions(-DCONFIG_GENERIC_ALLOCATOR)
add_definitions(-DCONFIG_HAS_IOMEM)
add_definitions(-DCONFIG_HAS_IOPORT)
add_definitions(-DCONFIG_HAS_DMA)
add_definitions(-DCONFIG_CHECK_SIGNATURE)
add_definitions(-DCONFIG_CPU_RMAP)
add_definitions(-DCONFIG_DQL)
add_definitions(-DCONFIG_NLATTR)
add_definitions(-DCONFIG_ARCH_HAS_ATOMIC64_DEC_IF_POSITIVE)
add_definitions(-DCONFIG_AVERAGE)
add_definitions(-DCONFIG_CLZ_TAB)
add_definitions(-DCONFIG_MPILIB)
add_definitions(-DCONFIG_OID_REGISTRY)
add_definitions(-DCONFIG_UCS2_STRING)
add_definitions(-DCONFIG_SERIAL_8250_NR_UARTS=4)
add_definitions(-DCONFIG_SYNO_BOOT_SATA_DOM) # only some platforms support that, notably 3615xs while 918+ doesn't
# SATA DOM-related configs (only models with it, e.g. 3615xs but not 918+)
add_definitions(-DCONFIG_SYNO_SATA_DOM_VENDOR=\"DUMMY_VENDOR\")
add_definitions(-DCONFIG_SYNO_SATA_DOM_MODEL=\"DUMMY_MODEL\")
add_executable(redpill
redpill_main.c redpill_main.h internal/call_protected.c internal/call_protected.h common.h config/cmdline_delegate.c config/cmdline_delegate.h shim/boot_device_shim.c shim/boot_device_shim.h internal/stealth.c internal/stealth.h config/runtime_config.c config/runtime_config.h test.c shim/bios_shim.c shim/bios_shim.h internal/override/override_symbol.c internal/override/override_symbol.h shim/bios/bios_shims_collection.c shim/bios/bios_shims_collection.h shim/block_fw_update_shim.c shim/block_fw_update_shim.h internal/intercept_execve.c internal/intercept_execve.h shim/disable_exectutables.c shim/disable_exectutables.h debug/debug_execve.c debug/debug_execve.h compat/string_compat.c compat/string_compat.h internal/stealth/sanitize_cmdline.c internal/stealth/sanitize_cmdline.h internal/virtual_pci.c internal/virtual_pci.h shim/pci_shim.c shim/pci_shim.h shim/bios/rtc_proxy.c shim/bios/rtc_proxy.h shim/bios/rtc_proxy.c shim/bios/rtc_proxy.h internal/uart/virtual_uart.c internal/uart/virtual_uart.h shim/uart_fixer.c shim/uart_fixer.h config/uart_defs.h debug/debug_vuart.h internal/uart/vuart_virtual_irq.c internal/uart/vuart_virtual_irq.h internal/uart/vuart_internal.h shim/boot_dev/usb_boot_shim.c shim/boot_dev/usb_boot_shim.h shim/boot_dev/native_sata_boot_shim.c shim/boot_dev/native_sata_boot_shim.h internal/uart/uart_swapper.c internal/uart/uart_swapper.h shim/pmu_shim.c shim/pmu_shim.h internal/intercept_driver_register.c internal/intercept_driver_register.h shim/shim_base.h shim/storage/sata_port_shim.c shim/storage/sata_port_shim.h internal/scsi/scsi_notifier.c internal/scsi/scsi_notifier.h internal/scsi/scsi_notifier.c internal/scsi/scsi_notifier.h internal/notifier_base.h internal/scsi/scsi_toolbox.c internal/scsi/scsi_toolbox.h internal/scsi/scsi_notifier_list.c internal/scsi/scsi_notifier_list.h shim/storage/smart_shim.c shim/storage/smart_shim.h internal/helper/memory_helper.c internal/helper/memory_helper.h internal/scsi/hdparam.h internal/scsi/scsiparam.h internal/helper/symbol_helper.c internal/helper/symbol_helper.h compat/toolkit/drivers/usb/storage/usb.h shim/boot_dev/fake_sata_boot_shim.c shim/boot_dev/fake_sata_boot_shim.h shim/boot_dev/boot_shim_base.c shim/boot_dev/boot_shim_base.h config/cmdline_opts.h internal/ioscheduler_fixer.c internal/ioscheduler_fixer.h shim/bios/bios_hwcap_shim.c shim/bios/bios_hwcap_shim.h internal/helper/math_helper.c internal/helper/math_helper.h config/hwmon_defs.h config/platform_types.h shim/bios/bios_hwmon_shim.c shim/bios/bios_hwmon_shim.h config/vpci_types.h internal/override/override_syscall.c internal/override/override_syscall.h)

674
LICENSE Executable file
View File

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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 3 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, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

97
Makefile Executable file
View File

@ -0,0 +1,97 @@
PWD := $(shell pwd)
ifeq ($(LINUX_SRC),)
LINUX_SRC := "$(PWD)/../linux-3.10.x-bromolow-25426"
endif
SRCS-$(DBG_EXECVE) += debug/debug_execve.c
ccflags-$(DBG_EXECVE) += -DRPDBG_EXECVE
SRCS-y += compat/string_compat.c \
\
internal/helper/math_helper.c internal/helper/memory_helper.c internal/helper/symbol_helper.c \
internal/scsi/scsi_toolbox.c internal/scsi/scsi_notifier_list.c internal/scsi/scsi_notifier.c \
internal/override/override_symbol.c internal/override/override_syscall.c internal/intercept_execve.c \
internal/call_protected.c internal/intercept_driver_register.c internal/stealth/sanitize_cmdline.c \
internal/stealth.c internal/virtual_pci.c internal/uart/uart_swapper.c internal/uart/vuart_virtual_irq.c \
internal/uart/virtual_uart.c internal/ioscheduler_fixer.c \
\
config/cmdline_delegate.c config/runtime_config.c \
\
shim/boot_dev/boot_shim_base.c shim/boot_dev/usb_boot_shim.c shim/boot_dev/fake_sata_boot_shim.c \
shim/boot_dev/native_sata_boot_shim.c shim/boot_device_shim.c \
\
shim/storage/smart_shim.c shim/storage/sata_port_shim.c shim/storage/scsi_disk_serial.c \
shim/bios/bios_hwcap_shim.c shim/bios/bios_hwmon_shim.c shim/bios/rtc_proxy.c \
shim/bios/bios_shims_collection.c shim/bios/bios_psu_status_shim.c shim/bios_shim.c \
shim/block_fw_update_shim.c shim/disable_exectutables.c shim/pci_shim.c shim/pmu_shim.c shim/uart_fixer.c \
\
redpill_main.c
OBJS = $(SRCS-y:.c=.o)
#this module name CAN NEVER be the same as the main file (or it will get weird ;)) and the main file has to be included
# in object file. So here we say the module file(s) which will create .ko(s) is "redpill.o" and that other objects which
# must be linked (redpill-objs variable)
obj-m += redpill.o
redpill-objs := $(OBJS)
ccflags-y += -std=gnu99 -fgnu89-inline -Wno-declaration-after-statement
ccflags-y += -I$(src)/compat/toolkit/include
ifndef RP_VERSION_POSTFIX
RP_VERSION_POSTFIX := $(shell git rev-parse --is-inside-work-tree 1>/dev/null 2>/dev/null && echo -n "git-" && git log -1 --pretty='%h' 2>/dev/null || date '+at-%Y_%m_%d-%H_%M_%S')
endif
ccflags-y += -DRP_VERSION_POSTFIX="\"$(RP_VERSION_POSTFIX)\""
# Optimization settings per-target. Since LKM makefiles are evaluated twice (first with the specified target and second
# time with target "modules") we need to set the custom target variable during first parsing and based on that variable
# set additional CC-flags when the makefile is parsed for the second time
ifdef RP_MODULE_TARGET
ccflags-dev = -g -fno-inline -DDEBUG
ccflags-test = -O3
ccflags-prod = -O3
ccflags-y += -DRP_MODULE_TARGET_VER=${RP_MODULE_TARGET_VER} # this is assumed to be defined when target is specified
$(info RP-TARGET SPECIFIED AS ${RP_MODULE_TARGET} v${RP_MODULE_TARGET_VER})
# stealth mode can always be overridden but there are sane per-target defaults (see above)
ifneq ($(STEALTH_MODE),)
$(info STEATLH MODE OVERRIDE: ${STEALTH_MODE})
ccflags-y += -DSTEALTH_MODE=$(STEALTH_MODE)
else
ccflags-dev += -DSTEALTH_MODE=1
ccflags-test += -DSTEALTH_MODE=2
ccflags-prod += -DSTEALTH_MODE=3
endif
ccflags-y += ${ccflags-${RP_MODULE_TARGET}}
else
# during the first read of the makefile we don't get the RP_MODULE_TARGET - if for some reason we didn't get it during
# the actual build phase it should explode (and it will if an unknown GCC flag is specified). We cannot sue makefile
# error here as we don't know if the file is parsed for the first time or the second time. Just Kbuild peculiarities ;)
ccflags-y = --bogus-flag-which-should-not-be-called-NO_RP_MODULE_TARGER_SPECIFIED
endif
# this MUST be last after all other options to force GNU89 for the file being a workaround for GCC bug #275674
# see internal/scsi/scsi_notifier_list.h for detailed explanation
CFLAGS_scsi_notifier_list.o += -std=gnu89
# do NOT move this target - make <3.80 doesn't have a way to specify default target and takes the first one found
default_error:
$(error You need to specify one of the following targets: dev-v6, dev-v7, test-v6, test-v7, prod-v6, prod-v7, clean)
# All v6 targets
dev-v6: # kernel running in v6.2+ OS, all symbols included, debug messages included
$(MAKE) -C $(LINUX_SRC) M=$(PWD) RP_MODULE_TARGET="dev" RP_MODULE_TARGET_VER="6" modules
test-v6: # kernel running in v6.2+ OS, fully stripped with only warning & above (no debugs or info)
$(MAKE) -C $(LINUX_SRC) M=$(PWD) RP_MODULE_TARGET="test" RP_MODULE_TARGET_VER="6" modules
prod-v6: # kernel running in v6.2+ OS, fully stripped with no debug messages
$(MAKE) -C $(LINUX_SRC) M=$(PWD) RP_MODULE_TARGET="prod" RP_MODULE_TARGET_VER="6" modules
# All v7 targets
dev-v7: # kernel running in v6.2+ OS, all symbols included, debug messages included
$(MAKE) -C $(LINUX_SRC) M=$(PWD) RP_MODULE_TARGET="dev" RP_MODULE_TARGET_VER="7" modules
test-v7: # kernel running in v6.2+ OS, fully stripped with only warning & above (no debugs or info)
$(MAKE) -C $(LINUX_SRC) M=$(PWD) RP_MODULE_TARGET="test" RP_MODULE_TARGET_VER="7" modules
prod-v7: # kernel running in v6.2+ OS, fully stripped with no debug messages
$(MAKE) -C $(LINUX_SRC) M=$(PWD) RP_MODULE_TARGET="prod" RP_MODULE_TARGET_VER="7" modules
clean:
$(MAKE) -C $(LINUX_SRC) M=$(PWD) clean

10
README.md Executable file
View File

@ -0,0 +1,10 @@
# LKM for Arc Redpill Loader
### Links
- <a href="https://github.com/AuxXxilium">Overview</a>
- <a href="https://github.com/AuxXxilium/AuxXxilium/wiki">Wiki and Informations</a>
- <a href="https://github.com/AuxXxilium/arc/releases/latest">Download</a>
### Thanks
All code was based on the work of TTG, pocopico, jumkey, fbelavenuto and others involved in continuing TTG's original redpill-load project.

128
common.h Executable file
View File

@ -0,0 +1,128 @@
#ifndef REDPILLLKM_COMMON_H
#define REDPILLLKM_COMMON_H
/******************************************** Available whole-module flags ********************************************/
//This (shameful) flag disables shims which cannot be properly unloaded to make debugging of other things easier
//#define DBG_DISABLE_UNLOADABLE
//disabled uart unswapping even if needed (useful for hand-loading while running)
//#define DBG_DISABLE_UART_SWAP_FIX
//Whether to cause a kernel panic when module fails to load internally (which should be normally done on production)
#define KP_ON_LOAD_ERROR
//Print A LOT of vUART debug messages
//#define VUART_DEBUG_LOG
//Enabled printing of all ioctl() calls (hooked or not)
//#define DBG_SMART_PRINT_ALL_IOCTL
//Normally GetHwCapability calls (checking what hardware supports) are responded internally. Setting this DBG adds log
// of all requests & responses for hardware capabilities (and there're frquent but not overwhelming). Additionally this
// option turns on additional calls to the original GetHwCapability and logs compared values. Some values are ALWAYS
// proxied to the original GetHwCapability
//#define DBG_HWCAP
//Debug all hardware monitoring features (shim/bios/bios_hwmon_shim.c)
//#define DBG_HWMON
/**********************************************************************************************************************/
#include "internal/stealth.h"
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/slab.h> //kmalloc
#include <linux/string.h>
#include "compat/string_compat.h"
#include <linux/types.h> //bool & others
/************************************************** Strings handling **************************************************/
#define get_static_name(variable) #variable
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
#define strlen_static(param) (sizeof(param)-1) //gets the size of a string minus trailing nullbyte (useful for partial matches)
#define strlen_to_size(len) (sizeof(char) * ((len)+1)) //useful for static strings, use strsize() for dynamic ones
#define strsize(param) strlen_to_size(strlen(param)) //strlen including NULLbyte; useful for kmalloc-ing
/**********************************************************************************************************************/
/****************************************** Dynamic memory allocation helpers *****************************************/
//[internal] Cleans up & provides standard reporting and return
#define __kalloc_err_report_clean(variable, size, exit) \
(variable) = NULL; \
pr_loc_crt("kernel memory alloc failure - tried to allocate %ld bytes for %s", \
(long)(size), get_static_name(variable)); \
return exit;
//Use these if you need to do a manual malloc with some extra checks but want to return a consistant message
#define kalloc_error_int(variable, size) do { __kalloc_err_report_clean(variable, size, -ENOMEM); } while(0)
#define kalloc_error_ptr(variable, size) do { __kalloc_err_report_clean(variable, size, ERR_PTR(-ENOMEM)); } while(0)
//[internal] Reserves memory & checks result
#define __kalloc_or_exit(type, variable, size, exit_type) \
(variable) = (type)(size, GFP_KERNEL); \
if (unlikely(!(variable))) { kalloc_error_ ## exit_type (variable, size); }
//Use these to do a standard malloc with error reporting
#define kmalloc_or_exit_int(variable, size) do { __kalloc_or_exit(kmalloc, variable, size, int); } while(0)
#define kmalloc_or_exit_ptr(variable, size) do { __kalloc_or_exit(kmalloc, variable, size, ptr); } while(0)
#define kzalloc_or_exit_int(variable, size) do { __kalloc_or_exit(kzalloc, variable, size, int); } while(0)
#define kzalloc_or_exit_ptr(variable, size) do { __kalloc_or_exit(kzalloc, variable, size, ptr); } while(0)
#define try_kfree(variable) do { if(variable) { kfree(variable); } } while(0)
/**********************************************************************************************************************/
/****************************************************** Logging *******************************************************/
#define _pr_loc_crt(fmt, ...) pr_crit( "<%s/%s:%d> " pr_fmt(fmt) "\n", KBUILD_MODNAME, __FILENAME__, __LINE__, ##__VA_ARGS__)
#define _pr_loc_err(fmt, ...) pr_err ( "<%s/%s:%d> " pr_fmt(fmt) "\n", KBUILD_MODNAME, __FILENAME__, __LINE__, ##__VA_ARGS__)
#define _pr_loc_wrn(fmt, ...) pr_warn( "<%s/%s:%d> " pr_fmt(fmt) "\n", KBUILD_MODNAME, __FILENAME__, __LINE__, ##__VA_ARGS__)
#define _pr_loc_inf(fmt, ...) pr_info( "<%s/%s:%d> " pr_fmt(fmt) "\n", KBUILD_MODNAME, __FILENAME__, __LINE__, ##__VA_ARGS__)
#define _pr_loc_dbg(fmt, ...) pr_info( "<%s/%s:%d> " pr_fmt(fmt) "\n", KBUILD_MODNAME, __FILENAME__, __LINE__, ##__VA_ARGS__)
#define _pr_loc_dbg_raw(fmt, ...) printk(fmt, ##__VA_ARGS__)
#define _pr_loc_bug(fmt, ...) \
do { \
pr_err("<%s/%s:%d> !!BUG!! " pr_fmt(fmt) "\n", KBUILD_MODNAME, __FILENAME__, __LINE__, ##__VA_ARGS__); \
WARN(1, "BUG log triggered"); \
} while(0)
#if STEALTH_MODE >= STEALTH_MODE_FULL //all logs will be disabled in full
#define pr_loc_crt(fmt, ...)
#define pr_loc_err(fmt, ...)
#define pr_loc_wrn(fmt, ...)
#define pr_loc_inf(fmt, ...)
#define pr_loc_dbg(fmt, ...)
#define pr_loc_dbg_raw(fmt, ...)
#define pr_loc_bug(fmt, ...)
#define DBG_ALLOW_UNUSED(var) ((void)var) //in debug modes some variables are seen as unused (as they're only for dbg)
#elif STEALTH_MODE >= STEALTH_MODE_NORMAL //in normal mode we only warnings/errors/etc.
#define pr_loc_crt _pr_loc_crt
#define pr_loc_err _pr_loc_err
#define pr_loc_wrn _pr_loc_wrn
#define pr_loc_inf(fmt, ...)
#define pr_loc_dbg(fmt, ...)
#define pr_loc_dbg_raw(fmt, ...)
#define pr_loc_bug _pr_loc_bug
#define DBG_ALLOW_UNUSED(var) ((void)var) //in debug modes some variables are seen as unused (as they're only for dbg)
#else
#define pr_loc_crt _pr_loc_crt
#define pr_loc_err _pr_loc_err
#define pr_loc_inf _pr_loc_inf
#define pr_loc_wrn _pr_loc_wrn
#define pr_loc_dbg _pr_loc_dbg
#define pr_loc_dbg_raw _pr_loc_dbg_raw
#define pr_loc_bug _pr_loc_bug
#define DBG_ALLOW_UNUSED(var) //when debug logs are enables we don't silence unused variables warnings
#endif //STEALTH_MODE
/**********************************************************************************************************************/
#ifndef RP_MODULE_TARGET_VER
#error "The RP_MODULE_TARGET_VER is not defined - it is required to properly set VTKs"
#endif
//Before you change that you need to go and check all usages of RP_MODULE_TARGET_VER
#if RP_MODULE_TARGET_VER != 6 && RP_MODULE_TARGET_VER != 7
#error "The RP_MODULE_TARGET_VER value is invalid"
#endif
#endif //REDPILLLKM_COMMON_H

95
compat/string_compat.c Executable file
View File

@ -0,0 +1,95 @@
/*
* linux/lib/string.c
* Modified to take care of kernel versions
*
* Copyright (C) 1991, 1992 Linus Torvalds
*/
#include "string_compat.h"
#include <linux/errno.h> //E2BIG
#if LINUX_VERSION_CODE <= KERNEL_VERSION(4,3,0)
#include <asm/byteorder.h>
#include <asm/word-at-a-time.h>
#include <asm/page.h> //PAGE_SIZE
/**
* strscpy - Copy a C-string into a sized buffer
* @dest: Where to copy the string to
* @src: Where to copy the string from
* @count: Size of destination buffer
*
* Copy the string, or as much of it as fits, into the dest buffer.
* The routine returns the number of characters copied (not including
* the trailing NUL) or -E2BIG if the destination buffer wasn't big enough.
* The behavior is undefined if the string buffers overlap.
* The destination buffer is always NUL terminated, unless it's zero-sized.
*
* Preferred to strlcpy() since the API doesn't require reading memory
* from the src string beyond the specified "count" bytes, and since
* the return value is easier to error-check than strlcpy()'s.
* In addition, the implementation is robust to the string changing out
* from underneath it, unlike the current strlcpy() implementation.
*
* Preferred to strncpy() since it always returns a valid string, and
* doesn't unnecessarily force the tail of the destination buffer to be
* zeroed. If the zeroing is desired, it's likely cleaner to use strscpy()
* with an overflow test, then just memset() the tail of the dest buffer.
*/
ssize_t strscpy(char *dest, const char *src, size_t count)
{
const struct word_at_a_time constants = WORD_AT_A_TIME_CONSTANTS;
size_t max = count;
long res = 0;
if (count == 0)
return -E2BIG;
#ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
/*
* If src is unaligned, don't cross a page boundary,
* since we don't know if the next page is mapped.
*/
if ((long)src & (sizeof(long) - 1)) {
size_t limit = PAGE_SIZE - ((long)src & (PAGE_SIZE - 1));
if (limit < max)
max = limit;
}
#else
/* If src or dest is unaligned, don't do word-at-a-time. */
if (((long) dest | (long) src) & (sizeof(long) - 1))
max = 0;
#endif
while (max >= sizeof(unsigned long)) {
unsigned long c, data;
c = *(unsigned long *)(src+res);
*(unsigned long *)(dest+res) = c;
if (has_zero(c, &data, &constants)) {
data = prep_zero_mask(c, data, &constants);
data = create_zero_mask(data);
return res + find_zero(data);
}
res += sizeof(unsigned long);
count -= sizeof(unsigned long);
max -= sizeof(unsigned long);
}
while (count) {
char c;
c = src[res];
dest[res] = c;
if (!c)
return res;
res++;
count--;
}
/* Hit buffer length without finding a NUL; force NUL-termination. */
if (res)
dest[res-1] = '\0';
return -E2BIG;
}
#endif //kernel 4.3.0

11
compat/string_compat.h Executable file
View File

@ -0,0 +1,11 @@
#ifndef REDPILL_STRING_COMPAT_H
#define REDPILL_STRING_COMPAT_H
#include <linux/version.h> //KERNEL_VERSION()
#include <linux/types.h> //ssize_t
#if LINUX_VERSION_CODE <= KERNEL_VERSION(4,3,0)
ssize_t __must_check strscpy(char *, const char *, size_t);
#endif
#endif //REDPILL_STRING_COMPAT_H

View File

@ -0,0 +1,30 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef REDPILL_PCI_H
#define REDPILL_PCI_H
#warning "Using compatibility file for drivers/pci/pci.h - if possible do NOT compile using toolkit"
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,18,0) && LINUX_VERSION_CODE < KERNEL_VERSION(4,20,0) //v4.18 - v4.20
/* pci_dev priv_flags */
#define PCI_DEV_ADDED 1
static inline bool pci_dev_is_added(const struct pci_dev *dev)
{
return test_bit(PCI_DEV_ADDED, &dev->priv_flags);
}
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4,20,0) && LINUX_VERSION_CODE < KERNEL_VERSION(6,0,0) //v4.20 - v6.0
#define PCI_DEV_ADDED 0
static inline bool pci_dev_is_added(const struct pci_dev *dev)
{
return test_bit(PCI_DEV_ADDED, &dev->priv_flags);
}
#else
static inline bool pci_dev_is_added(const struct pci_dev *dev)
{
return 1 == dev->is_added;
}
#endif //LINUX_VERSION_CODE check
#endif /* REDPILL_PCI_H */

View File

@ -0,0 +1,135 @@
/**
* Cherry-picked USB.h internal structures from Linux v4.4.x. If possible avoid using anything from this file like fire.
*
* ORIGINAL FILE HEADER PRESERVED BELOW
* ------------------------------------
* Driver for USB Mass Storage compliant devices
* Main Header File
*
* Current development and maintenance by:
* (c) 1999-2002 Matthew Dharm (mdharm-usb@one-eyed-alien.net)
*
* Initial work by:
* (c) 1999 Michael Gee (michael@linuxspecific.com)
*
* This driver is based on the 'USB Mass Storage Class' document. This
* describes in detail the protocol used to communicate with such
* devices. Clearly, the designers had SCSI and ATAPI commands in
* mind when they created this document. The commands are all very
* similar to commands in the SCSI-II and ATAPI specifications.
*
* It is important to note that in a number of cases this class
* exhibits class-specific exemptions from the USB specification.
* Notably the usage of NAK, STALL and ACK differs from the norm, in
* that they are used to communicate wait, failed and OK on commands.
*
* Also, for certain devices, the interrupt endpoint is used to convey
* status of a command.
*
* Please see http://www.one-eyed-alien.net/~mdharm/linux-usb for more
* information about this driver.
*
* 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, 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.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef REDPILL_USB_H
#define REDPILL_USB_H
#warning "Using compatibility file for drivers/usb/storage/usb.h - if possible do NOT compile using toolkit"
//This structure didn't change substantially since v2.6 days; 5.14 is simply the newest one we checked - it will
// probably remain unchanged for years to come
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,3,0) && LINUX_VERSION_CODE < KERNEL_VERSION(5,14,0) //v3.3 - v5.14
#include <linux/usb.h> //struct usb_sg_request
struct us_data;
typedef int (*trans_cmnd)(struct scsi_cmnd *, struct us_data*);
typedef int (*trans_reset)(struct us_data*);
typedef void (*proto_cmnd)(struct scsi_cmnd*, struct us_data*);
typedef void (*extra_data_destructor)(void *); /* extra data destructor */
typedef void (*pm_hook)(struct us_data *, int); /* power management hook */
struct us_data {
/* The device we're working with
* It's important to note:
* (o) you must hold dev_mutex to change pusb_dev
*/
struct mutex dev_mutex; /* protect pusb_dev */
struct usb_device *pusb_dev; /* this usb_device */
struct usb_interface *pusb_intf; /* this interface */
struct us_unusual_dev *unusual_dev; /* device-filter entry */
unsigned long fflags; /* fixed flags from filter */
unsigned long dflags; /* dynamic atomic bitflags */
unsigned int send_bulk_pipe; /* cached pipe values */
unsigned int recv_bulk_pipe;
unsigned int send_ctrl_pipe;
unsigned int recv_ctrl_pipe;
unsigned int recv_intr_pipe;
/* information about the device */
char *transport_name;
char *protocol_name;
__le32 bcs_signature;
u8 subclass;
u8 protocol;
u8 max_lun;
u8 ifnum; /* interface number */
u8 ep_bInterval; /* interrupt interval */
/* function pointers for this device */
trans_cmnd transport; /* transport function */
trans_reset transport_reset; /* transport device reset */
proto_cmnd proto_handler; /* protocol handler */
/* SCSI interfaces */
struct scsi_cmnd *srb; /* current srb */
unsigned int tag; /* current dCBWTag */
char scsi_name[32]; /* scsi_host name */
/* control and bulk communications data */
struct urb *current_urb; /* USB requests */
struct usb_ctrlrequest *cr; /* control requests */
struct usb_sg_request current_sg; /* scatter-gather req. */
unsigned char *iobuf; /* I/O buffer */
dma_addr_t iobuf_dma; /* buffer DMA addresses */
struct task_struct *ctl_thread; /* the control thread */
/* mutual exclusion and synchronization structures */
struct completion cmnd_ready; /* to sleep thread on */
struct completion notify; /* thread begin/end */
wait_queue_head_t delay_wait; /* wait during reset */
struct delayed_work scan_dwork; /* for async scanning */
/* subdriver information */
void *extra; /* Any extra data */
extra_data_destructor extra_destructor;/* extra data destructor */
#ifdef CONFIG_PM
pm_hook suspend_resume_hook;
#endif
/* hacks for READ CAPACITY bug handling */
int use_last_sector_hacks;
int last_sector_retries;
};
#endif //LINUX_VERSION_CODE check
struct Scsi_Host;
static inline struct us_data *host_to_us(struct Scsi_Host *host) {
return (struct us_data *) host->hostdata;
}
#endif //REDPILL_USB_H

115
compat/toolkit/fs/proc/internal.h Executable file
View File

@ -0,0 +1,115 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Internal procfs definitions
*
* Copyright (C) 2004 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This file contains curated definitions from original fs/proc/internal.h file which we (unfortunately) need to access.
* As the internal.h, as the name implies, is meant for in-tree use only official toolkits lack it.
* Kernel version constrains here are taken from the real kernel source tree to prevent including wrong structs
* definitions. Please keep it neat as mismatch will cause very hard to debug problems.
*/
#warning "Using compatibility file for fs/proc/internal.h - if possible do NOT compile using toolkit"
#include <linux/proc_fs.h>
#include <linux/proc_ns.h>
#include <linux/spinlock.h>
#include <linux/atomic.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0) && LINUX_VERSION_CODE < KERNEL_VERSION(3,19,0) //v3.10 - v3.18
struct proc_dir_entry {
unsigned int low_ino;
umode_t mode;
nlink_t nlink;
kuid_t uid;
kgid_t gid;
loff_t size;
const struct inode_operations *proc_iops;
const struct file_operations *proc_fops;
struct proc_dir_entry *next, *parent, *subdir;
void *data;
atomic_t count; /* use count */
atomic_t in_use; /* number of callers into module in progress; */
/* negative -> it's going away RSN */
struct completion *pde_unload_completion;
struct list_head pde_openers; /* who did ->open, but not ->release */
spinlock_t pde_unload_lock; /* proc_fops checks and pde_users bumps */
u8 namelen;
char name[];
};
union proc_op {
int (*proc_get_link)(struct dentry *, struct path *);
int (*proc_read)(struct task_struct *task, char *page);
int (*proc_show)(struct seq_file *m,
struct pid_namespace *ns, struct pid *pid,
struct task_struct *task);
};
struct proc_inode {
struct pid *pid;
int fd;
union proc_op op;
struct proc_dir_entry *pde;
struct ctl_table_header *sysctl;
struct ctl_table *sysctl_entry;
struct proc_ns ns;
struct inode vfs_inode;
};
//See https://github.com/torvalds/linux/commit/771187d61bb3cbaf62c492ec3b8b789933f7691e
//v3.19 - <v4.9 (they changed it a bit starting with v4.9)
#elif LINUX_VERSION_CODE < KERNEL_VERSION(4,9,0)
struct proc_dir_entry {
unsigned int low_ino;
umode_t mode;
nlink_t nlink;
kuid_t uid;
kgid_t gid;
loff_t size;
const struct inode_operations *proc_iops;
const struct file_operations *proc_fops;
struct proc_dir_entry *parent;
struct rb_root subdir;
struct rb_node subdir_node;
void *data;
atomic_t count; /* use count */
atomic_t in_use; /* number of callers into module in progress; */
/* negative -> it's going away RSN */
struct completion *pde_unload_completion;
struct list_head pde_openers; /* who did ->open, but not ->release */
spinlock_t pde_unload_lock; /* proc_fops checks and pde_users bumps */
u8 namelen;
char name[];
};
union proc_op {
int (*proc_get_link)(struct dentry *, struct path *);
int (*proc_show)(struct seq_file *m,
struct pid_namespace *ns, struct pid *pid,
struct task_struct *task);
};
struct proc_inode {
struct pid *pid;
int fd;
union proc_op op;
struct proc_dir_entry *pde;
struct ctl_table_header *sysctl;
struct ctl_table *sysctl_entry;
const struct proc_ns_operations *ns_ops;
struct inode vfs_inode;
};
#endif
//These methods are the same forever
static inline struct proc_inode *PROC_I(const struct inode *inode)
{
return container_of(inode, struct proc_inode, vfs_inode);
}
static inline struct proc_dir_entry *PDE(const struct inode *inode)
{
return PROC_I(inode)->pde;
}

View File

455
config/cmdline_delegate.c Executable file
View File

@ -0,0 +1,455 @@
#include "cmdline_delegate.h"
#include "../common.h" //commonly used headers in this module
#include "../internal/call_protected.h" //used to call cmdline_proc_show()
#include <linux/seq_file.h> //struct seq_file
#define ensure_cmdline_param(cmdline_param) \
if (strncmp(param_pointer, cmdline_param, strlen_static(cmdline_param)) != 0) { return false; }
#define ensure_cmdline_token(cmdline_param) \
if (strncmp(param_pointer, cmdline_param, sizeof(cmdline_param)) != 0) { return false; }
/**
* Extracts device model (syno_hw_version=<string>) from kernel cmd line
*
* @param model pointer to save model to
* @param param_pointer currently processed token
* @return true on match, false if param didn't match
*/
static bool extract_hw(syno_hw *model, const char *param_pointer)
{
ensure_cmdline_param(CMDLINE_KT_HW);
if (strscpy((char *)model, param_pointer + strlen_static(CMDLINE_KT_HW), sizeof(syno_hw)) < 0)
pr_loc_wrn("HW version truncated to %zu", sizeof(syno_hw)-1);
pr_loc_dbg("HW version set to: %s", (char *)model);
return true;
}
/**
* Extracts serial number (sn=<string>) from kernel cmd line
*
* @param sn pointer to save s/n to
* @param param_pointer currently processed token
* @return true on match, false if param didn't match
*/
static bool extract_sn(serial_no *sn, const char *param_pointer)
{
ensure_cmdline_param(CMDLINE_KT_SN);
if(strscpy((char *)sn, param_pointer + strlen_static(CMDLINE_KT_SN), sizeof(serial_no)) < 0)
pr_loc_wrn("S/N truncated to %zu", sizeof(serial_no)-1);
pr_loc_dbg("S/N set to: %s", (char *)sn);
return true;
}
static bool extract_boot_media_type(struct boot_media *boot_media, const char *param_pointer)
{
ensure_cmdline_param(CMDLINE_KT_SATADOM);
char value = param_pointer[strlen_static(CMDLINE_KT_SATADOM)];
switch (value) {
case CMDLINE_KT_SATADOM_NATIVE:
boot_media->type = BOOT_MEDIA_SATA_DOM;
pr_loc_dbg("Boot media SATADOM (native) requested");
break;
case CMDLINE_KT_SATADOM_FAKE:
boot_media->type = BOOT_MEDIA_SATA_DISK;
pr_loc_dbg("Boot media SATADISK (fake) requested");
break;
case CMDLINE_KT_SATADOM_DISABLED:
//There's no point to set that option but it's not an error
pr_loc_wrn("SATA-based boot media disabled (default will be used, %s0 is a noop)", CMDLINE_KT_SATADOM);
break;
default:
pr_loc_err("Option \"%s%c\" is invalid (value should be 0/1/2)", CMDLINE_KT_SATADOM, value);
}
return true;
}
/**
* Extracts VID override (vid=<uint>) from kernel cmd line
*
* @param user_vid pointer to save VID
* @param param_pointer currently processed token
* @return true on match, false if param didn't match
*/
static bool extract_vid(device_id *user_vid, const char *param_pointer)
{
ensure_cmdline_param(CMDLINE_CT_VID);
long long numeric_param;
int tmp_call_res = kstrtoll(param_pointer + strlen_static(CMDLINE_CT_VID), 0, &numeric_param);
if (unlikely(tmp_call_res != 0)) {
pr_loc_err("Call to %s() failed => %d", "kstrtoll", tmp_call_res);
return true;
}
if (unlikely(numeric_param > VID_PID_MAX)) {
pr_loc_err("Cmdline %s is invalid (value larger than %d)", CMDLINE_CT_VID, VID_PID_MAX);
return true;
}
if (unlikely(*user_vid) != 0)
pr_loc_wrn(
"VID was already set to 0x%04x by a previous instance of %s - it will be changed now to 0x%04x",
*user_vid, CMDLINE_CT_VID, (unsigned int)numeric_param);
*user_vid = (unsigned int)numeric_param;
pr_loc_dbg("VID override: 0x%04x", *user_vid);
return true;
}
/**
* Extracts PID override (pid=<uint>) from kernel cmd line
*
* @param user_pid pointer to save PID
* @param param_pointer currently processed token
* @return true on match, false if param didn't match
*/
static bool extract_pid(device_id *user_pid, const char *param_pointer)
{
ensure_cmdline_param(CMDLINE_CT_PID);
long long numeric_param;
int tmp_call_res = kstrtoll(param_pointer + strlen_static(CMDLINE_CT_PID), 0, &numeric_param);
if (unlikely(tmp_call_res != 0)) {
pr_loc_err("Call to %s() failed => %d", "kstrtoll", tmp_call_res);
return true;
}
if (unlikely(numeric_param > VID_PID_MAX)) {
pr_loc_err("Cmdline %s is invalid (value larger than %d)", CMDLINE_CT_PID, VID_PID_MAX);
return true;
}
if (unlikely(*user_pid) != 0)
pr_loc_wrn(
"PID was already set to 0x%04x by a previous instance of %s - it will be changed now to 0x%04x",
*user_pid, CMDLINE_CT_PID, (unsigned int)numeric_param);
*user_pid = (unsigned int)numeric_param;
pr_loc_dbg("PID override: 0x%04x", *user_pid);
return true;
}
/**
* Extracts MFG mode enable switch (mfg<noval>) from kernel cmd line
*
* @param is_mfg_boot pointer to flag
* @param param_pointer currently processed token
* @return true on match, false if param didn't match
*/
static bool extract_mfg(bool *is_mfg_boot, const char *param_pointer)
{
ensure_cmdline_token(CMDLINE_CT_MFG);
*is_mfg_boot = true;
pr_loc_dbg("MFG boot requested");
return true;
}
/**
* Extracts maximum size of SATA DOM (dom_szmax=<number of MiB>) from kernel cmd line
*/
static bool extract_dom_max_size(struct boot_media *boot_media, const char *param_pointer)
{
ensure_cmdline_param(CMDLINE_CT_DOM_SZMAX);
long size_mib = simple_strtol(param_pointer + strlen_static(CMDLINE_CT_DOM_SZMAX), NULL, 10);
if (size_mib <= 0) {
pr_loc_err("Invalid maximum size of SATA DoM (\"%s=%ld\")", CMDLINE_CT_DOM_SZMAX, size_mib);
return true;
}
boot_media->dom_size_mib = size_mib;
pr_loc_dbg("Set maximum SATA DoM to %ld", size_mib);
return true;
}
/**
* Extracts MFG mode enable switch (syno_port_thaw=<1|0>) from kernel cmd line
*
* @param port_thaw pointer to flag
* @param param_pointer currently processed token
* @return true on match, false if param didn't match
*/
static bool extract_port_thaw(bool *port_thaw, const char *param_pointer)
{
ensure_cmdline_param(CMDLINE_KT_THAW);
short value = param_pointer[strlen_static(CMDLINE_KT_THAW)];
if (value == '0') {
*port_thaw = false;
goto out_found;
}
if (value == '1') {
*port_thaw = true;
goto out_found;
}
if (value == '\0') {
pr_loc_err("Option \"%s%d\" is invalid (value should be 0 or 1)", CMDLINE_KT_THAW, value);
return true;
}
out_found:
pr_loc_dbg("Port thaw set to: %d", port_thaw?1:0);
return true;
}
/**
* Extracts number of expected network interfaces (netif_num=<number>) from kernel cmd line
*
* @param netif_num pointer to save number
* @param param_pointer currently processed token
* @return true on match, false if param didn't match
*/
static bool extract_netif_num(unsigned short *netif_num, const char *param_pointer)
{
ensure_cmdline_param(CMDLINE_KT_NETIF_NUM);
short value = *(param_pointer + strlen_static(CMDLINE_KT_NETIF_NUM)) - 48; //ASCII: 0=48 and 9=57
if (value == 0) {
pr_loc_wrn("You specified no network interfaces (\"%s=0\")", CMDLINE_KT_NETIF_NUM);
return true;
}
if (value < 1 || value > 9) {
pr_loc_err("Invalid number of network interfaces set (\"%s%d\")", CMDLINE_KT_NETIF_NUM, value);
return true;
}
*netif_num = value;
pr_loc_dbg("Declared network ifaces # as %d", value);
return true;
}
/**
* Extracts network interfaces MAC addresses (mac1...mac4=<MAC> **OR** macs=<mac1,mac2,macN>)
*
* Note: mixing two notations may lead to undefined behaviors
*
* @param macs pointer to save macs
* @param param_pointer currently processed token
* @return true on match, false if param didn't match
*/
static bool extract_netif_macs(mac_address *macs[MAX_NET_IFACES], const char *param_pointer)
{
if (strncmp(param_pointer, CMDLINE_KT_MACS, strlen_static(CMDLINE_KT_MACS)) == 0) {
unsigned short i = 0;
const char *pBegin = param_pointer + strlen_static(CMDLINE_KT_MACS);
char *pEnd = strchr(pBegin, ',');
while (NULL != pEnd && MAX_NET_IFACES > i) {
*pEnd = '\0';
macs[i] = kmalloc(sizeof(mac_address), GFP_KERNEL);
if (unlikely(!macs[i])) {
pr_loc_crt("kernel memory alloc failure - tried to allocate %lu bytes for macs[%d]", sizeof(mac_address),
i);
goto out_found;
}
if(strscpy((char *)macs[i], pBegin, sizeof(mac_address)) < 0)
pr_loc_wrn("MAC #%d truncated to %zu", i+1, sizeof(mac_address)-1);
pr_loc_dbg("Set MAC #%d: %s", i+1, (char *)macs[i]);
pBegin = pEnd + 1;
pEnd = strchr(pBegin, ',');
i++;
}
if ('\0' != *pBegin && MAX_NET_IFACES > i) {
macs[i] = kmalloc(sizeof(mac_address), GFP_KERNEL);
if (unlikely(!macs[i])) {
pr_loc_crt("kernel memory alloc failure - tried to allocate %lu bytes for macs[%d]", sizeof(mac_address),
i);
goto out_found;
}
if(strscpy((char *)macs[i], pBegin, sizeof(mac_address)) < 0)
pr_loc_wrn("MAC #%d truncated to %zu", i+1, sizeof(mac_address)-1);
pr_loc_dbg("Set MAC #%d: %s", i+1, (char *)macs[i]);
}
goto out_found;
}
//mac1=...mac8= are valid options. ASCII for 1 is 49, ASCII for MAX_NET_IFACES is (49 + (MAX_NET_IFACES - 1)) # MAX_NET_IFACES must <=9
if (strncmp(param_pointer, "mac", 3) != 0 || *(param_pointer + 4) != '=' || *(param_pointer + 3) < 49 ||
*(param_pointer + 3) > (49 + (MAX_NET_IFACES - 1)))
return false;
//Find free spot
unsigned short i = 0;
for (; i < MAX_NET_IFACES; i++) {
if (macs[i])
continue;
macs[i] = kmalloc(sizeof(mac_address), GFP_KERNEL);
if (unlikely(!macs[i])) {
pr_loc_crt("kernel memory alloc failure - tried to allocate %lu bytes for macs[%d]", sizeof(mac_address),
i);
goto out_found;
}
if(strscpy((char *)macs[i], param_pointer + strlen_static(CMDLINE_KT_MAC1), sizeof(mac_address)) < 0)
pr_loc_wrn("MAC #%d truncated to %zu", i+1, sizeof(mac_address)-1);
pr_loc_dbg("Set MAC #%d: %s", i+1, (char *)macs[i]);
goto out_found;
}
pr_loc_err("You set more than MAC addresses! Only first %d will be honored.", MAX_NET_IFACES);
out_found:
return true;
}
static bool report_unrecognized_option(const char *param_pointer)
{
pr_loc_dbg("Option \"%s\" not recognized - ignoring", param_pointer);
return true;
}
/************************************************* End of extractors **************************************************/
static char cmdline_cache[CMDLINE_MAX] = { '\0' };
/**
* Extracts the cmdline from kernel and caches it for later use
*
* The method we use here may seem weird but it is, believe or not, the most direct one available. Kernel cmdline
* internally is stored in a "saved_command_line" variable (and few derivatives) which isn't exported for modules in
* any way (at least on x86). The only semi-direct way to get it is to call the method responsible for /proc/cmdline)
*/
static int extract_kernel_cmdline(void)
{
struct seq_file cmdline_itr = {
.buf = cmdline_cache,
.size = CMDLINE_MAX
};
int out = _cmdline_proc_show(&cmdline_itr, 0);
if (out != 0)
return out;
pr_loc_dbg("Cmdline count: %d", (unsigned int)cmdline_itr.count);
if (unlikely(cmdline_itr.count == CMDLINE_MAX)) //if the kernel line is >1K
pr_loc_wrn("Cmdline may have been truncated to %d", CMDLINE_MAX);
return 0;
}
/**
* Returns kernel cmdline up to the length specified by maxlen
*
* @param cmdline_out
* @param maxlen
* @return 0 on success, -E2BIG if buffer was too small (but the operation succeeded up to maxlen), -E for error
*/
long get_kernel_cmdline(char *cmdline_out, size_t maxlen)
{
if (unlikely(cmdline_cache[0] == '\0')) {
int out = extract_kernel_cmdline();
if (out != 0) {
pr_loc_err("Failed to extract kernel cmdline");
return out;
}
}
if (unlikely(maxlen > CMDLINE_MAX))
maxlen = CMDLINE_MAX;
return strscpy(cmdline_out, cmdline_cache, maxlen);
}
#define ADD_BLACKLIST_ENTRY(idx, token) kmalloc_or_exit_int(cmdline_blacklist[idx], sizeof(token)); \
strcpy((char *)cmdline_blacklist[idx], token); \
pr_loc_dbg("Add cmdline blacklist \"%s\" @ %d", \
(char *)cmdline_blacklist[idx], idx);
int populate_cmdline_blacklist(cmdline_token *cmdline_blacklist[MAX_BLACKLISTED_CMDLINE_TOKENS], syno_hw *model)
{
//Currently, this list is static. However, it's prepared to be dynamic based on the model
//Make sure you don't go over MAX_BLACKLISTED_CMDLINE_TOKENS (and if so adjust it)
ADD_BLACKLIST_ENTRY(0, CMDLINE_CT_VID);
ADD_BLACKLIST_ENTRY(1, CMDLINE_CT_PID);
ADD_BLACKLIST_ENTRY(2, CMDLINE_CT_MFG);
ADD_BLACKLIST_ENTRY(3, CMDLINE_CT_DOM_SZMAX);
ADD_BLACKLIST_ENTRY(4, CMDLINE_KT_ELEVATOR);
ADD_BLACKLIST_ENTRY(5, CMDLINE_KT_LOGLEVEL);
ADD_BLACKLIST_ENTRY(6, CMDLINE_KT_PK_BUFFER);
ADD_BLACKLIST_ENTRY(7, CMDLINE_KT_EARLY_PK);
ADD_BLACKLIST_ENTRY(8, CMDLINE_KT_THAW);
#ifndef NATIVE_SATA_DOM_SUPPORTED //on kernels without SATA DOM support we shouldn't reveal that it's a SATA DOM-boot
ADD_BLACKLIST_ENTRY(9, CMDLINE_KT_SATADOM);
#endif
return 0;
}
int extract_config_from_cmdline(struct runtime_config *config)
{
int out = 0;
char *cmdline_txt;
kzalloc_or_exit_int(cmdline_txt, strlen_to_size(CMDLINE_MAX));
if(get_kernel_cmdline(cmdline_txt, CMDLINE_MAX) <= 0) {
pr_loc_crt("Failed to extract cmdline");
out = -EIO;
goto exit_free;
}
pr_loc_dbg("Cmdline: %s", cmdline_txt);
/**
* Temporary variables
*/
unsigned int param_counter = 0;
char *single_param_chunk; //Pointer to the beginning of the cmdline token
DBG_ALLOW_UNUSED(param_counter);
while ((single_param_chunk = strsep(&cmdline_txt, CMDLINE_SEP)) != NULL ) {
if (unlikely(single_param_chunk[0] == '\0')) //Skip empty params (e.g. last one)
continue;
pr_loc_dbg("Param #%d: |%s|", param_counter++, single_param_chunk);
//Stop after the first one matches
extract_hw(&config->hw, single_param_chunk) ||
extract_sn(&config->sn, single_param_chunk) ||
extract_boot_media_type(&config->boot_media, single_param_chunk) ||
extract_vid(&config->boot_media.vid, single_param_chunk) ||
extract_pid(&config->boot_media.pid, single_param_chunk) ||
extract_dom_max_size(&config->boot_media, single_param_chunk) ||
extract_mfg(&config->boot_media.mfg_mode, single_param_chunk) ||
extract_port_thaw(&config->port_thaw, single_param_chunk) ||
extract_netif_num(&config->netif_num, single_param_chunk) ||
extract_netif_macs(config->macs, single_param_chunk) ||
report_unrecognized_option(single_param_chunk) ;
}
if (populate_cmdline_blacklist(config->cmdline_blacklist, &config->hw) != 0) {
out = -EIO;
goto exit_free;
}
pr_loc_inf("CmdLine processed successfully, tokens=%d", param_counter);
exit_free:
kfree(cmdline_txt);
return out;
}

31
config/cmdline_delegate.h Executable file
View File

@ -0,0 +1,31 @@
#ifndef REDPILLLKM_CMDLINE_DELEGATE_H
#define REDPILLLKM_CMDLINE_DELEGATE_H
#include "runtime_config.h"
#include "cmdline_opts.h"
/**
* Provides an easy access to kernel cmdline
*
* Internally in the kernel code it is available as "saved_command_line". However that variable is not accessible for
* modules. This function populates a char buffer with the cmdline extracted using other methods.
*
* WARNING: if something (e.g. sanitize cmdline) overrides the cmdline this method will return the overridden one!
* However, this method caches the cmdline, so if you call it once it will cache the original one internally.
*
* @param cmdline_out A pointer to your buffer to save the cmdline
* @param maxlen Your buffer space (in general you should use CMDLINE_MAX)
* @return cmdline length on success or -E on error
*/
long get_kernel_cmdline(char *cmdline_out, unsigned long maxlen);
/**
* Extracts & processes parameters from kernel cmdline
*
* Note: it's not guaranteed that the config will be valid. Check runtime_config.h.
*
* @param config pointer to save configuration
*/
int extract_config_from_cmdline(struct runtime_config *config);
#endif //REDPILLLKM_CMDLINE_DELEGATE_H

48
config/cmdline_opts.h Executable file
View File

@ -0,0 +1,48 @@
#ifndef REDPILL_CMDLINE_OPTS_H
#define REDPILL_CMDLINE_OPTS_H
#define CMDLINE_MAX 1024 //Max length of cmdline expected/processed; if longer a warning will be emitted
#define CMDLINE_SEP "\t\n "
/**
* Kernel command line tokens. For clarity keep them separated.
* CT = custom token
* KT = kernel token (default or syno)
*
* All should be defined in the .h to allow accessing outside for hints in errors.
*/
#define CMDLINE_CT_VID "vid=" //Boot media Vendor ID override
#define CMDLINE_CT_PID "pid=" //Boot media Product ID override
#define CMDLINE_CT_MFG "mfg" //VID & PID override will use force-reinstall VID/PID combo
#define CMDLINE_CT_DOM_SZMAX "dom_szmax=" //Max size of SATA device (MiB) to be considered a DOM (usually you should NOT use this)
//Standard Linux cmdline tokens
#define CMDLINE_KT_ELEVATOR "elevator=" //Sets I/O scheduler (we use it to load RP LKM earlier than normally possible)
#define CMDLINE_KT_LOGLEVEL "loglevel="
#define CMDLINE_KT_PK_BUFFER "log_buf_len=" //Length of the printk ring buffer (should usually be increased for debug)
#define CMDLINE_KT_EARLY_PK "earlyprintk"
//Syno-specific cmdline tokens
#define CMDLINE_KT_HW "syno_hw_version="
#define CMDLINE_KT_THAW "syno_port_thaw=" //??
//0|1 - whether to use native SATA Disk-on-Module for boot drive (syno); 2 - use fake/emulated SATA DOM (rp)
#define CMDLINE_KT_SATADOM "synoboot_satadom="
# define CMDLINE_KT_SATADOM_DISABLED '0'
# define CMDLINE_KT_SATADOM_NATIVE '1'
# define CMDLINE_KT_SATADOM_FAKE '2'
#define CMDLINE_KT_SN "sn="
#define CMDLINE_KT_NETIF_NUM "netif_num="
#define CMDLINE_KT_MACS "macs="
//You CANNOT simply add more macN= - DSM kernel only uses 4. If they ever support >4 you need to modify cmdline handling
#define CMDLINE_KT_MAC1 "mac1="
#define CMDLINE_KT_MAC2 "mac2="
#define CMDLINE_KT_MAC3 "mac3="
#define CMDLINE_KT_MAC4 "mac4="
#define CMDLINE_KT_MAC5 "mac5="
#define CMDLINE_KT_MAC6 "mac6="
#define CMDLINE_KT_MAC7 "mac7="
#define CMDLINE_KT_MAC8 "mac8="
#endif //REDPILL_CMDLINE_OPTS_H

126
config/platform_types.h Executable file
View File

@ -0,0 +1,126 @@
#ifndef REDPILL_PLATFORM_TYPES_H
#define REDPILL_PLATFORM_TYPES_H
#include "vpci_types.h" //vpci_device_stub, MAX_VPCI_DEVS
#ifndef RP_MODULE_TARGET_VER
#error "The RP_MODULE_TARGET_VER is not defined - it is required to properly set VTKs"
#endif
//All HWMON_SYS enums defined here are for internal RP use only. Normally these have long names but duplicating names
// across multiple platforms is wasteful (and causes platforms.h compilation unit to grow)
//While adding new constants here MAKE SURE TO NOT CONFLICT with existing ones defining names in synobios.h (here we
// postfixed everything with _ID)
enum hwmon_sys_thermal_zone_id {
//i.e. "non-existent zone" so that we don't need another flag/number to indicated # of supported zones
HWMON_SYS_TZONE_NULL_ID = 0,
HWMON_SYS_TZONE_REMOTE1_ID,
HWMON_SYS_TZONE_REMOTE2_ID,
HWMON_SYS_TZONE_LOCAL_ID,
HWMON_SYS_TZONE_SYSTEM_ID,
HWMON_SYS_TZONE_ADT1_LOC_ID,
HWMON_SYS_TZONE_ADT2_LOC_ID,
};
#define HWMON_SYS_THERMAL_ZONE_IDS 5 //number of thermal zones minus the fake NULL_ID
enum hwmon_sys_voltage_sensor_id {
//i.e. "non-existent sensor type" so that we don't need another flag/number to indicated # of supported ones
HWMON_SYS_VSENS_NULL_ID = 0,
HWMON_SYS_VSENS_VCC_ID,
HWMON_SYS_VSENS_VPP_ID,
HWMON_SYS_VSENS_V33_ID,
HWMON_SYS_VSENS_V5_ID,
HWMON_SYS_VSENS_V12_ID,
HWMON_SYS_VSENS_ADT1_V33_ID,
HWMON_SYS_VSENS_ADT2_V33_ID,
};
#define HWMON_SYS_VOLTAGE_SENSOR_IDS 7 //number of voltage sensors minus the fake NULL_ID
enum hwmon_sys_fan_rpm_id {
//i.e. "non-existent fan" so that we don't need another flag/number to indicated # of supported fans
HWMON_SYS_FAN_NULL_ID = 0,
HWMON_SYS_FAN1_ID,
HWMON_SYS_FAN2_ID,
HWMON_SYS_FAN3_ID,
HWMON_SYS_FAN4_ID,
};
#define HWMON_SYS_FAN_RPM_IDS 4
enum hwmon_sys_hdd_bp_id {
//i.e. "non-existent backplane sensor" so that we don't need another flag/number to indicated # of supported ones
HWMON_SYS_HDD_BP_NULL_ID = 0,
HWMON_SYS_HDD_BP_DETECT_ID,
HWMON_SYS_HDD_BP_ENABLE_ID,
};
#define HWMON_SYS_HDD_BP_IDS 2 //number of HDD backplane sensors minus the fake NULL_ID
enum hw_psu_sensor_id {
//i.e. "non-existent PSU sensor" so that we don't need another flag/number to indicated # of supported ones
HWMON_PSU_NULL_ID = 0,
HWMON_PSU_PWR_IN_ID,
HWMON_PSU_PWR_OUT_ID,
#if RP_MODULE_TARGET_VER == 6
HWMON_PSU_TEMP_ID,
#elif RP_MODULE_TARGET_VER == 7
HWMON_PSU_TEMP1_ID,
HWMON_PSU_TEMP2_ID,
HWMON_PSU_TEMP3_ID,
HWMON_PSU_FAN_VOLT,
#endif
HWMON_PSU_FAN_RPM_ID,
HWMON_PSU_STATUS_ID,
};
#if RP_MODULE_TARGET_VER == 6
#define HWMON_PSU_SENSOR_IDS 2 //number of power supply sensors minus the fake NULL_ID
#elif RP_MODULE_TARGET_VER == 7
#define HWMON_PSU_SENSOR_IDS 8 //number of power supply sensors minus the fake NULL_ID
#else
#error "Unknown RP_MODULE_TARGET_VER version specified"
#endif
enum hwmon_sys_current_id {
//i.e. "non-existent current sensor" so that we don't need another flag/number to indicated # of supported ones
HWMON_SYS_CURR_NULL_ID = 0,
HWMON_SYS_CURR_ADC_ID,
};
#define HWMON_SYS_CURRENT_IDS 1 //number of current sensors minus the fake NULL_ID
struct hw_config {
const char *name; //the longest so far is "RR36015xs+++" (12+1)
const struct vpci_device_stub pci_stubs[MAX_VPCI_DEVS];
//All custom flags
const bool emulate_rtc:1;
const bool swap_serial:1; //Whether ttyS0 and ttyS1 are swapped (reverses CONFIG_SYNO_X86_SERIAL_PORT_SWAP)
const bool reinit_ttyS0:1; //Should the ttyS0 be forcefully re-initialized after module loads
const bool fix_disk_led_ctrl:1; //Disabled libata-scsi bespoke disk led control (which often crashes some v4 platforms)
//See SYNO_HWMON_SUPPORT_ID in include/linux/synobios.h GPLed sources - it defines which ones are possible
//These define which parts of ACPI HWMON should be emulated
//For those with GetHwCapability() note enable DBG_HWCAP which will force bios_hwcap_shim to print original values.
// Unless there's a good reason to diverge from the platform-defined values you should not.
//Supported hwmon sensors; order of sensors within type IS IMPORTANT to be accurate with a real hardware. The number
// of sensors is derived from the enums defining their types. Internally the absolute maximum number is determined
// by MAX_SENSOR_NUM defined in include/linux/synobios.h
const bool has_cpu_temp:1; //GetHwCapability(id = CAPABILITY_CPU_TEMP)
// Device-tree models
const bool is_dt:1;
const struct hw_config_hwmon {
enum hwmon_sys_thermal_zone_id sys_thermal[HWMON_SYS_THERMAL_ZONE_IDS]; //GetHwCapability(id = CAPABILITY_THERMAL)
enum hwmon_sys_voltage_sensor_id sys_voltage[HWMON_SYS_VOLTAGE_SENSOR_IDS];
enum hwmon_sys_fan_rpm_id sys_fan_speed_rpm[HWMON_SYS_FAN_RPM_IDS]; //GetHwCapability(id = CAPABILITY_FAN_RPM_RPT)
enum hwmon_sys_hdd_bp_id hdd_backplane[HWMON_SYS_HDD_BP_IDS];
enum hw_psu_sensor_id psu_status[HWMON_PSU_SENSOR_IDS];
enum hwmon_sys_current_id sys_current[HWMON_SYS_CURRENT_IDS];
} hwmon;
};
#define platform_has_hwmon_thermal(hw_config_ptr) ((hw_config_ptr)->hwmon.sys_thermal[0] != HWMON_SYS_TZONE_NULL_ID)
#define platform_has_hwmon_voltage(hw_config_ptr) ((hw_config_ptr)->hwmon.sys_voltage[0] != HWMON_SYS_VSENS_NULL_ID)
#define platform_has_hwmon_fan_rpm(hw_config_ptr) ((hw_config_ptr)->hwmon.sys_fan_speed_rpm[0] != HWMON_SYS_FAN_NULL_ID)
#define platform_has_hwmon_hdd_bpl(hw_config_ptr) ((hw_config_ptr)->hwmon.hdd_backplane[0] != HWMON_SYS_HDD_BP_NULL_ID)
#define platform_has_hwmon_psu_status(hw_config_ptr) ((hw_config_ptr)->hwmon.psu_status[0] != HWMON_PSU_NULL_ID)
#define platform_has_hwmon_current_sens(hw_config_ptr) ((hw_config_ptr)->hwmon.sys_current[0] != HWMON_SYS_CURR_NULL_ID)
#endif //REDPILL_PLATFORM_TYPES_H

1047
config/platforms.h Executable file

File diff suppressed because it is too large Load Diff

246
config/runtime_config.c Executable file
View File

@ -0,0 +1,246 @@
#include "runtime_config.h"
#include "platforms.h"
#include "../common.h"
#include "cmdline_delegate.h"
#include "uart_defs.h"
struct runtime_config current_config = {
.hw = { '\0' },
.sn = { '\0' },
.boot_media = {
.type = BOOT_MEDIA_USB,
.mfg_mode = false,
.vid = VID_PID_EMPTY,
.pid = VID_PID_EMPTY,
.dom_size_mib = 1024, //usually the image will be used with ESXi and thus it will be ~100MB anyway
},
.port_thaw = true,
.netif_num = 0,
.macs = { '\0' },
.cmdline_blacklist = { '\0' },
.hw_config = NULL,
};
static inline bool validate_sn(const serial_no *sn) {
if (*sn[0] == '\0') {
pr_loc_err("Serial number is empty");
return false;
}
//TODO: add more validation here, probably w/model?
return true;
}
static __always_inline bool validate_boot_dev_usb(const struct boot_media *boot)
{
if (boot->vid == VID_PID_EMPTY && boot->pid == VID_PID_EMPTY) {
pr_loc_wrn("Empty/no \"%s\" and \"%s\" specified - first USB storage device will be used", CMDLINE_CT_VID,
CMDLINE_CT_PID);
return true; //this isn't necessarily an error (e.g. running under a VM with only a single USB port)
}
if (boot->vid == VID_PID_EMPTY) { //PID=0 is valid, but the VID is not
pr_loc_err("Empty/no \"%s\" specified", CMDLINE_CT_VID);
return false;
}
pr_loc_dbg("Configured boot device type to USB");
return true;
//not checking for >VID_PID_MAX as vid type is already ushort
}
static __always_inline bool validate_boot_dev_sata_dom(const struct boot_media *boot)
{
#ifndef NATIVE_SATA_DOM_SUPPORTED
pr_loc_err("Kernel you are running a kernel was built without SATA DoM support, you cannot use %s%c. "
"You can try booting with %s%c to enable experimental fake-SATA DoM.",
CMDLINE_KT_SATADOM, CMDLINE_KT_SATADOM_NATIVE,
CMDLINE_KT_SATADOM, CMDLINE_KT_SATADOM_FAKE);
return false;
#endif
if (boot->vid != VID_PID_EMPTY || boot->pid != VID_PID_EMPTY)
pr_loc_wrn("Using native SATA-DoM boot - %s and %s parameter values will be ignored",
CMDLINE_CT_VID, CMDLINE_CT_PID);
//this config is impossible as there's no equivalent for force-reinstall boot on SATA, so it's better to detect
//that rather than causing WTFs for someone who falsely assuming that it's possible
//However, it does work with fake-SATA boot (as it emulates USB disk anyway)
if (boot->mfg_mode) {
pr_loc_err("You cannot combine %s%c with %s - the OS supports force-reinstall on USB and fake SATA disk only",
CMDLINE_KT_SATADOM, CMDLINE_KT_SATADOM_NATIVE, CMDLINE_CT_MFG);
return false;
}
pr_loc_dbg("Configured boot device type to fake-SATA DOM");
return true;
}
static __always_inline bool validate_boot_dev_sata_disk(const struct boot_media *boot)
{
#ifdef NATIVE_SATA_DOM_SUPPORTED
pr_loc_wrn("The kernel you are running supports native SATA DoM (%s%c). You're currently using an experimental "
"fake-SATA DoM (%s%c) - consider switching to native SATA DoM (%s%c) for more stable operation.",
CMDLINE_KT_SATADOM, CMDLINE_KT_SATADOM_NATIVE,
CMDLINE_KT_SATADOM, CMDLINE_KT_SATADOM_FAKE,
CMDLINE_KT_SATADOM, CMDLINE_KT_SATADOM_NATIVE);
#endif
if (boot->vid != VID_PID_EMPTY || boot->pid != VID_PID_EMPTY)
pr_loc_wrn("Using fake SATA disk boot - %s and %s parameter values will be ignored",
CMDLINE_CT_VID, CMDLINE_CT_PID);
pr_loc_dbg("Configured boot device type to fake-SATA DOM");
return true;
}
static inline bool validate_boot_dev(const struct boot_media *boot)
{
switch (boot->type) {
case BOOT_MEDIA_USB:
return validate_boot_dev_usb(boot);
case BOOT_MEDIA_SATA_DOM:
return validate_boot_dev_sata_dom(boot);
case BOOT_MEDIA_SATA_DISK:
return validate_boot_dev_sata_disk(boot);
default:
pr_loc_bug("Got unknown boot type - did you forget to update %s after changing cmdline parsing?",
__FUNCTION__);
return false;
}
}
static inline bool validate_nets(const unsigned short if_num, mac_address * const macs[MAX_NET_IFACES])
{
size_t mac_len;
unsigned short macs_num = 0;
bool valid = true;
for (; macs_num < MAX_NET_IFACES; macs_num++) {
if (!macs[macs_num])
break; //You cannot have gaps in macs array
mac_len = strlen(*macs[macs_num]);
if (mac_len != MAC_ADDR_LEN) {
pr_loc_err("MAC address \"%s\" is invalid (expected %d characters, found %zu)", *macs[macs_num], MAC_ADDR_LEN,
mac_len);
valid = false;
} //else if validate if the MAC is actually semi-valid
}
if (if_num == 0) {
pr_loc_wrn("Number of defined interfaces (\"%s\") is not specified or empty", CMDLINE_KT_NETIF_NUM);
}
if (macs_num == 0) {
pr_loc_wrn("No MAC addressed are specified - use \"%s\" or \"%s\"...\"%s\" to set them", CMDLINE_KT_MACS,
CMDLINE_KT_MAC1, CMDLINE_KT_MAC8);
}
if (if_num != macs_num) {
pr_loc_err("Number of defined interfaces (\"%s%d\") is not equal to the number of MAC addresses found (%d)",
CMDLINE_KT_NETIF_NUM, if_num, macs_num);
valid = false;
}
return valid;
}
/**
* This function validates consistency of the currently loaded platform config with the current environment
*
* Some options don't make sense unless the kernel was built with some specific configuration. This function aims to
* detect common pitfalls in platforms configuration. This doesn't so much validate the platform definition per se
* (but partially too) but the match between platform config chosen vs. kernel currently attempting to run that
* platform.
*/
static inline bool validate_platform_config(const struct hw_config *hw)
{
#ifdef UART_BUG_SWAPPED
const bool kernel_serial_swapped = true;
#else
const bool kernel_serial_swapped = false;
#endif
//This will not prevent the code from working, so it's not an error state by itself
if (unlikely(hw->swap_serial && !kernel_serial_swapped))
pr_loc_bug("Your kernel indicates COM1 & COM2 ARE NOT swapped but your platform specifies swapping");
else if(unlikely(!hw->swap_serial && kernel_serial_swapped))
pr_loc_bug("Your kernel indicates COM1 & COM2 ARE swapped but your platform specifies NO swapping");
return true;
}
static int populate_hw_config(struct runtime_config *config)
{
//We cannot run with empty model or model which didn't match
if (config->hw[0] == '\0') {
pr_loc_crt("Empty model, please set \"%s\" parameter", CMDLINE_KT_HW);
return -ENOENT;
}
for (int i = 0; i < ARRAY_SIZE(supported_platforms); i++) {
if (strcmp(supported_platforms[i].name, (char *)config->hw) != 0)
continue;
pr_loc_dbg("Found platform definition for \"%s\"", config->hw);
config->hw_config = &supported_platforms[i];
return 0;
}
pr_loc_crt("The model set using \"%s%s\" is not valid", CMDLINE_KT_HW, config->hw);
return -EINVAL;
}
static bool validate_runtime_config(const struct runtime_config *config)
{
pr_loc_dbg("Validating runtime config...");
bool valid = true;
valid &= validate_sn(&config->sn);
valid &= validate_boot_dev(&config->boot_media);
valid &= validate_nets(config->netif_num, config->macs);
valid &= validate_platform_config(config->hw_config);
pr_loc_dbg("Config validation resulted in %s", valid ? "OK" : "ERR");
//if (valid) {
return 0;
//} else {
// pr_loc_err("Config validation FAILED");
// return -EINVAL;
//}
}
int populate_runtime_config(struct runtime_config *config)
{
int out = 0;
if ((out = populate_hw_config(config)) != 0 || (out = validate_runtime_config(config)) != 0) {
pr_loc_err("Failed to populate runtime config!");
return out;
}
pr_loc_inf("Runtime config populated");
return out;
}
void free_runtime_config(struct runtime_config *config)
{
for (int i = 0; i < MAX_NET_IFACES; i++) {
if (config->macs[i]) {
pr_loc_dbg("Free MAC%d @ %p", i, config->macs[i]);
kfree(config->macs[i]);
}
}
for (int i = 0; i < MAX_BLACKLISTED_CMDLINE_TOKENS; i++) {
if (config->cmdline_blacklist[i]) {
pr_loc_dbg("Free cmdline blacklist entry %d @ %p", i, config->cmdline_blacklist[i]);
kfree(config->cmdline_blacklist[i]);
}
}
pr_loc_inf("Runtime config freed");
}

71
config/runtime_config.h Executable file
View File

@ -0,0 +1,71 @@
#ifndef REDPILLLKM_RUNTIME_CONFIG_H
#define REDPILLLKM_RUNTIME_CONFIG_H
#include "uart_defs.h" //UART config values
#include <linux/types.h> //bool
//These below are currently known runtime limitations
#define MAX_NET_IFACES 8
#define MAC_ADDR_LEN 12
#define MAX_BLACKLISTED_CMDLINE_TOKENS 10
#ifdef CONFIG_SYNO_BOOT_SATA_DOM
#define NATIVE_SATA_DOM_SUPPORTED //whether SCSI sd.c driver supports native SATA DOM
#endif
//UART-related constants were moved to uart_defs.h, to allow subcomponents to importa a smaller subset than this header
#define MODEL_MAX_LENGTH 10
#define SN_MAX_LENGTH 13
#define VID_PID_EMPTY 0x0000
#define VID_PID_MAX 0xFFFF
typedef unsigned short device_id;
typedef char syno_hw[MODEL_MAX_LENGTH + 1];
typedef char mac_address[MAC_ADDR_LEN + 1];
typedef char serial_no[SN_MAX_LENGTH + 1];
typedef char cmdline_token[];
enum boot_media_type {
BOOT_MEDIA_USB,
BOOT_MEDIA_SATA_DOM,
BOOT_MEDIA_SATA_DISK,
};
struct boot_media {
enum boot_media_type type; // Default: BOOT_MEDIA_USB <valid>
//USB only options
bool mfg_mode; //emulate mfg mode (valid for USB boot only). Default: false <valid>
device_id vid; //Vendor ID of device containing the loader. Default: empty <valid, use first>
device_id pid; //Product ID of device containing the loader. Default: empty <valid, use first>
//SATA only options
unsigned long dom_size_mib; //Max size of SATA DOM Default: 1024 <valid, READ native_sata_boot_shim.c!!!>
};
struct hw_config;
struct runtime_config {
syno_hw hw; //used to determine quirks. Default: empty <invalid>
serial_no sn; //Used to validate it and warn the user. Default: empty <invalid>
struct boot_media boot_media;
bool port_thaw; //Currently unknown. Default: true <valid>
unsigned short netif_num; //Number of eth interfaces. Default: 0 <invalid>
mac_address *macs[MAX_NET_IFACES]; //MAC addresses of eth interfaces. Default: [] <invalid>
cmdline_token *cmdline_blacklist[MAX_BLACKLISTED_CMDLINE_TOKENS];// Default: []
const struct hw_config *hw_config;
};
extern struct runtime_config current_config;
/**
* Takes a raw extracted config and "shakes it a little bit" by validating things & constructing dependent structures
*
* Warning: if this function returns false YOU MUST NOT trust the config structure. Other code WILL break as it assumes
* the config is valid (e.g. doesn't have null ptrs which this function generates).
* Also, after you call this function you should call free_runtime_config() to clear up memory reservations.
*/
int populate_runtime_config(struct runtime_config *config);
void free_runtime_config(struct runtime_config *config);
#endif //REDPILLLKM_RUNTIME_CONFIG_H

50
config/uart_defs.h Executable file
View File

@ -0,0 +1,50 @@
/**
* This file is meant to be small and portable. It can be included by other parts of the module wishing to get some info
* about UARTs. It should not contain any extensive definitions or static structures reservation. It is mostly
* extracting information buried in the Linux serial subsystem into usable constants.
*/
#ifndef REDPILL_UART_DEFS_H
#define REDPILL_UART_DEFS_H
#include <asm/serial.h> //flags for pc_com*
#include <linux/serial_core.h> //struct uart_port
#include <linux/version.h> //KERNEL_VERSION()
//These definitions are taken from asm/serial.h for a normal (i.e. non-swapped) UART1/COM1 port on an x86 PC
#define STD_COM1_IOBASE 0x3f8
#define STD_COM1_IRQ 4
#define STD_COM2_IOBASE 0x2f8
#define STD_COM2_IRQ 3
#define STD_COM3_IOBASE 0x3e8
#define STD_COM3_IRQ 4
#define STD_COM4_IOBASE 0x2e8
#define STD_COM4_IRQ 3
//They changed name of flags const: https://github.com/torvalds/linux/commit/196cf358422517b3ff3779c46a1f3e26fb084172
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,18,0)
#define STD_COMX_FLAGS STD_COM_FLAGS
#endif
#define STD_COMX_BAUD BASE_BAUD
#define STD_COMX_DEV_NAME "ttyS"
#define SRD_COMX_BAUD_OPTS "115200n8"
#define UART_NR CONFIG_SERIAL_8250_NR_UARTS
#define SERIAL8250_LAST_ISA_LINE (UART_NR-1) //max valid index of ttyS
#define SERIAL8250_SOFT_IRQ 0 //a special IRQ value which, if set on a port, will force 8250 driver to use timers
#ifdef CONFIG_SYNO_X86_SERIAL_PORT_SWAP
#define UART_BUG_SWAPPED //indicates that first two UARTs are swapped (sic!). Yes, we do consider it a fucking bug.
#endif
// CONFIG_SYNO_FIX_TTYS_FUNCTIONS=y
// CONFIG_SYNO_TTYS_FUN_NUM=2
#ifdef CONFIG_SYNO_TTYS_FUN_NUM
#define UART_SYNO_TTYS_FUN_NUM CONFIG_SYNO_TTYS_FUN_NUM // 看着像群晖独占的口数 模拟时直接跳过
#else
#define UART_SYNO_TTYS_FUN_NUM 0
#endif
#endif //REDPILL_UART_DEFS_H

18
config/vpci_types.h Executable file
View File

@ -0,0 +1,18 @@
#ifndef REDPILL_VPCI_LIMITS_H
#define REDPILL_VPCI_LIMITS_H
#include "../shim/pci_shim.h" //pci_shim_device_type
//Defines below are experimentally determined to be sufficient but can often be changed
#define MAX_VPCI_BUSES 8 //adjust if needed, max 256
#define MAX_VPCI_DEVS 16 //adjust if needed, max 256*32=8192
struct vpci_device_stub {
enum pci_shim_device_type type;
u8 bus;
u8 dev;
u8 fn;
bool multifunction:1;
};
#endif //REDPILL_VPCI_LIMITS_H

135
debug/debug_execve.c Executable file
View File

@ -0,0 +1,135 @@
#include "debug_execve.h"
#include "../common.h"
#include <linux/sched.h> //task_struct
#include <asm/uaccess.h> //get_user
#include <linux/compat.h> //compat_uptr_t
#include <linux/binfmts.h> //MAX_ARG_STRINGS
/*
* Struct copied 1:1 from:
*
* linux/fs/exec.c
*
* Copyright (C) 1991, 1992 Linus Torvalds
*/
struct user_arg_ptr {
#ifdef CONFIG_COMPAT
bool is_compat;
#endif
union {
const char __user *const __user *native;
#ifdef CONFIG_COMPAT
const compat_uptr_t __user *compat;
#endif
} ptr;
};
/*
* Function copied 1:1 from:
*
* linux/fs/exec.c
*
* Copyright (C) 1991, 1992 Linus Torvalds
*/
static const char __user *get_user_arg_ptr(struct user_arg_ptr argv, int nr)
{
const char __user *native;
#ifdef CONFIG_COMPAT
if (unlikely(argv.is_compat)) {
compat_uptr_t compat;
if (get_user(compat, argv.ptr.compat + nr))
return ERR_PTR(-EFAULT);
return compat_ptr(compat);
}
#endif
if (get_user(native, argv.ptr.native + nr))
return ERR_PTR(-EFAULT);
return native;
}
/*
* Modified for simplicity from count() in:
*
* linux/fs/exec.c
*
* Copyright (C) 1991, 1992 Linus Torvalds
*/
static int count_args(struct user_arg_ptr argv)
{
if (argv.ptr.native == NULL)
return 0;
int i = 0;
for (;;) {
const char __user *p = get_user_arg_ptr(argv, i);
if (!p)
break;
if (IS_ERR(p) || i >= MAX_ARG_STRINGS)
return -EFAULT;
++i;
if (fatal_signal_pending(current))
return -ERESTARTNOHAND;
cond_resched();
}
return i;
}
static void inline fixup_arg_str(char *arg_ptr, int cur_argc, const char *what)
{
pr_loc_wrn("Failed to copy %d arg - %s failed", cur_argc, what);
memcpy(arg_ptr, "..?\0", 4);
}
void RPDBG_print_execve_call(const char *filename, const char __user *const __user *argv)
{
struct task_struct *caller = get_cpu_var(current_task);
struct user_arg_ptr argv_up = { .ptr.native = argv };
int argc = count_args(argv_up);
char *arg_str = kzalloc(MAX_ARG_STRLEN, GFP_KERNEL);
if (unlikely(!arg_str)) {
pr_loc_crt("kzalloc failed");
return;
}
char *arg_ptr = &arg_str[0];
for (int i = 0; i < argc; i++) {
const char __user *p = get_user_arg_ptr(argv_up, i);
if (IS_ERR(p)) {
fixup_arg_str(arg_ptr, i, "get_user_arg_ptr");
goto out_free;
}
int len = strnlen_user(p, MAX_ARG_STRLEN); //includes nullbyte
if (!len) {
fixup_arg_str(arg_ptr, i, "strnlen_user");
goto out_free;
}
--len; //we want to copy without nullbyte as we handle it ourselves while attaching to arg_ptr
if (copy_from_user(arg_ptr, p, len)) {
fixup_arg_str(arg_ptr, i, "copy_from_user");
goto out_free;
}
arg_ptr += len;
*arg_ptr = (i + 1 == argc) ? '\0' : ' '; //separate by spaces UNLESS it's the last argument
++arg_ptr;
}
out_free:
pr_loc_dbg("execve@cpu%d: %s[%d]=>%s[%d] {%s}", caller->on_cpu, caller->comm, caller->pid, filename, argc, arg_str);
kfree(arg_str);
}

6
debug/debug_execve.h Executable file
View File

@ -0,0 +1,6 @@
#ifndef REDPILL_DEBUG_EXECVE_H
#define REDPILL_DEBUG_EXECVE_H
void RPDBG_print_execve_call(const char *filename, const char *const *argv);
#endif //REDPILL_DEBUG_EXECVE_H

77
debug/debug_vuart.h Executable file
View File

@ -0,0 +1,77 @@
#ifndef REDPILL_DEBUG_VUART_H
#define REDPILL_DEBUG_VUART_H
//Whether the code will print all internal state changes
#ifdef VUART_DEBUG_LOG
//Main print macro used everywhere below
#define uart_prdbg(f, ...) pr_loc_dbg(f, ##__VA_ARGS__)
#define reg_read(rN) uart_prdbg("Reading " rN " registry");
#define reg_write(rN) uart_prdbg("Writing " rN " registry");
#define reg_read_dump(d, rF, rN) reg_read(rN); dump_##rF(d);
#define reg_write_dump(d, rF, rN) reg_write(rN); dump_##rF(d);
#define dri(vdev, reg, flag) ((vdev)->reg&(flag)) ? 1:0 //Dump Register as 1-0 Integer
#define diiri(vdev, flag) (((vdev)->iir&UART_IIR_ID) == (flag)) ? 1:0 //Dump IIR Interrupt type as 1-0 integer
#define dump_ier(d) \
uart_prdbg("IER[0x%02x]: DR_int=%d | THRe_int=%d | RLS_int=%d | " \
"MS_int=%d", \
(d)->ier, dri(d,ier,UART_IER_RDI), dri(d,ier,UART_IER_THRI), dri(d,ier,UART_IER_RLSI), \
dri(d,ier,UART_IER_MSI));
//Be careful interpreting the result of this macro - no_int_pend means "no interrupts pending" (so 0 if there are
// pending interrupts and 1 if there are no interrupts pending); see Table 3-5 in TI doc
//Also FIFO flags are slightly weird (it's 2 bit, see IIR table in https://en.wikibooks.org/wiki/Serial_Programming/8250_UART_Programming)
// so fifoen=0_0 means "FIFO disabled", fifoen=1_1 means "FIFO enabled", and fifoen=0_1 means "FIFO enabled & broken"
//Also, since MSI is 0-0-0 it's a special-ish case: it's only considered enabled when int is pending and all bits are 0
#define dump_iir(d) \
uart_prdbg("IIR/ISR[0x%02x]: no_int_pend=%d | int_MS=%d | " \
"int_THRe=%d | int_DR=%d | int_RLS=%d | " \
"fifoen=%d_%d", \
(d)->iir, dri(d,iir,UART_IIR_NO_INT), (!((d)->iir&UART_IIR_NO_INT)&&((d)->iir&UART_IIR_ID)==UART_IIR_MSI)?1:0, \
diiri(d,UART_IIR_THRI), diiri(d,UART_IIR_RDI), diiri(d,UART_IIR_RLSI), \
(((d)->iir & UART_IIR_FIFEN_B6)?1:0), (((d)->iir & UART_IIR_FIFEN_B7)?1:0));
#define dump_fcr(d) \
uart_prdbg("FCR[0x%02x]: FIFOon=%d | RxFIFOrst=%d | " \
"TxFIFOrst=%d | EnDMAend=%d", \
(d)->fcr, dri(d,fcr,UART_FCR_ENABLE_FIFO), dri(d,fcr,UART_FCR_CLEAR_RCVR), \
dri(d,fcr,UART_FCR_CLEAR_XMIT), dri(d,fcr,UART_FCR_DMA_SELECT));
#define dump_lcr(d) \
uart_prdbg("LCR[0x%02x]: Stop=%d | PairEN=%d | EvenP=%d | " \
"ForcPair=%d | SetBrk=%d | DLAB=%d", \
(d)->lcr, dri(d,lcr,UART_LCR_STOP), dri(d,lcr,UART_LCR_PARITY), dri(d,lcr,UART_LCR_EPAR), \
dri(d,lcr,UART_LCR_SPAR), dri(d,lcr,UART_LCR_SBC), dri(d,lcr,UART_LCR_DLAB));
#define dump_mcr(d) \
uart_prdbg("MCR[0x%02x]: DTR=%d | RTS=%d | Out1=%d | " \
"Out2/IntE=%d | Loop=%d", \
(d)->mcr, dri(d,mcr,UART_MCR_DTR), dri(d,mcr,UART_MCR_RTS), dri(d,mcr,UART_MCR_OUT1), \
dri(d,mcr,UART_MCR_OUT2), dri(d,mcr,UART_MCR_LOOP));
#define dump_lsr(d) \
uart_prdbg("LSR[0x%02x]: data_ready=%d | ovrunE=%d | pairE=%d | " \
"frE=%d | break_req=%d | THRemp=%d | TransEMP=%d | " \
"FIFOdE=%d", \
(d)->lsr, dri(d,lsr,UART_LSR_DR), dri(d,lsr,UART_LSR_OE), dri(d,lsr,UART_LSR_PE), \
dri(d,lsr,UART_LSR_FE), dri(d,lsr,UART_LSR_BI), dri(d,lsr,UART_LSR_THRE), dri(d,lsr,UART_LSR_TEMT), \
dri(d,lsr,UART_LSR_FIFOE));
#define dump_msr(d) \
uart_prdbg("MSR[0x%02x]: delCTS=%d | delDSR=%d | trEdgRI=%d | " \
"delCD=%d | CTS=%d | DSR=%d | RI=%d | " \
"DCD=%d", \
(d)->msr, dri(d,msr,UART_MSR_DCTS), dri(d,msr,UART_MSR_DDSR), dri(d,msr,UART_MSR_TERI), \
dri(d,msr,UART_MSR_DDCD), dri(d,msr,UART_MSR_CTS), dri(d,msr,UART_MSR_DSR), dri(d,msr,UART_MSR_RI), \
dri(d,msr,UART_MSR_DCD));
#else //VUART_DEBUG_LOG disabled \/
#define uart_prdbg(f, ...) { /* noop */ }
#define reg_read(rN) { /* noop */ }
#define reg_write(rN) { /* noop */ }
#define reg_read_dump(d, rF, rN) { /* noop */ }
#define reg_write_dump(d, rF, rN) { /* noop */ }
#define dump_ier(d) { /* noop */ }
#define dump_iir(d) { /* noop */ }
#define dump_fcr(d) { /* noop */ }
#define dump_lcr(d) { /* noop */ }
#define dump_mcr(d) { /* noop */ }
#define dump_lsr(d) { /* noop */ }
#define dump_msr(d) { /* noop */ }
#endif //VUART_DEBUG_LOG
#endif //REDPILL_DEBUG_VUART_H

113
internal/call_protected.c Executable file
View File

@ -0,0 +1,113 @@
#include "call_protected.h"
#include "../common.h"
#include <linux/errno.h> //common exit codes
//#include <linux/kallsyms.h> //kallsyms_lookup_name()
#include "helper/symbol_helper.h" //kln_func
#include <linux/module.h> //symbol_get()/put
//This will eventually stop working (since Linux >=5.7.0 has the kallsyms_lookup_name() removed)
//Workaround will be needed: https://github.com/xcellerator/linux_kernel_hacking/issues/3
#define __VOID_RETURN__
//This macro should be used to export symbols which aren't normally EXPORT_SYMBOL/EXPORT_SYMBOL_GPL in the kernel but
// they exist within the kernel (and not a loadable module!). Keep in mind that most of the time "static" cannot be
// reexported using this trick.
//All re-exported function will have _ prefix (e.g. foo() becomes _foo())
#define DEFINE_UNEXPORTED_SHIM(return_type, org_function_name, call_args, call_vars, fail_return) \
extern asmlinkage return_type org_function_name(call_args); \
typedef typeof(org_function_name) *org_function_name##__ret; \
static unsigned long org_function_name##__addr = 0; \
return_type _##org_function_name(call_args) \
{ \
if (unlikely(org_function_name##__addr == 0)) { \
org_function_name##__addr = kln_func(#org_function_name); \
if (org_function_name##__addr == 0) { \
pr_loc_bug("Failed to fetch %s() syscall address", #org_function_name); \
return fail_return; \
} \
pr_loc_dbg("Got addr %lx for %s", org_function_name##__addr, #org_function_name); \
} \
\
return ((org_function_name##__ret)org_function_name##__addr)(call_vars); \
}
//This macro should be used to export symbols which aren't normally EXPORT_SYMBOL/EXPORT_SYMBOL_GPL in the kernel but
// they exist within the kernel and are defined as __init. These symbol can only be called when the system is still
// booting (i.e. before init user-space binary was called). After that calling such functions is a lottery - the memory
// of them is freed by free_initmem() [called in main.c:kernel_init()]. That's why we skip any caching kere as these are
// called mostly as a one-off during boot process when this module was loaded as a I/O scheduler.
//All re-exported function will have _ prefix (e.g. foo() becomes _foo())
#define DEFINE_UNEXPORTED_INIT_SHIM(return_type, org_function_name, call_args, call_vars, fail_return) \
extern asmlinkage return_type org_function_name(call_args); \
typedef typeof(org_function_name) *org_function_name##__ret; \
return_type _##org_function_name(call_args) \
{ \
unsigned long org_function_name##__addr = 0; \
if (unlikely(!is_system_booting())) { \
pr_loc_bug("Attempted to call %s() when the system is already booted (state=%d)", \
#org_function_name, system_state); \
return fail_return; \
} \
org_function_name##__addr = kln_func(#org_function_name); \
if (org_function_name##__addr == 0) { \
pr_loc_bug("Failed to fetch %s() syscall address", #org_function_name); \
return fail_return; \
} \
pr_loc_dbg("Got addr %lx for %s", org_function_name##__addr, #org_function_name); \
\
return ((org_function_name##__ret)org_function_name##__addr)(call_vars); \
}
//This macro should be used to export symbols which are normally exported by modules in situations where this module
// must be loaded before such module exporting the symbol.
//Normally if symbol for module "X" is used in "Y" the kernel will complain that "X" muse be loaded before "Y".
//All re-exported function will have _ prefix (e.g. foo() becomes _foo())
#define DEFINE_DYNAMIC_SHIM(return_type, org_function_name, call_args, call_vars, fail_return) \
extern asmlinkage return_type org_function_name(call_args); \
typedef typeof(org_function_name) *org_function_name##__ret; \
return_type _##org_function_name(call_args) \
{ \
org_function_name##__ret org_function_name##__ptr = (org_function_name##__ret)__symbol_get(#org_function_name); \
if (!org_function_name##__ptr) { \
pr_loc_bug("Failed to fetch %s() symbol (is that module loaded?)", #org_function_name); \
return fail_return; \
} \
pr_loc_dbg("Got ptr %p for %s", org_function_name##__ptr, #org_function_name); \
/*Doing this BEFORE the call makes a TINY window where the symbol can "escape" but it's protects from deadlock*/\
__symbol_put(#org_function_name); \
\
return ((org_function_name##__ret)org_function_name##__ptr)(call_vars); \
}
//********************************************************************************************************************//
DEFINE_UNEXPORTED_SHIM(int, cmdline_proc_show, CP_LIST(struct seq_file *m, void *v), CP_LIST(m, v), -EFAULT);
DEFINE_UNEXPORTED_SHIM(void, flush_tlb_all, CP_LIST(void), CP_LIST(), __VOID_RETURN__);
//See header file for detailed explanation what's going on here as it's more complex than a single commit
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0)
DEFINE_UNEXPORTED_SHIM(int, do_execve, CP_LIST(const char *filename,
const char __user *const __user *__argv,
const char __user *const __user *__envp), CP_LIST(filename, __argv, __envp), -EINTR);
#ifndef CONFIG_AUDITSYSCALL
DEFINE_UNEXPORTED_SHIM(void, final_putname, CP_LIST(struct filename *name), CP_LIST(name), __VOID_RETURN__);
#else
DEFINE_UNEXPORTED_SHIM(void, putname, CP_LIST(struct filename *name), CP_LIST(name), __VOID_RETURN__);
#endif
#else
DEFINE_UNEXPORTED_SHIM(int, do_execve, CP_LIST(struct filename *filename,
const char __user *const __user *__argv,
const char __user *const __user *__envp), CP_LIST(filename, __argv, __envp), -EINTR);
DEFINE_UNEXPORTED_SHIM(struct filename *, getname, CP_LIST(const char __user *name), CP_LIST(name), ERR_PTR(-EFAULT));
#endif
DEFINE_UNEXPORTED_SHIM(int, scsi_scan_host_selected, CP_LIST(struct Scsi_Host *shost, unsigned int channel, unsigned int id, u64 lun, int rescan), CP_LIST(shost, channel, id, lun, rescan), -EIO);
DEFINE_UNEXPORTED_SHIM(int, ida_pre_get, CP_LIST(struct ida *ida, gfp_t gfp_mask), CP_LIST(ida, gfp_mask), -EINVAL);
DEFINE_UNEXPORTED_SHIM(int, early_serial_setup, CP_LIST(struct uart_port *port), port, -EIO);
DEFINE_UNEXPORTED_SHIM(int, serial8250_find_port, CP_LIST(struct uart_port *p), CP_LIST(p), -EIO);
DEFINE_UNEXPORTED_INIT_SHIM(int, elevator_setup, CP_LIST(char *str), CP_LIST(str), -EINVAL);
DEFINE_DYNAMIC_SHIM(void, usb_register_notify, CP_LIST(struct notifier_block *nb), CP_LIST(nb), __VOID_RETURN__);
DEFINE_DYNAMIC_SHIM(void, usb_unregister_notify, CP_LIST(struct notifier_block *nb), CP_LIST(nb), __VOID_RETURN__);

77
internal/call_protected.h Executable file
View File

@ -0,0 +1,77 @@
#ifndef REDPILLLKM_CALL_PROTECTED_H
#define REDPILLLKM_CALL_PROTECTED_H
#include <linux/version.h> //LINUX_VERSION_CODE, KERNEL_VERSION
#include <linux/types.h> //bool
#include <linux/kernel.h> //system_states & system_state
// *************************************** Useful macros *************************************** //
//Check if the system is still in booting stage (useful when you want to call __init functions as they're deleted)
#define is_system_booting() (system_state == SYSTEM_BOOTING)
// ************************** Exports of normally protected functions ************************** //
//A usual macros to make defining them easier & consistent with .c implementation
#define CP_LIST(...) __VA_ARGS__ //used to pass a list of arguments as a single argument
#define CP_DECLARE_SHIM(return_type, org_function_name, call_args) return_type _##org_function_name(call_args);
struct seq_file;
CP_DECLARE_SHIM(int, cmdline_proc_show, CP_LIST(struct seq_file *m, void *v)); //extracts kernel cmdline
CP_DECLARE_SHIM(void, flush_tlb_all, CP_LIST(void)); //used to flush caches in memory.c operations
/* Thanks Jeff... https://groups.google.com/g/kernel-meetup-bangalore/c/rvQccTl_3kc/m/BJCnnXGCAgAJ
* In case the link disappears: Jeff Layton from RedHat decided to just nuke the getname() API after 7 years of it being
* exposed in the kernel. So in practice we need to use kallsyms to get it on kernels >=3.14 (up to current 5.14)
* See https://github.com/torvalds/linux/commit/9115eac2c788c17b57c9256cb322fa7371972ddf
* Another unrelated change which happened in v3.14 was that when "struct filename*" is passed the callee is responsible
* for freeing it (using putname()). However, in older versions we (the caller) needs to free it
* See https://github.com/torvalds/linux/commit/c4ad8f98bef77c7356aa6a9ad9188a6acc6b849d
*
* This whole block deals with functions needed for execve() shimming
*/
struct filename;
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0)
CP_DECLARE_SHIM(int, do_execve, CP_LIST(const char *filename,
const char __user *const __user *__argv,
const char __user *const __user *__envp));
#include <linux/fs.h>
#define _getname(...) getname(__VA_ARGS__)
//If syscall audit is disabled putname is an alias to final_putname(), see include/linux/fs.h; later on this was changed
// but this branch needs to handle only <3.14 as we don't need (and shouldn't use!) putname() in >=3.14
#ifndef CONFIG_AUDITSYSCALL //if CONFIG_AUDITSYSCALL disabled we unexport final_putname and add putname define line fs.h
#define _putname(name) _final_putname(name)
CP_DECLARE_SHIM(void, final_putname, CP_LIST(struct filename *name));
#else //if the CONFIG_AUDITSYSCALL is enabled we need to proxy to traced putname to make sure references are counted
CP_DECLARE_SHIM(void, putname, CP_LIST(struct filename *name));
#endif
#else
CP_DECLARE_SHIM(int, do_execve, CP_LIST(struct filename *filename,
const char __user *const __user *__argv,
const char __user *const __user *__envp));
CP_DECLARE_SHIM(struct filename *, getname, CP_LIST(const char __user *name));
#endif
//The following functions are used by vUART and uart_fixer
typedef struct uart_port *uart_port_p;
CP_DECLARE_SHIM(int, early_serial_setup, CP_LIST(struct uart_port *port));
CP_DECLARE_SHIM(int, serial8250_find_port, CP_LIST(struct uart_port *p));
//Exported so that we can forcefully rescan the SCSI host in scsi_toolbox. This operation is normally available in
// userland when you're a root, but somehow they missed an export for kernel code (which according to kernel rules is a
// bug, but probably nobody asked before)
struct Scsi_Host;
CP_DECLARE_SHIM(int, scsi_scan_host_selected,
CP_LIST(struct Scsi_Host *shost, unsigned int channel, unsigned int id, u64 lun, int rescan));
struct ida;
CP_DECLARE_SHIM(int, ida_pre_get, CP_LIST(struct ida *ida, gfp_t gfp_mask));
//Used for fixing I/O scheduler if module was loaded using elevator= and broke it
CP_DECLARE_SHIM(int, elevator_setup, CP_LIST(char *str));
struct notifier_block;
CP_DECLARE_SHIM(void, usb_register_notify, CP_LIST(struct notifier_block *nb));
CP_DECLARE_SHIM(void, usb_unregister_notify, CP_LIST(struct notifier_block *nb));
#endif //REDPILLLKM_CALL_PROTECTED_H

View File

@ -0,0 +1,200 @@
/*
* Helper library for ftrace hooking kernel functions
* Author: Harvey Phillips (xcellerator@gmx.com)
* License: GPL
* Link: https://github.com/xcellerator/linux_kernel_hacking/blob/master/3_RootkitTechniques/3.9_hiding_logged_in_users/ftrace_helper.h
* */
#include <linux/ftrace.h>
#include <linux/linkage.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/version.h>
#if defined(CONFIG_X86_64) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4,17,0))
#define PTREGS_SYSCALL_STUBS 1
#endif
/*
* On Linux kernels 5.7+, kallsyms_lookup_name() is no longer exported,
* so we have to use kprobes to get the address.
* Full credit to @f0lg0 for the idea.
*/
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0)
#define KPROBE_LOOKUP 1
#include <linux/kprobes.h>
static struct kprobe kp = {
.symbol_name = "kallsyms_lookup_name"
};
#endif
#define HOOK(_name, _hook, _orig) \
{ \
.name = (_name), \
.function = (_hook), \
.original = (_orig), \
}
/* We need to prevent recursive loops when hooking, otherwise the kernel will
* panic and hang. The options are to either detect recursion by looking at
* the function return address, or by jumping over the ftrace call. We use the
* first option, by setting USE_FENTRY_OFFSET = 0, but could use the other by
* setting it to 1. (Oridinarily ftrace provides it's own protections against
* recursion, but it relies on saving return registers in $rip. We will likely
* need the use of the $rip register in our hook, so we have to disable this
* protection and implement our own).
* */
#define USE_FENTRY_OFFSET 0
#if !USE_FENTRY_OFFSET
#pragma GCC optimize("-fno-optimize-sibling-calls")
#endif
/* We pack all the information we need (name, hooking function, original function)
* into this struct. This makes is easier for setting up the hook and just passing
* the entire struct off to fh_install_hook() later on.
* */
struct ftrace_hook {
const char *name;
void *function;
void *original;
unsigned long address;
struct ftrace_ops ops;
};
/* Ftrace needs to know the address of the original function that we
* are going to hook. As before, we just use kallsyms_lookup_name()
* to find the address in kernel memory.
* */
static int fh_resolve_hook_address(struct ftrace_hook *hook)
{
#ifdef KPROBE_LOOKUP
typedef unsigned long (*kallsyms_lookup_name_t)(const char *name);
kallsyms_lookup_name_t kallsyms_lookup_name;
register_kprobe(&kp);
kallsyms_lookup_name = (kallsyms_lookup_name_t) kp.addr;
unregister_kprobe(&kp);
#endif
hook->address = kallsyms_lookup_name(hook->name);
if (!hook->address)
{
printk(KERN_DEBUG "rootkit: unresolved symbol: %s\n", hook->name);
return -ENOENT;
}
#if USE_FENTRY_OFFSET
*((unsigned long*) hook->original) = hook->address + MCOUNT_INSN_SIZE;
#else
*((unsigned long*) hook->original) = hook->address;
#endif
return 0;
}
/* See comment below within fh_install_hook() */
static void notrace fh_ftrace_thunk(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *ops, struct pt_regs *regs)
{
struct ftrace_hook *hook = container_of(ops, struct ftrace_hook, ops);
#if USE_FENTRY_OFFSET
regs->ip = (unsigned long) hook->function;
#else
if(!within_module(parent_ip, THIS_MODULE))
regs->ip = (unsigned long) hook->function;
#endif
}
/* Assuming we've already set hook->name, hook->function and hook->original, we
* can go ahead and install the hook with ftrace. This is done by setting the
* ops field of hook (see the comment below for more details), and then using
* the built-in ftrace_set_filter_ip() and register_ftrace_function() functions
* provided by ftrace.h
* */
int fh_install_hook(struct ftrace_hook *hook)
{
int err;
err = fh_resolve_hook_address(hook);
if(err)
return err;
/* For many of function hooks (especially non-trivial ones), the $rip
* register gets modified, so we have to alert ftrace to this fact. This
* is the reason for the SAVE_REGS and IP_MODIFY flags. However, we also
* need to OR the RECURSION_SAFE flag (effectively turning if OFF) because
* the built-in anti-recursion guard provided by ftrace is useless if
* we're modifying $rip. This is why we have to implement our own checks
* (see USE_FENTRY_OFFSET). */
hook->ops.func = fh_ftrace_thunk;
hook->ops.flags = FTRACE_OPS_FL_SAVE_REGS
| FTRACE_OPS_FL_RECURSION_SAFE
| FTRACE_OPS_FL_IPMODIFY;
err = ftrace_set_filter_ip(&hook->ops, hook->address, 0, 0);
if(err)
{
printk(KERN_DEBUG "rootkit: ftrace_set_filter_ip() failed: %d\n", err);
return err;
}
err = register_ftrace_function(&hook->ops);
if(err)
{
printk(KERN_DEBUG "rootkit: register_ftrace_function() failed: %d\n", err);
return err;
}
return 0;
}
/* Disabling our function hook is just a simple matter of calling the built-in
* unregister_ftrace_function() and ftrace_set_filter_ip() functions (note the
* opposite order to that in fh_install_hook()).
* */
void fh_remove_hook(struct ftrace_hook *hook)
{
int err;
err = unregister_ftrace_function(&hook->ops);
if(err)
{
printk(KERN_DEBUG "rootkit: unregister_ftrace_function() failed: %d\n", err);
}
err = ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0);
if(err)
{
printk(KERN_DEBUG "rootkit: ftrace_set_filter_ip() failed: %d\n", err);
}
}
/* To make it easier to hook multiple functions in one module, this provides
* a simple loop over an array of ftrace_hook struct
* */
int fh_install_hooks(struct ftrace_hook *hooks, size_t count)
{
int err;
size_t i;
for (i = 0 ; i < count ; i++)
{
err = fh_install_hook(&hooks[i]);
if(err)
goto error;
}
return 0;
error:
while (i != 0)
{
fh_remove_hook(&hooks[--i]);
}
return err;
}
void fh_remove_hooks(struct ftrace_hook *hooks, size_t count)
{
size_t i;
for (i = 0 ; i < count ; i++)
fh_remove_hook(&hooks[i]);
}

16
internal/helper/math_helper.c Executable file
View File

@ -0,0 +1,16 @@
#include "math_helper.h"
#include <linux/random.h> //prandom_u32()
int prandom_int_range_stable(int *cur_val, int dev, int min, int max)
{
if (likely(*cur_val != 0)) {
int new_min = (*cur_val) - dev;
int new_max = (*cur_val) + dev;
min = new_min < min ? min : new_min;
max = new_max > max ? max : new_max;
}
*cur_val = prandom_int_range(min, max);
return *cur_val;
}

29
internal/helper/math_helper.h Executable file
View File

@ -0,0 +1,29 @@
#ifndef REDPILL_MATH_HELPER_H
#define REDPILL_MATH_HELPER_H
/**
* Generates pseudo-random integer in a range specified
*
* @param min Lower boundary integer
* @param max Higher boundary integer
*
* @return pseudorandom integer up to 32 bits in length
*/
#define prandom_int_range(min, max) ({ \
int _rand = (prandom_u32() % ((max) + 1 - (min)) + (min)); \
_rand; \
})
/**
* Generates temporally stable pseudo-random integer in a range specified
*
* @param cur_val Pointer to store/read current value; set its value to 0 initially to generate setpoint automatically
* @param dev Max deviation from the current value
* @param min Lower boundary integer
* @param max Higher boundary integer
*
* @return pseudorandom integer up to 32 bits in length
*/
int prandom_int_range_stable(int *cur_val, int dev, int min, int max);
#endif //REDPILL_MATH_HELPER_H

44
internal/helper/memory_helper.c Executable file
View File

@ -0,0 +1,44 @@
/**
* TODO: look into override_symbol to check if there's any docs
*/
#include "memory_helper.h"
#include "../../common.h"
#include "../call_protected.h" //_flush_tlb_all()
#include <asm/cacheflush.h> //PAGE_ALIGN
#include <asm/page_types.h> //PAGE_SIZE
#include <asm/pgtable_types.h> //_PAGE_RW
#define PAGE_ALIGN_BOTTOM(addr) (PAGE_ALIGN(addr) - PAGE_SIZE) //aligns the memory address to bottom of the page boundary
#define NUM_PAGES_BETWEEN(low, high) (((PAGE_ALIGN_BOTTOM(high) - PAGE_ALIGN_BOTTOM(low)) / PAGE_SIZE) + 1)
void set_mem_addr_rw(const unsigned long vaddr, unsigned long len)
{
unsigned long addr = PAGE_ALIGN_BOTTOM(vaddr);
pr_loc_dbg("Disabling memory protection for page(s) at %p+%lu/%u (<<%p)", (void *) vaddr, len,
(unsigned int) NUM_PAGES_BETWEEN(vaddr, vaddr + len), (void *) addr);
//theoretically this should use set_pte_atomic() but we're touching pages that will not be modified by anything else
unsigned int level;
for(; addr <= vaddr; addr += PAGE_SIZE) {
pte_t *pte = lookup_address(addr, &level);
pte->pte |= _PAGE_RW;
}
_flush_tlb_all();
}
void set_mem_addr_ro(const unsigned long vaddr, unsigned long len)
{
unsigned long addr = PAGE_ALIGN_BOTTOM(vaddr);
pr_loc_dbg("Enabling memory protection for page(s) at %p+%lu/%u (<<%p)", (void *) vaddr, len,
(unsigned int) NUM_PAGES_BETWEEN(vaddr, vaddr + len), (void *) addr);
//theoretically this should use set_pte_atomic() but we're touching pages that will not be modified by anything else
unsigned int level;
for(; addr <= vaddr; addr += PAGE_SIZE) {
pte_t *pte = lookup_address(addr, &level);
pte->pte &= ~_PAGE_RW;
}
_flush_tlb_all();
}

37
internal/helper/memory_helper.h Executable file
View File

@ -0,0 +1,37 @@
#ifndef REDPILL_MEMORY_HELPER_H
#define REDPILL_MEMORY_HELPER_H
#define WITH_MEM_UNLOCKED(vaddr, size, code) \
do { \
set_mem_addr_rw((unsigned long)(vaddr), size); \
({code}); \
set_mem_addr_ro((unsigned long)(vaddr), size); \
} while(0)
/**
* Disables write-protection for the memory where symbol resides
*
* There are a million different methods of circumventing the memory protection in Linux. The reason being the kernel
* people make it harder and harder to modify syscall table (& others in the process), which in general is a great idea.
* There are two core methods people use: 1) disabling CR0 WP bit, and 2) setting memory page(s) as R/W.
* The 1) is a flag, present on x86 CPUs, which when cleared configures the MMU to *ignore* write protection set on
* memory regions. However, this flag is per-core (=synchronization problems) and it works as all-or-none. We don't
* want to leave such a big thing disabled (especially for long time).
* The second mechanism disabled memory protection on per-page basis. Normally the kernel contains set_memory_rw() which
* does what it says - sets the address (which should be lower-page aligned) to R/W. However, this function is evil for
* some time (~2.6?). In its course it calls static_protections() which REMOVES the R/W flag from the request
* (effectively making the call a noop) while still returning 0. Guess how long we debugged that... additionally, that
* function is removed in newer kernels.
* The easiest way is to just lookup the page table entry for a given address, modify the R/W attribute directly and
* dump CPU caches. This will work as there's no middle-man to mess with our request.
*/
void set_mem_addr_rw(const unsigned long vaddr, unsigned long len);
/**
* Reverses set_mem_rw()
*
* See set_mem_rw() for details
*/
void set_mem_addr_ro(const unsigned long vaddr, unsigned long len);
#endif //REDPILL_MEMORY_HELPER_H

112
internal/helper/symbol_helper.c Executable file
View File

@ -0,0 +1,112 @@
#include "symbol_helper.h" //kln_func
#include <linux/module.h> //__symbol_get(), __symbol_put()
#include <linux/kallsyms.h> //kallsyms_lookup_name
#include "../../common.h" //pr_loc_*
unsigned long (*kln_func)(const char* name) = NULL;
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,7,0)
int get_kln_p(void)
{
kln_func = kallsyms_lookup_name;
return 0;
}
#else
/*
* In kernel version 5.7, kallsyms_lookup_name() was unexported, so we can't use it anymore.
* The alternative method below is slower (but not really noticably), and works by brute-forcing
* possible addresses for the function name by starting at the kernel base address and using
* sprint_symbol() (which is still exported) to check if the symbol name at each address
* matches the one we want.
*
* https://github.com/xcellerator/linux_kernel_hacking/blob/446789fd152d2663cd2c7d7f8a5aaae873a92a30/3_RootkitTechniques/3.3_set_root/ftrace_helper.h
*/
static unsigned long kaddr_lookup_name(const char *fname_raw)
{
int i;
unsigned long kaddr;
char *fname_lookup, *fname;
fname_lookup = kzalloc(255, GFP_KERNEL);
if (!fname_lookup)
return 0;
fname = kzalloc(strlen(fname_raw)+4, GFP_KERNEL);
if (!fname)
return 0;
/*
* We have to add "+0x0" to the end of our function name
* because that's the format that sprint_symbol() returns
* to us. If we don't do this, then our search can stop
* prematurely and give us the wrong function address!
*/
strcpy(fname, fname_raw);
strcat(fname, "+0x0");
/*
* Get the kernel base address:
* sprint_symbol() is less than 0x100000 from the start of the kernel, so
* we can just AND-out the last 3 bytes from it's address to the the base
* address.
* There might be a better symbol-name to use?
*/
kaddr = (unsigned long) &sprint_symbol;
kaddr &= 0xffffffffff000000;
/*
* All the syscalls (and all interesting kernel functions I've seen so far)
* are within the first 0x100000 bytes of the base address. However, the kernel
* functions are all aligned so that the final nibble is 0x0, so we only
* have to check every 16th address.
*/
for ( i = 0x0 ; i < 0x100000 ; i++ )
{
/*
* Lookup the name ascribed to the current kernel address
*/
sprint_symbol(fname_lookup, kaddr);
/*
* Compare the looked-up name to the one we want
*/
if ( strncmp(fname_lookup, fname, strlen(fname)) == 0 )
{
/*
* Clean up and return the found address
*/
kfree(fname_lookup);
return kaddr;
}
/*
* Jump 16 addresses to next possible address
*/
kaddr += 0x10;
}
/*
* We didn't find the name, so clean up and return 0
*/
kfree(fname_lookup);
return 0;
}
int get_kln_p(void)
{
kln_func = (long unsigned int (*)(const char *))kaddr_lookup_name("kallsyms_lookup_name");
if (kln_func == 0) {
pr_loc_err("Error searching kallsyms_lookup_name address!");
return -1;
}
pr_loc_dbg("kallsyms_lookup_name address = 0x%lx\n", (long unsigned int)kln_func);
return 0;
}
#endif
bool kernel_has_symbol(const char *name) {
if (__symbol_get(name)) { //search for public symbols
__symbol_put(name);
return true;
}
return kln_func(name) != 0;
}

22
internal/helper/symbol_helper.h Executable file
View File

@ -0,0 +1,22 @@
#ifndef REDPILL_SYMBOL_HELPER_H
#define REDPILL_SYMBOL_HELPER_H
#include <linux/types.h> //bool
/**
* Workaround for kallsyms_lookup_name in kernels > 5.7
* https://github.com/xcellerator/linux_kernel_hacking/issues/3
*/
extern unsigned long (*kln_func)(const char*);
int get_kln_p(void);
/**
* Check if a given symbol exists
*
* This function will return true for both public and private kernel symbols
*
* @param name name of the symbol
*/
bool kernel_has_symbol(const char *name);
#endif //REDPILL_SYMBOL_HELPER_H

View File

@ -0,0 +1,269 @@
#include "intercept_driver_register.h"
#include "../common.h"
#include "override/override_symbol.h"
#include <linux/platform_device.h> //platform_bus_type
#define MAX_WATCHERS 5 //can be increased as-needed
#define WATCH_FUNCTION "driver_register"
struct driver_watcher_instance {
watch_dr_callback *cb;
bool notify_coming:1;
bool notify_live:1;
char name[];
};
static override_symbol_inst *ov_driver_register = NULL;
static driver_watcher_instance *watchers[MAX_WATCHERS] = { NULL };
/**
* Finds a registered watcher based on the driver name
*
* @return pointer to the spot on the list containing driver_watcher_instance or NULL if not found
*/
static driver_watcher_instance **match_watcher(const char *name)
{
for (int i=0; i < MAX_WATCHERS; ++i) {
if (!watchers[i])
continue; //there could be "holes" due to unwatch calls
if(strcmp(name, watchers[i]->name) == 0)
return &watchers[i];
}
return NULL;
}
/**
* Finds an empty spot in the watchers list
*
* @return pointer to the spot on the list which is empty or NULL if not found
*/
static driver_watcher_instance **watcher_list_spot(void)
{
for (int i=0; i < MAX_WATCHERS; ++i) {
if (!watchers[i])
return &watchers[i];
}
return NULL;
}
/**
* Checks if there any watchers registered (to determine if it makes sense to still shim the driver_register())
*/
static bool has_any_watchers(void)
{
for (int i=0; i < MAX_WATCHERS; ++i) {
if (watchers[i])
return true;
}
return false;
}
/**
* Calls the original driver_register() with error handling
*
* @return 0 on success, -E on error
*/
static int call_original_driver_register(struct device_driver *drv)
{
int driver_register_out, org_call_out;
org_call_out = call_overridden_symbol(driver_register_out, ov_driver_register, drv);
if (unlikely(org_call_out != 0)) {
pr_loc_err("Failed to call original %s (error=%d)", WATCH_FUNCTION, org_call_out);
return org_call_out;
}
return driver_register_out;
}
/**
* Replacement for driver_register(), executing registered hooks
*/
static int driver_register_shim(struct device_driver *drv)
{
driver_watcher_instance **watcher_lptr = match_watcher(drv->name);
int driver_load_result;
bool driver_register_fulfilled = false;
if (unlikely(!watcher_lptr)) {
pr_loc_dbg("%s() interception active - no handler observing \"%s\" found, calling original %s()",
WATCH_FUNCTION, drv->name, WATCH_FUNCTION);
return call_original_driver_register(drv);
}
pr_loc_dbg("%s() interception active - calling handler %pF<%p> for \"%s\"", WATCH_FUNCTION, (*watcher_lptr)->cb,
(*watcher_lptr)->cb, drv->name);
if ((*watcher_lptr)->notify_coming) {
pr_loc_dbg("Calling for DWATCH_STATE_COMING");
switch ((*watcher_lptr)->cb(drv, DWATCH_STATE_COMING)) {
//CONTINUE and DONE cannot use fall-through as we cannot unregister watcher before calling it (as if this is the
// last watcher the whole override will be stopped
case DWATCH_NOTIFY_CONTINUE:
pr_loc_dbg("Calling original %s() & leaving watcher active", WATCH_FUNCTION);
driver_load_result = call_original_driver_register(drv);
driver_register_fulfilled = true;
break;
case DWATCH_NOTIFY_DONE:
pr_loc_dbg("Calling original %s() & removing watcher", WATCH_FUNCTION);
driver_load_result = call_original_driver_register(drv);
unwatch_driver_register(*watcher_lptr); //regardless of the call result we unregister
return driver_load_result; //we return here as the watcher doesn't want to be bothered anymore
case DWATCH_NOTIFY_ABORT_OK:
pr_loc_dbg("Faking OK return of %s() per callback request", WATCH_FUNCTION);
driver_load_result = 0;
driver_register_fulfilled = true;
break;
case DWATCH_NOTIFY_ABORT_BUSY:
pr_loc_dbg("Faking BUSY return of %s() per callback request", WATCH_FUNCTION);
driver_load_result = -EBUSY;
driver_register_fulfilled = true;
break;
default: //This should never happen if the callback is correct
pr_loc_bug("%s callback %pF<%p> returned invalid status value during DWATCH_STATE_COMING",
WATCH_FUNCTION, (*watcher_lptr)->cb, (*watcher_lptr)->cb);
}
}
if (!driver_register_fulfilled)
driver_load_result = call_original_driver_register(drv);
if (driver_load_result != 0) {
pr_loc_err("%s driver failed to load - not triggering STATE_LIVE callbacks", drv->name);
return driver_load_result;
}
if ((*watcher_lptr)->notify_live) {
pr_loc_dbg("Calling for DWATCH_STATE_LIVE");
if ((*watcher_lptr)->cb(drv, DWATCH_STATE_LIVE) == DWATCH_NOTIFY_DONE)
unwatch_driver_register(*watcher_lptr);
}
return driver_load_result;
}
/**
* Enables override of driver_register() to watch for new drivers registration
*
* @return 0 on success, or -E on error
*/
static int start_watching(void)
{
if (unlikely(ov_driver_register)) {
pr_loc_bug("Watching is already enabled!");
return 0;
}
pr_loc_dbg("Starting intercept of %s()", WATCH_FUNCTION);
ov_driver_register = override_symbol(WATCH_FUNCTION, driver_register_shim);
if (unlikely(IS_ERR(ov_driver_register))) {
pr_loc_err("Failed to intercept %s() - error=%ld", WATCH_FUNCTION, PTR_ERR(ov_driver_register));
ov_driver_register = NULL;
return -EINVAL;
}
pr_loc_dbg("%s() is now intercepted", WATCH_FUNCTION);
return 0;
}
/**
* Disables override of driver_register(), started by start_watching()
*
* @return 0 on success, or -E on error
*/
static int stop_watching(void)
{
if (unlikely(!ov_driver_register)) {
pr_loc_bug("Watching is NOT enabled");
return 0;
}
pr_loc_dbg("Stopping intercept of %s()", WATCH_FUNCTION);
int out = restore_symbol(ov_driver_register);
if (unlikely(out != 0)) {
pr_loc_err("Failed to restore %s() - error=%ld", WATCH_FUNCTION, PTR_ERR(ov_driver_register));
return out;
}
pr_loc_dbg("Intercept of %s() stopped", WATCH_FUNCTION);
return 0;
}
driver_watcher_instance *watch_driver_register(const char *name, watch_dr_callback *cb, int event_mask)
{
driver_watcher_instance **watcher_lptr = match_watcher(name);
if (unlikely(watcher_lptr)) {
pr_loc_err("Watcher for %s already exists (callback=%pF<%p>)", name, (*watcher_lptr)->cb, (*watcher_lptr)->cb);
return ERR_PTR(-EEXIST);
}
watcher_lptr = watcher_list_spot();
if (unlikely(!watcher_lptr)) {
pr_loc_bug("There are no free spots for a new watcher");
return ERR_PTR(-ENOSPC);
}
kmalloc_or_exit_ptr(*watcher_lptr, sizeof(driver_watcher_instance) + strsize(name));
strcpy((*watcher_lptr)->name, name);
(*watcher_lptr)->cb = cb;
(*watcher_lptr)->notify_coming = ((event_mask & DWATCH_STATE_COMING) == DWATCH_STATE_COMING);
(*watcher_lptr)->notify_live = ((event_mask & DWATCH_STATE_LIVE) == DWATCH_STATE_LIVE);
pr_loc_dbg("Registered %s() watcher for \"%s\" driver (coming=%d, live=%d)", WATCH_FUNCTION, name,
(*watcher_lptr)->notify_coming ? 1 : 0, (*watcher_lptr)->notify_live ? 1 : 0);
if (!ov_driver_register) {
pr_loc_dbg("Registered the first driver_register watcher - starting watching");
int out = start_watching();
if (unlikely(out != 0))
return ERR_PTR(out);
}
return *watcher_lptr;
}
int unwatch_driver_register(driver_watcher_instance *instance)
{
driver_watcher_instance **matched_lptr = match_watcher(instance->name);
if (unlikely(!matched_lptr)) {
//This means it could be a double-unwatch situation and this will prevent a double-kfree (but the lack of crash
// is not guaranteed as match_watcher() already touched the memory)
pr_loc_bug("Watcher %p for %s couldn't be found in the watchers list", instance, instance->name);
return -ENOENT;
}
if (unlikely(*matched_lptr != instance)) {
pr_loc_bug("Watcher %p for %s was found but the instance on the list %p (@%p) isn't the same (?!)", instance,
instance->name, *matched_lptr, matched_lptr);
return -EINVAL;
}
pr_loc_dbg("Removed %pF<%p> subscriber for \"%s\" driver", (*matched_lptr)->cb, (*matched_lptr)->cb,
(*matched_lptr)->name);
kfree(*matched_lptr);
*matched_lptr = NULL;
if (!has_any_watchers()) {
pr_loc_dbg("Removed last %s() subscriber - unshimming %s()", WATCH_FUNCTION, WATCH_FUNCTION);
int out;
if ((out = stop_watching()) != 0)
return out;
}
return 0;
}
int is_driver_registered(const char *name, struct bus_type *bus)
{
if (!bus)
bus = &platform_bus_type;
struct device_driver *drv = driver_find(name, bus);
if (IS_ERR(drv))
return PTR_ERR(drv);
return drv ? 1:0;
}

View File

@ -0,0 +1,59 @@
#ifndef REDPILL_DRIVER_WATCHER_H
#define REDPILL_DRIVER_WATCHER_H
#include <linux/device.h> //struct device_driver, driver_find (in .c)
/**
* Codes which the callback call on watch can return
*/
typedef enum {
DWATCH_NOTIFY_CONTINUE, //callback processed the data and allows for the chain to continue
DWATCH_NOTIFY_DONE, //callback processed the data, allows for the chain to continue but wants to unregister
DWATCH_NOTIFY_ABORT_OK, //callback processed the data and determined that fake-OK should be returned to the original caller (DWATCH_STATE_COMING only)
DWATCH_NOTIFY_ABORT_BUSY, //callback processed the data and determined that fake-EBUSY should be returned to the original caller (DWATCH_STATE_COMING only)
} driver_watch_notify_result;
/**
* Controls when the callback for loaded driver is called
*/
typedef enum {
DWATCH_STATE_COMING = 0b100, //driver is loading, you can intercept the process using (DWATCH_NOTIFY_ABORT_*) and change data
DWATCH_STATE_LIVE = 0b010, //driver just loaded
} driver_watch_notify_state;
typedef struct driver_watcher_instance driver_watcher_instance;
typedef driver_watch_notify_result (watch_dr_callback)(struct device_driver *drv, driver_watch_notify_state event);
/**
* Start watching for a driver registration
*
* Note: if the driver is already loaded this will do nothing, unless the driver is removed and re-registers. You should
* probably call is_driver_registered() first.
*
* @param name Name of the driver you want to observe
* @param cb Callback called on an event
* @param event_mask ORed driver_watch_notify_state flags to when the callback is called
*
* @return 0 on success, -E on error
*/
driver_watcher_instance *watch_driver_register(const char *name, watch_dr_callback *cb, int event_mask);
/**
* Undoes what watch_driver_register() did
*
* @return 0 on success, -E on error
*/
int unwatch_driver_register(driver_watcher_instance *instance);
/**
* Checks if a given driver exists
*
* Usually if the driver exists already it doesn't make sense to watch for it as the event will never be triggered
* (unless the driver unregisters and registers again). If the bus is not specified here (NULL) a platform-driver will
* be looked up (aka legacy driver).
*
* @return 0 if the driver is not registered, 1 if the driver is registered, -E on lookup error
*/
int is_driver_registered(const char *name, struct bus_type *bus);
#endif //REDPILL_DRIVER_WATCHER_H

145
internal/intercept_execve.c Executable file
View File

@ -0,0 +1,145 @@
/*
* Submodule used to hook the execve() syscall, used by the userland to execute binaries.
*
* This submodule can currently block calls to specific binaries and fake a successful return of the execution. In the
* future, if needed, an option to fake certain response and/or execute a different binary instead can be easily added
* here.
*
* execve() is a rather special syscall. This submodule utilized override_symbool.c:override_syscall() to do the actual
* ground work of replacing the call. However some syscalls (execve, fork, etc.) use ASM stubs with a non-GCC call
* convention. Up until Linux v3.18 it wasn't a problem as long as the stub was called back. However, since v3.18 the
* stub was changed in such a way that calling it using a normal convention from (i.e. from the shim here) will cause
* IRET imbalance and a crash. This is worked around by skipping the whole stub and calling do_execve() with a filename
* struct directly. This requires re-exported versions of these functions, so it may be marginally slower.
* Because of that this trick is only utilized on Linux >v3.18 and older ones call the stub as normal.
*
* References:
* - https://github.com/torvalds/linux/commit/b645af2d5905c4e32399005b867987919cbfc3ae
* - https://my.oschina.net/macwe/blog/603583
* - https://stackoverflow.com/questions/8372912/hooking-sys-execve-on-linux-3-x
*/
#include "intercept_execve.h"
#include "../common.h"
#include <linux/limits.h>
#include <linux/fs.h> //struct filename
#include "override/override_syscall.h" //SYSCALL_SHIM_DEFINE3, override_symbol
#include "call_protected.h" //do_execve(), getname(), putname()
#ifdef RPDBG_EXECVE
#include "../debug/debug_execve.h"
#endif
#define MAX_INTERCEPTED_FILES 10
static char * intercepted_filenames[MAX_INTERCEPTED_FILES] = { NULL };
int add_blocked_execve_filename(const char *filename)
{
if (unlikely(strlen(filename) > PATH_MAX))
return -ENAMETOOLONG;
unsigned int idx = 0;
while (likely(intercepted_filenames[idx])) { //Find free spot
if (unlikely(strcmp(filename, intercepted_filenames[idx]) == 0)) { //Does it exist already?
pr_loc_bug("File %s was already added at %d", filename, idx);
return -EEXIST;
}
if(unlikely(++idx >= MAX_INTERCEPTED_FILES)) { //Are we out of indexes?
pr_loc_bug("Tried to add %d intercepted filename (max=%d)", idx, MAX_INTERCEPTED_FILES);
return -ENOMEM;
}
}
kmalloc_or_exit_int(intercepted_filenames[idx], strsize(filename));
strcpy(intercepted_filenames[idx], filename); //Size checked above
pr_loc_inf("Filename %s will be blocked from execution", filename);
return 0;
}
SYSCALL_SHIM_DEFINE3(execve,
const char __user *, filename,
const char __user *const __user *, argv,
const char __user *const __user *, envp)
{
struct filename *path = _getname(filename);
//this is essentially what do_execve() (or SYSCALL_DEFINE3 on older kernels) will do if the getname ptr is invalid
if (IS_ERR(path))
return PTR_ERR(path);
const char *pathname = path->name;
#ifdef RPDBG_EXECVE
RPDBG_print_execve_call(pathname, argv);
#endif
for (int i = 0; i < MAX_INTERCEPTED_FILES; i++) {
if (!intercepted_filenames[i])
break;
if (unlikely(strcmp(pathname, intercepted_filenames[i]) == 0)) {
pr_loc_inf("Blocked %s from running", pathname);
//We cannot just return 0 here - execve() *does NOT* return on success, but replaces the current process ctx
do_exit(0);
}
}
//Depending on the version of the kernel do_execve() accepts bare filename (old) or the full struct filename (newer)
//Additionally in older kernels we need to take care of the path lifetime and put it back (it's automatic in newer)
//See: https://github.com/torvalds/linux/commit/c4ad8f98bef77c7356aa6a9ad9188a6acc6b849d
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0)
int out = _do_execve(pathname, argv, envp);
_putname(path);
return out;
#else
return _do_execve(path, argv, envp);
#endif
}
static override_symbol_inst *sys_execve_ovs = NULL;
int register_execve_interceptor()
{
pr_loc_dbg("Registering execve() interceptor");
if (sys_execve_ovs) {
pr_loc_bug("Called %s() while execve() interceptor is already registered", __FUNCTION__);
return -EEXIST;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,10,0)
override_symbol_or_exit_int(sys_execve_ovs, "SyS_execve", SyS_execve_shim);
#else
// TODO there is another __ia32_sys_execve, maybe need to override.
override_symbol_or_exit_int(sys_execve_ovs, "__x64_sys_execve", SyS_execve_shim);
#endif
pr_loc_inf("execve() interceptor registered");
return 0;
}
int unregister_execve_interceptor()
{
pr_loc_dbg("Unregistering execve() interceptor");
if (!sys_execve_ovs) {
pr_loc_bug("Called %s() while execve() interceptor is not registered (yet?)", __FUNCTION__);
return -ENXIO;
}
int out = restore_symbol(sys_execve_ovs);
if (out != 0)
return out;
sys_execve_ovs = NULL;
//Free all strings duplicated in add_blocked_execve_filename()
unsigned int idx = 0;
while (idx < MAX_INTERCEPTED_FILES-1 && intercepted_filenames[idx]) {
kfree(intercepted_filenames[idx]);
intercepted_filenames[idx] = NULL;
idx++;
}
pr_loc_inf("execve() interceptor unregistered");
return 0;
}

9
internal/intercept_execve.h Executable file
View File

@ -0,0 +1,9 @@
#ifndef REDPILL_INTERCEPT_EXECVE_H
#define REDPILL_INTERCEPT_EXECVE_H
//There's no remove_ as this requires rearranging the list etc and is not needed for now
int add_blocked_execve_filename(const char * filename);
int register_execve_interceptor(void);
int unregister_execve_interceptor(void);
#endif //REDPILL_INTERCEPT_EXECVE_H

26
internal/ioscheduler_fixer.c Executable file
View File

@ -0,0 +1,26 @@
/**
* This very simple submodule which prevents kernel log from being flooded with "I/O scheduler elevator not found"
*
* When this shim is loaded as a I/O scheduler (to load very early) it is being set as a I/O scheduler. As we later
* remove the module file the system will constantly try to load now non-existing module "elevator-iosched". By
* resetting the "chosen_elevator" using the same function called by "elevator=" handler we can pretend no custom
* I/O scheduler was ever set (so that the system uses default one and stops complaining)
*/
#include "ioscheduler_fixer.h"
#include "../common.h"
#include "call_protected.h" //is_system_booting(), elevator_setup()
#include <linux/kernel.h> //system_state
#define SHIM_NAME "I/O scheduler fixer"
int reset_elevator(void)
{
if (!is_system_booting()) {
pr_loc_wrn("Cannot reset I/O scheduler / elevator= set - system is past booting stage (state=%d)",
system_state);
return 0; //This is not an error technically speaking
}
pr_loc_dbg("Resetting I/O scheduler to default");
return _elevator_setup("") == 1 ? 0 : -EINVAL;
}

6
internal/ioscheduler_fixer.h Executable file
View File

@ -0,0 +1,6 @@
#ifndef REDPILL_IOSCHEDULER_FIXER_H
#define REDPILL_IOSCHEDULER_FIXER_H
int reset_elevator(void);
#endif //REDPILL_IOSCHEDULER_FIXER_H

13
internal/notifier_base.h Executable file
View File

@ -0,0 +1,13 @@
#ifndef REDPILL_NOTIFIER_BASE_H
#define REDPILL_NOTIFIER_BASE_H
#define notifier_reg_in() pr_loc_dbg("Registering %s notifier", NOTIFIER_NAME);
#define notifier_reg_ok() pr_loc_inf("Successfully registered %s notifier", NOTIFIER_NAME);
#define notifier_ureg_in() pr_loc_dbg("Unregistering %s notifier", NOTIFIER_NAME);
#define notifier_ureg_ok() pr_loc_inf("Successfully unregistered %s notifier", NOTIFIER_NAME);
#define notifier_sub(nb) \
pr_loc_dbg("%pF (priority=%d) subscribed to %s events", (nb)->notifier_call, (nb)->priority, NOTIFIER_NAME);
#define notifier_unsub(nb) \
pr_loc_dbg("%pF (priority=%d) unsubscribed from %s events", (nb)->notifier_call, (nb)->priority, NOTIFIER_NAME);
#endif //REDPILL_NOTIFIER_BASE_H

View File

@ -0,0 +1,307 @@
/**
* This little (and dangerous) utility allows for replacement of any arbitrary kernel symbols with your own
*
* Since we're in the kernel we can do anything we want. This also included manipulating the actual code of functions
* executed by the kernel. So, if someone calls printk() it normally goes to the correct place. However this place can
* be override with a snippet of ASM which jumps to another place - a place we specify. It doesn't require a genius to
* understand the power and implication of this ;)
*
* HOW IT WORKS?
* See header file for example of usage. In short this code does the following:
* 0. Kernel protects .text pages (r/o by default, so you don't override the code by accident): they need to be unlocked
* 1. Find where our symbol-to-be-replaced is located
* 2. Disable memory pages write protection (as symbols are in .text which is )
* 4. Make memory page containing symbol-to-be-replaced r/w
* 5. Generate jump code ASM containing the address of new symbol specified by the caller
* 6. Mark the memory page from [4] r/o again
* 7. [optional] Process is fully reversible
*
* SYSCALL SPECIAL CASE
* There's also a variant made for syscalls specifically. It differs by the fact that override_symbol() makes
* the original code unusable (as the first bytes are replaced with a jump) yet allows you to replace ANY function. The
* overridden_syscall() in the other hand changes a pointer in the syscalls table. That way you CAN use the original
* pointer to call back the original Linux syscall code you replaced. It works roughly like so:
* 0. Kernel keeps syscalls table in .data section which is marked as r/o: it needs to be found & unlocked (#2-4 above)
* 1. replace pointer in the table with custom one
* 2. relock memory
*
* CALLING THE ORIGINAL CODE PROBLEM
* When we wrote this code intially it was meant to be a temporary stop-gap until we have time to write a proper
* rerouting using kernel's "insn" framework. However, this approach only looks simple on the surface. In theory we just
* need to override the function preamble with a simple trampoline consisting of MOV+JMP to our new code. This is rather
* simple and work well. However, attempting to call the original code without restoring the full function to its
* original shape opens a huge can of worms with many edge-cases. Again, in *theory* we can simply grab the original
* preamble, attach a JMP to the original function just after the preamble, and execute it.. it will work MOST of the
* time, but:
* - we must ensure we round copied preamble to full instruction
* - trampoline must be automatically padded with NOP-sled
* - if function has ANY arguments (and most do) we need to take care of fixing the stack or saving preamble with all
* pushes?
* - overriden function may be shorter than trampoline (ok, we don't handle this even now, but it's unlikely)
* - and the biggest one: RIP. Some instructions are execution-point addressed (e.g. jump -5 bytes from here).
* Detecting that for the preamble and blocking override of such function (or EVEN fixing that RIP/EIP addressing) is
* rather possible. However, what becomes a problem are backward jumps in the code following the preamble. If the
* code after the preamble jumps backwards and lands in our trampoline instead of the original preamble it will
* either jump into a "random" place or jump to the replacement function. This is not that unlikely if the function
* is a short loop and nothing else. Trying to find such bug would be nightmare and we don't see a sane way of
* scanning the whole function and determining if it has any negative RIPs/EIPs and if they happen to fall within
* preamble. It's a mess with maaaany traps. While kernel has kprobes and fprobes we cannot use them as they're not
* enabled in syno kernels.
*
* We decided to compromise. The code offers a special call_overridden_symbol(). It follows a very similar process to
* restore_symbol()+override_symbol(). Normally the restoration [R] + override [O] process chain will look like so:
* 1. [R] Disable CR0
* 2. [R] Unlock memory page(s) where trampoline lies
* 3. [R] Copy original preamble over trampoline
* 4. [R] Lock memory page(s) with preamble
* 5. [R] Enable CR0
* 6. Call original
* 7. [O] Disable CR0
* 8. [R] Unlock memory page(s) where we want to copy the trampoline
* 9. [R] Copy original trampoline over original preamble
* 10. [R] Lock memory page(s) with trampoline
* 11. [R] Enable CR0
*
* The call_overridden_symbol() obviously has to disable CR0 and unlock memory but it LEAVES the memory unlocked for any
* subsequent calls. While it's less safe (as something can accidentally override it will not be reported) it shortens
* the call path for 2nd and beyond calls to the original:
* 1. Check if memory needs to be unlocked
* 2. [O] Copy original preamble over trampoline
* 3. Call original
* 4. [R] Copy original trampoline over original preamble
*
* Using call_overridden_symbol() thus has huge advantages over override+restore if you plan to call the original
* function more than once. If you want to call it only once the call_overridden_symbol() is an equivalent of restore+
* override. That's why, for readability reasons and DRY of checking code it's preferred to use call_overridden_symbol()
* even if you call the original method even once.
* There's a third method: utilizing forceful breakpoints like kprobe does. However, this is a rather complex system and
* also contains many traps. Additionally, its overhead is no smaller than the current call_overridden_symbol()
* implementation. The kernel uses breakpoints for more safety and to detect possible interactions between different
* subsystems utilizing breakpoints. This isn't our concern here.
*
* References:
* - https://www.cs.uaf.edu/2016/fall/cs301/lecture/09_28_machinecode.html
* - http://www.watson.org/%7Erobert/2007woot/2007usenixwoot-exploitingconcurrency.pdf
* - https://stackoverflow.com/a/5711253
* - https://www.kernel.org/doc/Documentation/kprobes.txt
* - https://stackoverflow.com/a/6742086
*/
#include "override_symbol.h"
#include "../../common.h"
#include "../helper/memory_helper.h" //set_mem_addr_ro(), set_mem_addr_rw()
#include "../helper/symbol_helper.h" //kln_func
#include <linux/string.h> //memcpy()
#define JUMP_ADDR_POS 2 //JUMP starts at [2] in the jump template below
#define OVERRIDE_JUMP_SIZE 1 + 1 + 8 + 1 + 1 //MOVQ + %rax + $vaddr + JMP + *%rax
static const unsigned char jump_tpl[OVERRIDE_JUMP_SIZE] =
"\x48\xb8" "\x00\x00\x00\x00\x00\x00\x00\x00" /* MOVQ 64-bit-vaddr, %rax */
"\xff\xe0" /* JMP *%rax */
;
#define WITH_OVS_LOCK(__sym, code) \
do { \
pr_loc_dbg("Obtaining lock for <%pF/%p>", (__sym)->org_sym_ptr, (__sym)->org_sym_ptr); \
spin_lock_irqsave(&(__sym)->lock, (__sym)->lock_irq); \
({code}); \
spin_unlock_irqrestore(&(__sym)->lock, (__sym)->lock_irq); \
pr_loc_dbg("Released lock for <%p>", (__sym)->org_sym_ptr); \
} while(0)
struct override_symbol_inst {
void *org_sym_ptr;
const void *new_sym_ptr;
char org_sym_code[OVERRIDE_JUMP_SIZE];
char trampoline[OVERRIDE_JUMP_SIZE];
spinlock_t lock;
unsigned long lock_irq;
bool installed:1; //whether the symbol is currently overrode (=has trampoline installed)
bool has_trampoline:1; //does this structure contain a valid trampoline code already?
bool mem_protected:1; //is the trampoline installation site memory-protected?
char name[];
};
/**
* Wrapper for set_mem_addr_rw() which works with symbols
*/
static void __always_inline set_symbol_rw(struct override_symbol_inst *sym)
{
set_mem_addr_rw((unsigned long)sym->org_sym_ptr, OVERRIDE_JUMP_SIZE);
sym->mem_protected = false;
}
/**
* Wrapper for set_mem_addr_ro() which works with symbols
*/
static void __always_inline set_symbol_ro(struct override_symbol_inst *sym)
{
set_mem_addr_ro((unsigned long)sym->org_sym_ptr, OVERRIDE_JUMP_SIZE);
sym->mem_protected = true;
}
void put_overridden_symbol(struct override_symbol_inst *sym)
{
pr_loc_dbg("Freeing OVS for %s", sym->name);
kfree(sym);
}
/**
* Initializes new "override symbol instance" structure
*
* @return ptr to struct override_symbol_inst or ERR_PTR(-E) on error
*/
static struct override_symbol_inst* get_ov_symbol_instance(const char *symbol_name, const void *new_sym_ptr)
{
struct override_symbol_inst *sym;
kmalloc_or_exit_ptr(sym, sizeof(struct override_symbol_inst) + strsize(symbol_name));
sym->new_sym_ptr = new_sym_ptr;
spin_lock_init(&sym->lock);
sym->installed = false;
sym->has_trampoline = false;
sym->mem_protected = true;
strcpy(sym->name, symbol_name);
sym->org_sym_ptr = (void *)kln_func(sym->name);
if (unlikely(sym->org_sym_ptr == 0)) { //header file: "Lookup the address for a symbol. Returns 0 if not found."
pr_loc_err("Failed to locate vaddr for %s()", sym->name);
put_overridden_symbol(sym);
return ERR_PTR(-EFAULT);
}
pr_loc_dbg("Saved %s() ptr <%p>", sym->name, sym->org_sym_ptr);
return sym;
}
/**
* Generates trampoline code to jump from old symbol to the new symbol location and saves the original code
*/
static inline void prepare_trampoline(struct override_symbol_inst *sym)
{
pr_loc_dbg("Generating trampoline");
//First generate jump/trampoline to new_sym_ptr
memcpy(sym->trampoline, jump_tpl, OVERRIDE_JUMP_SIZE); //copy "empty" trampoline
*(long *)&sym->trampoline[JUMP_ADDR_POS] = (long)sym->new_sym_ptr; //paste new addr into trampoline
pr_loc_dbg("Generated trampoline to %pF<%p> for %s<%p>: ", sym->new_sym_ptr, sym->new_sym_ptr, sym->name,
sym->org_sym_ptr);
memcpy(sym->org_sym_code, sym->org_sym_ptr, OVERRIDE_JUMP_SIZE); //Backup old code
sym->has_trampoline = true;
}
/**
* Enables (previously disabled) symbol override, disabling memory barriers if needed & leaving them disabled upon exit
*
* Warning: this function is exported only to make universal call original macros working. You should NOT use it outside
* of this submodule
*
* @return 0 on success, -E on error
*/
int __enable_symbol_override(struct override_symbol_inst *sym)
{
if (sym->mem_protected)
set_symbol_rw(sym);
WITH_OVS_LOCK(sym,
if (likely(!sym->installed)) {
if (!sym->has_trampoline)
prepare_trampoline(sym);
//after we got the lock need to re-check the memory protection - this shouldn't be changed within spinlock
//since it generates a warning... but sometimes we have no choice
if (sym->mem_protected)
set_symbol_rw(sym);
pr_loc_dbg("Writing trampoline code to <%p>", sym->org_sym_ptr);
memcpy(sym->org_sym_ptr, sym->trampoline, OVERRIDE_JUMP_SIZE);
sym->installed = true;
}
);
return 0;
}
/**
* Disables (previously enables) symbol override, disabling memory barriers if needed & leaving them disabled upon exit
*
* Warning: this function is exported only to make universal call original macros working. You should NOT use it outside
* of this submodule
*
* @return 0 on success, -E on error
*/
int __disable_symbol_override(struct override_symbol_inst *sym)
{
if (sym->mem_protected)
set_symbol_rw(sym);
WITH_OVS_LOCK(sym,
if (likely(sym->installed)) {
//after we got the lock need to re-check the memory protection - this shouldn't be changed within spinlock
//since it generates a warning... but sometimes we have no choice
if (sym->mem_protected)
set_symbol_rw(sym);
pr_loc_dbg("Writing original code to <%p>", sym->org_sym_ptr);
memcpy(sym->org_sym_ptr, sym->org_sym_code, OVERRIDE_JUMP_SIZE);
sym->installed = false;
}
);
return 0;
}
struct override_symbol_inst* __must_check override_symbol(const char *name, const void *new_sym_ptr)
{
pr_loc_dbg("Overriding %s() with %pf()<%p>", name, new_sym_ptr, new_sym_ptr);
int out;
struct override_symbol_inst *sym = get_ov_symbol_instance(name, new_sym_ptr);
if (unlikely(IS_ERR(sym)))
return sym;
if ((out = __enable_symbol_override(sym)) != 0)
goto error_out;
set_symbol_ro(sym); //by design standard override leaves the memory protected
pr_loc_dbg("Successfully overrode %s() with trampoline to %pF<%p>", sym->name, sym->new_sym_ptr, sym->new_sym_ptr);
return sym;
error_out:
put_overridden_symbol(sym);
return ERR_PTR(out);
}
int restore_symbol(struct override_symbol_inst *sym)
{
pr_loc_dbg("Restoring %s<%p> to original code", sym->name, sym->org_sym_ptr);
int out;
if ((out = __disable_symbol_override(sym)) != 0)
goto out_free;
set_symbol_ro(sym); //by design restore leaves the memory protected
pr_loc_dbg("Successfully restored original code of %s", sym->name);
out_free:
put_overridden_symbol(sym);
return out;
}
/**
* Returns pointer to original symbol. This is a function made to avoid exposing internals of the struct to header.
*/
__always_inline void * __get_org_ptr(struct override_symbol_inst *sym)
{
return sym->org_sym_ptr;
}
/**
* Checks if override is enabled. This is a function made to avoid exposing internals of the struct to header.
*/
__always_inline bool symbol_is_overridden(struct override_symbol_inst *sym)
{
return likely(sym) && sym->installed;
}

View File

@ -0,0 +1,133 @@
#ifndef REDPILLLKM_OVERRIDE_KFUNC_H
#define REDPILLLKM_OVERRIDE_KFUNC_H
#include <linux/types.h>
#include <linux/err.h> //PTR_ERR, IS_ERR
typedef struct override_symbol_inst override_symbol_inst;
/************************************************* Current interface **************************************************/
/**
* Calls the original symbol, returning nothing, that was previously overridden
*
* @param sym pointer to a override_symbol_inst
* @param ... any arguments to the original function
*
* @return 0 if the execution succeeded, -E if it didn't
*/
#define call_overridden_symbol_void(sym, ...) ({ \
int __ret; \
bool __was_installed = symbol_is_overridden(sym); \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wstrict-prototypes\"") \
void (*__ptr)() = __get_org_ptr(sym); \
_Pragma("GCC diagnostic pop") \
__ret = __disable_symbol_override(sym); \
if (likely(__ret == 0)) { \
__ptr(__VA_ARGS__); \
if (likely(__was_installed)) { \
__ret = __enable_symbol_override(sym); \
} \
} \
__ret; \
});
/**
* Calls the original symbol, returning a value, that was previously overridden
*
* @param out_var name of the variable where original function return value should be placed
* @param sym pointer to a override_symbol_inst
* @param ... any arguments to the original function
*
* @return 0 if the execution succeeded, -E if it didn't
*/
#define call_overridden_symbol(out_var, sym, ...) ({ \
int __ret; \
bool __was_installed = symbol_is_overridden(sym); \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wstrict-prototypes\"") \
typeof (out_var) (*__ptr)() = __get_org_ptr(sym); \
_Pragma("GCC diagnostic pop") \
__ret = __disable_symbol_override(sym); \
if (likely(__ret == 0)) { \
out_var = __ptr(__VA_ARGS__); \
if (likely(__was_installed)) { \
__ret = __enable_symbol_override(sym); \
} \
} \
__ret; \
});
/**
* override_symbol() with automatic error handling. See the original function for details.
*
* @param ptr_var Variable to store ovs pointer
*/
#define override_symbol_or_exit_int(ptr_var, name, new_sym_ptr) \
(ptr_var) = override_symbol(name, new_sym_ptr); \
if (unlikely(IS_ERR((ptr_var)))) { \
int _err = PTR_ERR((ptr_var)); \
pr_loc_err("Failed to override %s - error=%d", name, _err); \
(ptr_var) = NULL; \
return _err; \
} \
/**
* Overrides a kernel symbol with something else of your choice
*
* @param name Name of the kernel symbol (function) to override
* @param new_sym_ptr An address/pointer to a new function
*
* @return Instance of override_symbol_inst struct pointer on success, ERR_PTR(-E) on error
*
* @example
* struct override_symbol_inst *ovi;
* int null_printk() {
* int print_res;
* call_overridden_symbol(print_res, ovi, "No print for you!");
* return print_res;
* }
* ovi = override_symbol("printk", null_printk);
* if (IS_ERR(ovi)) { ... handle error ... }
* ...
* restore_symbol(backup_addr, backup_code); //restore backed-up copy of printk()
*
* @todo: This should be rewritten using INSN without inline ASM wizardy, but this is much more complex
*/
struct override_symbol_inst* __must_check override_symbol(const char *name, const void *new_sym_ptr);
/**
* Restores symbol overridden by override_symbol()
*
* For details see override_symbol() docblock
*
* @return 0 on success, -E on error
*/
int restore_symbol(struct override_symbol_inst *sym);
/**
* Frees the symbol previously reserved by get_ov_symbol_instance() (e.g. via override_symbol)
*
* !! READ THIS SERIOUS WARNING BELOW CAREFULLY !!
* STOP! DO NOT USE THIS if you don't understand what it does and why it exists. This function should be called from
* outside of this submodule ONLY if the overridden code disappeared from memory. This practically can happen only when
* you override a symbol inside of a loadable module and the module is unloaded. In ANY other case you must call
* restore_symbol() to actually restore the original code. This function simply "forgets" about the override and frees
* memory (as if external module has been unloaded we are NOT allowed to touch that memory anymore as it may be freed).
* It is explicitly NOT necessary to call this function after restore_symbol() as it does so internally.
*/
void put_overridden_symbol(struct override_symbol_inst *sym);
/**
* Check if the given symbol override is currently active
*/
bool symbol_is_overridden(struct override_symbol_inst *sym);
/****************** Private helpers (should not be used directly by any code outside of this unit!) *******************/
#include <linux/types.h>
int __enable_symbol_override(override_symbol_inst *sym);
int __disable_symbol_override(override_symbol_inst *sym);
void * __get_org_ptr(struct override_symbol_inst *sym);
#endif //REDPILLLKM_OVERRIDE_KFUNC_H

View File

@ -0,0 +1,168 @@
#include "override_syscall.h"
#include "../../common.h"
#include "../helper/memory_helper.h" //set_mem_addr_ro(), set_mem_addr_rw()
#include <asm/asm-offsets.h> //__NR_syscall_max & NR_syscalls
#include <asm/unistd.h> //syscalls numbers (e.g. __NR_read)
#include "../helper/symbol_helper.h" //kln_func
static unsigned long *syscall_table_ptr = NULL;
static void print_syscall_table(unsigned int from, unsigned to)
{
if (unlikely(!syscall_table_ptr)) {
pr_loc_dbg("Cannot print - no syscall_table_ptr address");
return;
}
if (unlikely(from < 0 || to > __NR_syscall_max || from > to)) {
pr_loc_bug("%s called with from=%d to=%d which are invalid", __FUNCTION__, from, to);
return;
}
pr_loc_dbg("Printing syscall table %d-%d @ %p containing %d elements", from, to, (void *)syscall_table_ptr, NR_syscalls);
for (unsigned int i = from; i < to; i++) {
pr_loc_dbg("#%03d\t%pS", i, (void *)syscall_table_ptr[i]);
}
}
static int find_sys_call_table(void)
{
syscall_table_ptr = (unsigned long *)kln_func("sys_call_table");
if (syscall_table_ptr != 0) {
pr_loc_dbg("Found sys_call_table @ <%p> using kallsyms", syscall_table_ptr);
return 0;
}
//See https://kernelnewbies.kernelnewbies.narkive.com/L1uH0n8P/
//In essence some systems will have it and some will not - finding it using kallsyms is the easiest and fastest
pr_loc_dbg("Failed to locate vaddr for sys_call_table using kallsyms - falling back to memory search");
/*
There's also the bruteforce way - scan through the memory until you find it :D
We know numbers for syscalls (e.g. __NR_close, __NR_write, __NR_read, etc.) which are essentially fixed positions
in the sys_call_table. We also know addresses of functions handling these calls (sys_close/sys_write/sys_read
etc.). This lets us scan the memory for one syscall address reference and when found confirm if this is really
a place of sys_call_table by verifying other 2-3 places to make sure other syscalls are where they should be
The huge downside of this method is it is slow as potentially the amount of memory to search may be large.
*/
unsigned long sys_close_ptr = kln_func("sys_close");
unsigned long sys_open_ptr = kln_func("sys_open");
unsigned long sys_read_ptr = kln_func("sys_read");
unsigned long sys_write_ptr = kln_func("sys_write");
if (sys_close_ptr == 0 || sys_open_ptr == 0 || sys_read_ptr == 0 || sys_write_ptr == 0) {
pr_loc_bug(
"One or more syscall handler addresses cannot be located: "
"sys_close<%p>, sys_open<%p>, sys_read<%p>, sys_write<%p>",
(void *)sys_close_ptr, (void *)sys_open_ptr, (void *)sys_read_ptr, (void *)sys_write_ptr);
return -EFAULT;
}
/*
To speed up things a bit we search from a known syscall which was loaded early into the memory. To be safe we pick
the earliest address and go from there. It can be nicely visualized on a system which DO export sys_call_table
by running grep -E ' (__x64_)?sys_(close|open|read|write|call_table)$' /proc/kallsyms | sort
You will get something like that:
ffffffff860c18b0 T __x64_sys_close
ffffffff860c37a0 T __x64_sys_open
ffffffff860c7a80 T __x64_sys_read
ffffffff860c7ba0 T __x64_sys_write
ffffffff86e013a0 R sys_call_table <= it's way below any of the syscalls but not too far (~13,892,336 bytes)
*/
unsigned long i = sys_close_ptr;
if (sys_open_ptr < i) i = sys_open_ptr;
if (sys_read_ptr < i) i = sys_read_ptr;
if (sys_write_ptr < i) i = sys_write_ptr;
//If everything goes well it should take ~1-2ms tops (which is slow in the kernel sense but it's not bad)
pr_loc_dbg("Scanning memory for sys_call_table starting at %p", (void *)i);
for (; i < ULONG_MAX; i += sizeof(void *)) {
syscall_table_ptr = (unsigned long *)i;
if (unlikely(
syscall_table_ptr[__NR_close] == sys_close_ptr &&
syscall_table_ptr[__NR_open] == sys_open_ptr &&
syscall_table_ptr[__NR_read] == sys_read_ptr &&
syscall_table_ptr[__NR_write] == sys_write_ptr
)) {
pr_loc_dbg("Found sys_call_table @ %p", (void *)syscall_table_ptr);
return 0;
}
}
pr_loc_bug("Failed to find sys call table");
syscall_table_ptr = NULL;
return -EFAULT;
}
static unsigned long *overridden_syscall[NR_syscalls] = { NULL }; //@todo this should be alloced dynamically
int override_syscall(unsigned int syscall_num, const void *new_sysc_ptr, void * *org_sysc_ptr)
{
pr_loc_dbg("Overriding syscall #%d with %pf()<%p>", syscall_num, new_sysc_ptr, new_sysc_ptr);
int out = 0;
if (unlikely(!syscall_table_ptr)) {
out = find_sys_call_table();
if (unlikely(out != 0))
return out;
}
if (unlikely(syscall_num > __NR_syscall_max)) {
pr_loc_bug("Invalid syscall number: %d > %d", syscall_num, __NR_syscall_max);
return -EINVAL;
}
print_syscall_table(syscall_num-5, syscall_num+5);
if (unlikely(overridden_syscall[syscall_num])) {
pr_loc_bug("Syscall %d is already overridden - will be replaced (bug?)", syscall_num);
} else {
//Only save original-original entry (not the override one)
overridden_syscall[syscall_num] = (unsigned long *)syscall_table_ptr[syscall_num];
}
if (org_sysc_ptr != 0)
*org_sysc_ptr = overridden_syscall[syscall_num];
set_mem_addr_rw((long)&syscall_table_ptr[syscall_num], sizeof(unsigned long));
pr_loc_dbg("syscall #%d originally %ps<%p> will now be %ps<%p> @ %d", syscall_num,
(void *) overridden_syscall[syscall_num], (void *) overridden_syscall[syscall_num], new_sysc_ptr,
new_sysc_ptr, smp_processor_id());
syscall_table_ptr[syscall_num] = (unsigned long) new_sysc_ptr;
set_mem_addr_ro((long)&syscall_table_ptr[syscall_num], sizeof(unsigned long));
print_syscall_table(syscall_num-5, syscall_num+5);
return out;
}
int restore_syscall(unsigned int syscall_num)
{
pr_loc_dbg("Restoring syscall #%d", syscall_num);
if (unlikely(!syscall_table_ptr)) {
pr_loc_bug("Syscall table not found in %s ?!", __FUNCTION__);
return -EFAULT;
}
if (unlikely(syscall_num > __NR_syscall_max)) {
pr_loc_bug("Invalid syscall number: %d > %d", syscall_num, __NR_syscall_max);
return -EINVAL;
}
if (unlikely(overridden_syscall[syscall_num] == 0)) {
pr_loc_bug("Syscall #%d cannot be restored - it was never overridden", syscall_num);
return -EINVAL;
}
print_syscall_table(syscall_num-5, syscall_num+5);
set_mem_addr_rw((long)&syscall_table_ptr[syscall_num], sizeof(unsigned long));
pr_loc_dbg("Restoring syscall #%d from %ps<%p> to original %ps<%p>", syscall_num,
(void *) syscall_table_ptr[syscall_num], (void *) syscall_table_ptr[syscall_num],
(void *) overridden_syscall[syscall_num], (void *) overridden_syscall[syscall_num]);
syscall_table_ptr[syscall_num] = (unsigned long)overridden_syscall[syscall_num];
set_mem_addr_rw((long)&syscall_table_ptr[syscall_num], sizeof(unsigned long));
print_syscall_table(syscall_num-5, syscall_num+5);
return 0;
}

View File

@ -0,0 +1,59 @@
#ifndef REDPILL_OVERRIDE_SYSCALL_H
#define REDPILL_OVERRIDE_SYSCALL_H
#include "override_symbol.h"
#include <linux/syscalls.h>
//Modified syscall defines for shims based on native Linux syscalls (defined in linux/syscalls.h)
#define SYSCALL_SHIM_DEFINE1(name, ...) SYSCALL_SHIM_DEFINEx(1, _##name##_shim, __VA_ARGS__)
#define SYSCALL_SHIM_DEFINE2(name, ...) SYSCALL_SHIM_DEFINEx(2, _##name##_shim, __VA_ARGS__)
#define SYSCALL_SHIM_DEFINE3(name, ...) SYSCALL_SHIM_DEFINEx(3, _##name##_shim, __VA_ARGS__)
#define SYSCALL_SHIM_DEFINE4(name, ...) SYSCALL_SHIM_DEFINEx(4, _##name##_shim, __VA_ARGS__)
#define SYSCALL_SHIM_DEFINE5(name, ...) SYSCALL_SHIM_DEFINEx(5, _##name##_shim, __VA_ARGS__)
#define SYSCALL_SHIM_DEFINE6(name, ...) SYSCALL_SHIM_DEFINEx(6, _##name##_shim, __VA_ARGS__)
#define SYSCALL_SHIM_DEFINEx(x, name, ...) \
static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__)); \
static asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \
{ \
long ret = SYSC##name(__MAP(x,__SC_CAST,__VA_ARGS__)); \
__MAP(x,__SC_TEST,__VA_ARGS__); \
__PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__)); \
return ret; \
} \
static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__))
/**
* Non-destructively overrides a syscall
*
* This produces an effect similar to override_symbol(). However, it should be faster, safer, and most importantly
* allows calling the original syscall in the override.
*
* Warning: DO NOT use this method to override stubbed syscalls. These are syscall which aren't named "sys_foo" (e.g.
* sys_execve or SyS_execve [alias]) but are handled by ASM stubs in arch/x86/kernel/entry_64.S (and visible as e.g.
* stub_execve). If you do override such call with a normal function in the syscall table things will start breaking
* unexpectedly as the registries will be modified in an unexpected way (stubs don't use cdecl)!
* In such cases you need to override the actual sys_* (or even better: SyS_*) function with a jump using
* override_symbol(). Either way you should use SYSCALL_SHIM_DEFINE#() to define the new target/shim.
* Make sure to read https://lwn.net/Articles/604287/ and https://lwn.net/Articles/604406/
*
* @param syscall_num Number of the syscall to override (e.g. open)
* You can find them as __NR_* defines in arch/x86/include/generated/uapi/asm/unistd_64.h
* @param new_sysc_ptr An address/pointer to a new function
* @param org_sysc_ptr Pointer to some space to save address of the original syscall (warning: it's a pointer-pointer);
* You can pass a null-ptr if you don't care about the original syscall and the function will not
* touch it
*
* @return 0 on success, -E on error
*/
int override_syscall(unsigned int syscall_num, const void *new_sysc_ptr, void * *org_sysc_ptr);
/**
* Restores the syscall previously replaced by override_syscall()
*
* For details see override_syscall() docblock.
*
* @return 0 on success, -E on error
*/
int restore_syscall(unsigned int syscall_num);
#endif //REDPILL_OVERRIDE_SYSCALL_H

350
internal/scsi/hdparam.h Executable file
View File

@ -0,0 +1,350 @@
/**
* This file serves a similar role to include/uapi/linux/hdreg.h, combining constants & macros for dealing with ATA
*/
#include <linux/hdreg.h> //HDIO_*
#include <linux/ata.h> //ATA_ID_*, ATA_CMD_*, ATA_SECT_SIZE
#ifndef REDPILL_HDPARAM_H
#define REDPILL_HDPARAM_H
/********************************** Parameters related to HDIO_DRIVE_CMD ioctl call ***********************************/
//the following constants contain size/offsets/indexes of "struct hd_drive_cmd_hdr" fields from user API
// also see HDIO_DRIVE_CMD in https://www.kernel.org/doc/Documentation/ioctl/hdio.txt
#define HDIO_DRIVE_CMD_HDR_OFFSET 4 //same as HDIO_DRIVE_CMD_HDR_SIZE but in sectors
#define HDIO_DRIVE_CMD_HDR_CMD 0 //command
#define HDIO_DRIVE_CMD_HDR_SEC_NUM 1 //sector number
#define HDIO_DRIVE_CMD_HDR_FEATURE 2 //feature
#define HDIO_DRIVE_CMD_HDR_SEC_CNT 3 //sector count
#define HDIO_DRIVE_CMD_RET_STATUS 0
#define HDIO_DRIVE_CMD_RET_ERROR 1
#define HDIO_DRIVE_CMD_RET_SEC_CNT 2 //sector count
//convert sector size of data section of an ATA (sub)command to the full buffer size with header
#define ata_ioctl_buf_size(data_sectors) (u16)(HDIO_DRIVE_CMD_HDR_SIZE+((data_sectors)*(ATA_SECT_SIZE*sizeof(u8))))
//Expected sizes of various commands/subcommands data sections
#define ATA_CMD_ID_ATA_SECTORS 1
#define ATA_SMART_READ_VALUES_SECTORS 1 //subcommand of ATA_CMD_SMART
#define ATA_SMART_READ_THRESHOLDS_SECTORS 1 //subcommand of ATA_CMD_SMART
#define ATA_WIN_SMART_READ_LOG_SECTORS 1 //subcommand of ATA_CMD_SMART
#define ATA_WIN_SMART_EXEC_TEST 1 //subcommand of ATA_CMD_SMART
/********************************** Parameters related to HDIO_DRIVE_TASK ioctl call **********************************/
//another method of calling some ioctls
// see https://github.com/mirror/smartmontools/blob/b63206bc12efb2ae543040b9008f42c037eb1f04/os_linux.cpp#L379
// also see HDIO_DRIVE_TASK in https://www.kernel.org/doc/Documentation/ioctl/hdio.txt
#define HDIO_DRIVE_TASK_HDR_OFFSET 7
#define HDIO_DRIVE_TASK_HDR_CMD 0 //command code
#define HDIO_DRIVE_TASK_HDR_FEATURE 1 //feature
#define HDIO_DRIVE_TASK_HDR_SEC_CNT 2 //sector count
#define HDIO_DRIVE_TASK_HDR_SEC_NUM 3 //sector number
#define HDIO_DRIVE_TASK_HDR_LCYL 4 //CYL LO
#define HDIO_DRIVE_TASK_HDR_HCYL 5 //CYL HI
#define HDIO_DRIVE_TASK_HDR_SEL 6 //device head
#define HDIO_DRIVE_TASK_RET_STATUS 0
#define HDIO_DRIVE_TASK_RET_ERROR 1
#define HDIO_DRIVE_TASK_RET_SEC_CNT 2 //sector count
#define HDIO_DRIVE_TASK_RET_SEC_NUM 3 //sector number
#define HDIO_DRIVE_TASK_RET_LCYL 4 //CYL LO
#define HDIO_DRIVE_TASK_RET_HCYL 5 //CYL HI
#define HDIO_DRIVE_TASK_RET_SEL 6 //device head
//all WIN_FT_* entries are defined under "WIN_SMART sub-commands" in hdreg.h
#define WIN_CMD_SMART 0xb0 //defined in full linux headers as WIN_SMART in hdreg.h
#define WIN_FT_SMART_IMMEDIATE_OFFLINE 0xd4
#define WIN_FT_SMART_READ_LOG_SECTOR 0xd5
#define WIN_FT_SMART_STATUS 0xda
#define WIN_FT_SMART_AUTOSAVE 0xd2 //this is not a typo (AUTOSAVE and AUTO_OFFLINE are spelled differently in ATA spec)
#define WIN_FT_SMART_AUTO_OFFLINE 0xdb
/*************************************** Params related to ATA IDENTIFY command ***************************************/
//Word numbers for the ATA IDENTIFY command response fields & bits in them (described in "struct hd_driveid")
#define ATA_ID_COMMAND_SET_1_SMART 0x01 //first bit of command set #1 contains SMART supported flag
#define ATA_ID_COMMAND_SET_2_VALID 0x4000 //14th bit with should always be 1 when disk supports cmd set 2
#define ATA_ID_CFS_ENABLE_1_SMART 0x01 //first bit of command set #1 contains SMART enable flag
#define ATA_ID_CSF_DEFAULT_VALID 0x4000 //14th bit with should always be 1 when disk supports that
/************************************************* ATA IDENTIFY macros ************************************************/
//These can be used with ATA IDENTIFY data returned by HDIO_GET_IDENTITY or HDIO_DRIVE_CMD=>ATA_CMD_IDENTIFY_DEV with
// when buffer is corrected by HDIO_DRIVE_CMD_HDR_OFFSET (to move over the header data)
#define ata_is_smart_supported(id_data) (((id_data)[ATA_ID_COMMAND_SET_2] & ATA_ID_COMMAND_SET_2_VALID) && \
((id_data)[ATA_ID_COMMAND_SET_1] & ATA_ID_COMMAND_SET_1_SMART))
#define ata_is_smart_enabled(id_data) (((id_data)[ATA_ID_CSF_DEFAULT] & ATA_ID_CSF_DEFAULT_VALID) && \
((id_data)[ATA_ID_CFS_ENABLE_1] & ATA_ID_CFS_ENABLE_1_SMART))
//set_/unset_ are deliberately asymmetrical here - we don't want to invalidate whole word when disabling SMART
#define ata_set_smart_supported(id_data) \
do { \
(id_data)[ATA_ID_COMMAND_SET_2] |= ATA_ID_COMMAND_SET_2_VALID; \
(id_data)[ATA_ID_COMMAND_SET_1] |= ATA_ID_COMMAND_SET_1_SMART; \
} while(0)
#define ata_reset_smart_supported(id_data) \
do { \
(id_data)[ATA_ID_COMMAND_SET_1] &= ~ATA_ID_COMMAND_SET_1_SMART; \
} while(0)
#define ata_set_smart_enabled(id_data) \
do { \
(id_data)[ATA_ID_CSF_DEFAULT] |= ATA_ID_CSF_DEFAULT_VALID; \
(id_data)[ATA_ID_CFS_ENABLE_1] |= ATA_ID_CFS_ENABLE_1_SMART; \
} while(0)
#define ata_reset_smart_enabled(id_data) \
do { \
(id_data)[ATA_ID_CFS_ENABLE_1] &= ~ATA_ID_CFS_ENABLE_1_SMART; \
} while(0)
/*********************************************** Miscellaneous constants **********************************************/
#define ATA_SMART_RECORD_LEN 12 //length of the SMART snapshot data row in bytes, defined
//Modified for kernel use - it's the "hd_driveid" struct from Linux include/uapi/linux/hdreg.h which represents a
// response to HDIO_GET_IDENTITY. See "Table 26 IDENTIFY DEVICE information" in ATA/ATAPI-6 spec for details.
struct rp_hd_driveid {
u16 config; /* lots of obsolete bit flags */
u16 cyls; /* Obsolete, "physical" cyls */
u16 reserved2; /* reserved (word 2) */
u16 heads; /* Obsolete, "physical" heads */
u16 track_bytes; /* unformatted bytes per track */
u16 sector_bytes; /* unformatted bytes per sector */
u16 sectors; /* Obsolete, "physical" sectors per track */
u16 vendor0; /* vendor unique */
u16 vendor1; /* vendor unique */
u16 vendor2; /* Retired vendor unique */
u8 serial_no[20]; /* 0 = not_specified */
u16 buf_type; /* Retired */
u16 buf_size; /* Retired, 512 byte increments
* 0 = not_specified
*/
u16 ecc_bytes; /* for r/w long cmds; 0 = not_specified */
u8 fw_rev[8]; /* 0 = not_specified */
u8 model[40]; /* 0 = not_specified */
u8 max_multsect; /* 0=not_implemented */
u8 vendor3; /* vendor unique */
u16 dword_io; /* 0=not_implemented; 1=implemented */
u8 vendor4; /* vendor unique */
u8 capability; /* (upper byte of word 49)
* 3: IORDYsup
* 2: IORDYsw
* 1: LBA
* 0: DMA
*/
u16 reserved50; /* reserved (word 50) */
u8 vendor5; /* Obsolete, vendor unique */
u8 tPIO; /* Obsolete, 0=slow, 1=medium, 2=fast */
u8 vendor6; /* Obsolete, vendor unique */
u8 tDMA; /* Obsolete, 0=slow, 1=medium, 2=fast */
u16 field_valid; /* (word 53)
* 2: ultra_ok word 88
* 1: eide_ok words 64-70
* 0: cur_ok words 54-58
*/
u16 cur_cyls; /* Obsolete, logical cylinders */
u16 cur_heads; /* Obsolete, l heads */
u16 cur_sectors; /* Obsolete, l sectors per track */
u16 cur_capacity0; /* Obsolete, l total sectors on drive */
u16 cur_capacity1; /* Obsolete, (2 words, misaligned int) */
u8 multsect; /* current multiple sector count */
u8 multsect_valid; /* when (bit0==1) multsect is ok */
unsigned int lba_capacity; /* Obsolete, total number of sectors */
u16 dma_1word; /* Obsolete, single-word dma info */
u16 dma_mword; /* multiple-word dma info */
u16 eide_pio_modes; /* bits 0:mode3 1:mode4 */
u16 eide_dma_min; /* min mword dma cycle time (ns) */
u16 eide_dma_time; /* recommended mword dma cycle time (ns) */
u16 eide_pio; /* min cycle time (ns), no IORDY */
u16 eide_pio_iordy; /* min cycle time (ns), with IORDY */
u16 words69_70[2]; /* reserved words 69-70
* future command overlap and queuing
*/
u16 words71_74[4]; /* reserved words 71-74
* for IDENTIFY PACKET DEVICE command
*/
u16 queue_depth; /* (word 75)
* 15:5 reserved
* 4:0 Maximum queue depth -1
*/
u16 words76_79[4]; /* reserved words 76-79 */
u16 major_rev_num; /* (word 80) */
u16 minor_rev_num; /* (word 81) */
u16 command_set_1; /* (word 82) supported
* 15: Obsolete
* 14: NOP command
* 13: READ_BUFFER
* 12: WRITE_BUFFER
* 11: Obsolete
* 10: Host Protected Area
* 9: DEVICE Reset
* 8: SERVICE Interrupt
* 7: Release Interrupt
* 6: look-ahead
* 5: write cache
* 4: PACKET Command
* 3: Power Management Feature Set
* 2: Removable Feature Set
* 1: Security Feature Set
* 0: SMART Feature Set
*/
u16 command_set_2; /* (word 83)
* 15: Shall be ZERO
* 14: Shall be ONE
* 13: FLUSH CACHE EXT
* 12: FLUSH CACHE
* 11: Device Configuration Overlay
* 10: 48-bit Address Feature Set
* 9: Automatic Acoustic Management
* 8: SET MAX security
* 7: reserved 1407DT PARTIES
* 6: SetF sub-command Power-Up
* 5: Power-Up in Standby Feature Set
* 4: Removable Media Notification
* 3: APM Feature Set
* 2: CFA Feature Set
* 1: READ/WRITE DMA QUEUED
* 0: Download MicroCode
*/
u16 cfsse; /* (word 84)
* cmd set-feature supported extensions
* 15: Shall be ZERO
* 14: Shall be ONE
* 13:6 reserved
* 5: General Purpose Logging
* 4: Streaming Feature Set
* 3: Media Card Pass Through
* 2: Media Serial Number Valid
* 1: SMART selt-test supported
* 0: SMART error logging
*/
u16 cfs_enable_1; /* (word 85)
* command set-feature enabled
* 15: Obsolete
* 14: NOP command
* 13: READ_BUFFER
* 12: WRITE_BUFFER
* 11: Obsolete
* 10: Host Protected Area
* 9: DEVICE Reset
* 8: SERVICE Interrupt
* 7: Release Interrupt
* 6: look-ahead
* 5: write cache
* 4: PACKET Command
* 3: Power Management Feature Set
* 2: Removable Feature Set
* 1: Security Feature Set
* 0: SMART Feature Set
*/
u16 cfs_enable_2; /* (word 86)
* command set-feature enabled
* 15: Shall be ZERO
* 14: Shall be ONE
* 13: FLUSH CACHE EXT
* 12: FLUSH CACHE
* 11: Device Configuration Overlay
* 10: 48-bit Address Feature Set
* 9: Automatic Acoustic Management
* 8: SET MAX security
* 7: reserved 1407DT PARTIES
* 6: SetF sub-command Power-Up
* 5: Power-Up in Standby Feature Set
* 4: Removable Media Notification
* 3: APM Feature Set
* 2: CFA Feature Set
* 1: READ/WRITE DMA QUEUED
* 0: Download MicroCode
*/
u16 csf_default; /* (word 87)
* command set-feature default
* 15: Shall be ZERO
* 14: Shall be ONE
* 13:6 reserved
* 5: General Purpose Logging enabled
* 4: Valid CONFIGURE STREAM executed
* 3: Media Card Pass Through enabled
* 2: Media Serial Number Valid
* 1: SMART selt-test supported
* 0: SMART error logging
*/
u16 dma_ultra; /* (word 88) */
u16 trseuc; /* time required for security erase */
u16 trsEuc; /* time required for enhanced erase */
u16 CurAPMvalues; /* current APM values */
u16 mprc; /* master password revision code */
u16 hw_config; /* hardware config (word 93)
* 15: Shall be ZERO
* 14: Shall be ONE
* 13:
* 12:
* 11:
* 10:
* 9:
* 8:
* 7:
* 6:
* 5:
* 4:
* 3:
* 2:
* 1:
* 0: Shall be ONE
*/
u16 acoustic; /* (word 94)
* 15:8 Vendor's recommended value
* 7:0 current value
*/
u16 msrqs; /* min stream request size */
u16 sxfert; /* stream transfer time */
u16 sal; /* stream access latency */
unsigned int spg; /* stream performance granularity */
unsigned long long lba_capacity_2;/* 48-bit total number of sectors */
u16 words104_125[22];/* reserved words 104-125 */
u16 last_lun; /* (word 126) */
u16 word127; /* (word 127) Feature Set
* Removable Media Notification
* 15:2 reserved
* 1:0 00 = not supported
* 01 = supported
* 10 = reserved
* 11 = reserved
*/
u16 dlf; /* (word 128)
* device lock function
* 15:9 reserved
* 8 security level 1:max 0:high
* 7:6 reserved
* 5 enhanced erase
* 4 expire
* 3 frozen
* 2 locked
* 1 en/disabled
* 0 capability
*/
u16 csfo; /* (word 129)
* current set features options
* 15:4 reserved
* 3: auto reassign
* 2: reverting
* 1: read-look-ahead
* 0: write cache
*/
u16 words130_155[26];/* reserved vendor words 130-155 */
u16 word156; /* reserved vendor word 156 */
u16 words157_159[3];/* reserved vendor words 157-159 */
u16 cfa_power; /* (word 160) CFA Power Mode
* 15 word 160 supported
* 14 reserved
* 13
* 12
* 11:0
*/
u16 words161_175[15];/* Reserved for CFA */
u16 words176_205[30];/* Current Media Serial Number */
u16 words206_254[49];/* reserved words 206-254 */
u16 integrity_word; /* (word 255)
* 15:8 Checksum
* 7:0 Signature
*/
} __packed;
#endif //REDPILL_HDPARAM_H

248
internal/scsi/scsi_notifier.c Executable file
View File

@ -0,0 +1,248 @@
/**
* Notification chain implementation for SCSI devices
*
* Linux kernel contains a subsystem responsible for delivering notifications about asynchronous events. It implements a
* pub/sub model. As many subsystems predate existence of the so-called Notification Chains these subsystems usually
* lack any pub/sub functionality. SCSI is no exception. SCSI layer/driver is ancient and huge. It does not have any way
* of delivering events to other parts of the system. This submodule retrofits notification chains to the SCSI layer to
* notify about new devices being added to the system. It can be easily extended to notify about removed devices as
* well.
*
* Before using this submodule you should read the notice below + the gitbooks article if you have never worked with
* Linux notification chains.
*
* !! READ ME - THIS IS IMPORTANT !!
* The core notifier.h contains the following return constants:
* - NOTIFY_DONE: "don't care about that event really"
* - NOTIFY_OK: "good, processed" (really the same as DONE; semantic is defined by a particular publisher[!])
* - NOTIFY_BAD: "stop the notification chain! I veto that action!"
* - NOTIFY_STOP: "stop the notification chain. It's all good."
* This SCSI notifier defines them as such:
* - NOTIFY_DONE, NOTIFY_OK: processed, continue calling other
* - NOTIFY_BAD:
* scsi_event=SCSI_EVT_DEV_PROBING: stop sd_probe() with EBUSY error; subscribers with lower priority will not exec
* scsi_event=SCSI_EVT_DEV_PROBED_OK: subscribers with lower priority will not exec
* scsi_event=SCSI_EVT_DEV_PROBED_ERR: subscribers with lower priority will not exec
* - NOTIFY_STOP:
* scsi_event=SCSI_EVT_DEV_PROBING: stop sd_probe() with 0 err-code; subscribers with lower priority will not exec
* scsi_event=SCSI_EVT_DEV_PROBED_OK: subscribers with lower priority will not exec
* scsi_event=SCSI_EVT_DEV_PROBED_ERR: subscribers with lower priority will not exec
*
* SUPPORTED DEVICES
* Currently only SCSI disks are supported. This isn't a technical limitation but rather a practical one - we don't want
* to trigger notifications for all-all SCSI devices (which include hosts, buses, etc). If needed a new set of functions
* subscribe_.../ubsubscribe_... can easily be added which don't filter by type.
*
* TODO
* This notifier does not support notifying about disconnection of the device. It should as we need to know if device
* disappeared (e.g. while processing shimming of boot devices).
*
* ADDITIONAL TOOLS
* It is highly recommended to use scsi_toolbox when subscribing to notifications from the SCSI subsystem.
*
* References:
* - https://0xax.gitbooks.io/linux-insides/content/Concepts/linux-cpu-4.html (about notification chains subsystem)
*/
#include "scsi_notifier.h"
#include "../../common.h"
#include "../notifier_base.h" //notifier_*()
#include "scsi_notifier_list.h"
#include "scsi_toolbox.h"
#include "../intercept_driver_register.h" //watching for sd driver loading
#include <scsi/scsi_device.h> //to_scsi_device()
#include <scsi/scsi_host.h>
#define NOTIFIER_NAME "SCSI device"
/*********************************** Interacting with an active/loaded SCSI driver ************************************/
static driver_watcher_instance *driver_watcher = NULL;
static int (*org_sd_probe) (struct device *dev) = NULL; //set during register
/**
* Main notification routine hooking sd_probe()
*/
static int sd_probe_shim(struct device *dev)
{
pr_loc_dbg("Probing SCSI device using %s", __FUNCTION__);
if (!is_scsi_leaf(dev)) {
pr_loc_dbg("%s: new SCSI device connected - not a leaf, ignoring", __FUNCTION__);
return org_sd_probe(dev);
}
struct scsi_device *sdp = to_scsi_device(dev);
if (!is_scsi_disk(sdp)) {
pr_loc_dbg("%s: new SCSI device connected - not a disk, ignoring", __FUNCTION__);
return org_sd_probe(dev);
}
// by jim3ma:
// dsm 7.2 check syno_port_type in sd_probe, when syno_port_type == 1(SYNO_PORT_TYPE_SATA), the disk is sata
// for disks from hba card like Microsemi Adaptec HBA 1000-8i with aacraid driver, syno_port_type is always 0, we need change it to 1(SYNO_PORT_TYPE_SATA), otherwise sd_probe will return error with -22
// solution: update syno_port_type in hba driver
if (sdp->host->hostt->syno_port_type == 0) {
pr_loc_err("syno_port_type is 0, please update syno_port_type and syno_block_info in disk driver");
}
pr_loc_dbg("Triggering SCSI_EVT_DEV_PROBING notifications");
int out = notifier_to_errno(blocking_notifier_call_chain(&rp_scsi_notify_list, SCSI_EVT_DEV_PROBING, sdp));
if (unlikely(out == NOTIFY_STOP)) {
pr_loc_dbg("After SCSI_EVT_DEV_PROBING a callee stopped chain with non-error condition. Faking probe-ok.");
return 0;
} else if (unlikely(out == NOTIFY_BAD)) {
pr_loc_dbg("After SCSI_EVT_DEV_PROBING a callee stopped chain with non-error condition. Faking probe-ok.");
return -EIO; //some generic error
}
pr_loc_dbg("Calling original sd_probe()");
out = org_sd_probe(dev);
scsi_event evt = (out == 0) ? SCSI_EVT_DEV_PROBED_OK : SCSI_EVT_DEV_PROBED_ERR;
pr_loc_dbg("Triggering SCSI_EVT_DEV_PROBED notifications - sd_probe() exit=%d", out);
blocking_notifier_call_chain(&rp_scsi_notify_list, evt, sdp);
return out;
}
/**
* Overrides sd_probe() to provide notifications via sd_probe_shim()
*
* @param drv "sd" driver instance
*/
static inline void install_sd_probe_shim(struct device_driver *drv)
{
pr_loc_dbg("Overriding %pf()<%p> with %pf()<%p>", drv->probe, drv->probe, sd_probe_shim, sd_probe_shim);
org_sd_probe = drv->probe;
drv->probe = sd_probe_shim;
}
/**
* Removes override of sd_probe(), installed by install_sd_probe_shim()
*
* @param drv "sd" driver instance
*/
static inline void uninstall_sd_probe_shim(struct device_driver *drv)
{
if (unlikely(!org_sd_probe)) {
pr_loc_wrn(
"Cannot %s - original drv->probe is not saved. It was either never installed or it's a bug. "
"The current drv->probe is %pf()<%p>",
__FUNCTION__, drv->probe, drv->probe);
return;
}
pr_loc_dbg("Restoring %pf()<%p> to %pf()<%p>", drv->probe, drv->probe, org_sd_probe, org_sd_probe);
drv->probe = org_sd_probe;
org_sd_probe = NULL;
}
/**
* Watches for the sd driver to load in order to shim it. The driver registration is modified before the driver loads.
*/
static driver_watch_notify_result sd_load_watcher(struct device_driver *drv, driver_watch_notify_state event)
{
if (unlikely(event != DWATCH_STATE_COMING))
return DWATCH_NOTIFY_CONTINUE;
pr_loc_dbg("%s driver loaded - triggering sd_probe shim installation", SCSI_DRV_NAME);
install_sd_probe_shim(drv);
driver_watcher = NULL; //returning DWATCH_NOTIFY_DONE causes automatic unwatching
return DWATCH_NOTIFY_DONE;
}
/******************************************** Public API of the notifier **********************************************/
extern struct bus_type scsi_bus_type;
int subscribe_scsi_disk_events(struct notifier_block *nb)
{
notifier_sub(nb);
return blocking_notifier_chain_register(&rp_scsi_notify_list, nb);
}
int unsubscribe_scsi_disk_events(struct notifier_block *nb)
{
notifier_unsub(nb);
return blocking_notifier_chain_unregister(&rp_scsi_notify_list, nb);
}
// We need an additional flag as depending on which method of sd_probe override (watcher vs. existing driver find &
// switch)
static bool notifier_registered = false;
int register_scsi_notifier(void)
{
notifier_reg_in();
if (unlikely(notifier_registered)) {
pr_loc_bug("%s notifier is already registered", NOTIFIER_NAME);
return -EEXIST;
}
struct device_driver *drv = find_scsi_driver();
if(unlikely(drv < 0)) { //some error occurred while looking for the driver
return PTR_ERR(drv); //find_scsi_driver() should already log what went wrong
} else if(drv) { //the driver is already loaded - driver watcher cannot help us
pr_loc_wrn(
"The %s driver was already loaded when %s notifier registered - some devices may already be registered",
SCSI_DRV_NAME, NOTIFIER_NAME);
install_sd_probe_shim(drv);
} else { //driver not yet loaded - driver watcher will trigger sd_probe_shim installation when driver loads
pr_loc_dbg("The %s driver is not ready to dispatch %s notifier events - awaiting driver", SCSI_DRV_NAME,
NOTIFIER_NAME);
driver_watcher = watch_scsi_driver_register(sd_load_watcher, DWATCH_STATE_COMING);
if (unlikely(IS_ERR(driver_watcher))) {
pr_loc_err("Failed to register driver watcher for driver %s", SCSI_DRV_NAME);
return PTR_ERR(driver_watcher);
}
}
notifier_registered = true;
notifier_reg_ok();
return 0;
}
int unregister_scsi_notifier(void)
{
notifier_ureg_in();
if (unlikely(!notifier_registered)) {
pr_loc_bug("%s notifier is not registered", NOTIFIER_NAME);
return -ENOENT;
}
bool is_error = false;
int out = -EINVAL;
//Check if we're watching sd driver (i.e. SCSI notifier was registered and is now being unregistered before the
// driver had a chance to load)
if (unlikely(driver_watcher)) {
pr_loc_dbg("%s notifier is still observing %s driver - stopping observer", NOTIFIER_NAME, SCSI_DRV_NAME);
out = unwatch_driver_register(driver_watcher);
if (unlikely(out != 0)) {
pr_loc_err("Failed to unregister driver watcher - error=%d", out);
is_error = true;
}
}
//sd_probe() was replaced either after watching for the driver or on-the-spot after the driver was already loaded
if (likely(org_sd_probe)) {
struct device_driver *drv = find_scsi_driver();
if (unlikely(IS_ERR(drv))) {
return PTR_ERR(drv); //find_scsi_driver() should already log what went wrong
} else if(likely(drv)) {
uninstall_sd_probe_shim(drv);
} else { //that is almost impossible as sd is built-in, but we if it happens there's nothing to recover
pr_loc_wrn("%s driver went away (?!)", SCSI_DRV_NAME);
is_error = true;
}
}
notifier_registered = false;
if (unlikely(is_error)) {
return out;
} else {
notifier_ureg_ok();
return 0;
}
}

29
internal/scsi/scsi_notifier.h Executable file
View File

@ -0,0 +1,29 @@
#ifndef REDPILL_SCSI_NOTIFIER_H
#define REDPILL_SCSI_NOTIFIER_H
#include <linux/notifier.h> //All other parts including scsi_notifier.h cannot really not use linux/notifier.h
typedef enum {
SCSI_EVT_DEV_PROBING, //device is being probed; it can be modified or outright ignored
SCSI_EVT_DEV_PROBED_OK, //device is probed and ready
SCSI_EVT_DEV_PROBED_ERR, //device was probed but it failed
} scsi_event;
/**
* Callback signature: void (*f)(struct notifier_block *self, unsigned long state, void *data), where:
* unsigned long state => scsi_event event
* void *data => struct scsi_device *sdp
*
* Currently these methods are DELIBERATELY limited to SCSI TYPE_DISK scope. If you need other SCSI devices watching
* add another set of methods (subscribe scsi_device_events() and such, do NOT extend the scope of these methods as
* other parts of the code rely on pre-filtered events as in most cases listening for ALL devices is a lot of noise).
*
* @return
*/
int subscribe_scsi_disk_events(struct notifier_block *nb);
int unsubscribe_scsi_disk_events(struct notifier_block *nb);
int register_scsi_notifier(void);
int unregister_scsi_notifier(void);
#endif //REDPILL_SCSI_NOTIFIER_H

View File

@ -0,0 +1,4 @@
#include "scsi_notifier_list.h"
#include <linux/notifier.h>
BLOCKING_NOTIFIER_HEAD(rp_scsi_notify_list);

View File

@ -0,0 +1,27 @@
/**
* This file exists solely as a workaround for GCC bug #275674 - static structures are misdirected as dynamic
*
* Linux contains many clever idioms. One of them is a complex initialization of heads for notifier chains
* (include/linux/notifier.h). They do contain an embedded cast to a struct. GCC <5 detects that as a dynamic allocation
* and refuses to initialize it statically. This breaks all the macros for notifier (e.g. BLOCKING_NOTIFIER_INIT). Old
* kernels (i.e. <3.18) cannot be compiled with GCC >4.9 so... we cannot use a newer GCC but we cannot use older due to
* a bug. One of the solutions would be to convert the whole code of this module to GNU89 but this is painful to use.
*
* Such structures are working in GNU89 mode as well as when defined as a heap variable in a function. However, GCC is
* smart enough to release the memory from within a function (so we cannot just wrap it in a function and return a ptr).
* Due to the complex nature of the struct we didn't want to hardcode it here as they change between kernel version.
* As a workaround we created a separate compilation unit containing just the struct and compile it in GNU89 mode, while
* rest of the project stays at GNU99.
*
* Resources
* - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63567 (bug report)
* - https://unix.stackexchange.com/a/275674 (kernel v3.18 restriction)
* - https://stackoverflow.com/a/49119902 (linking files compiled with different language standard in GCC)
* - https://www.kernel.org/doc/Documentation/kbuild/makefiles.txt (compilation option per file in Kbuild; sect. 3.7)
*/
#ifndef REDPILL_SCSI_NOTIFIER_LIST_H
#define REDPILL_SCSI_NOTIFIER_LIST_H
extern struct blocking_notifier_head rp_scsi_notify_list;
#endif //REDPILL_SCSI_NOTIFIER_LIST_H

246
internal/scsi/scsi_toolbox.c Executable file
View File

@ -0,0 +1,246 @@
#include "scsi_toolbox.h"
#include "scsiparam.h" //SCSI_*
#include "../../common.h"
#include "../../internal/call_protected.h" //scsi_scan_host_selected()
#include <linux/dma-direction.h> //DMA_FROM_DEVICE
#include <linux/unaligned/be_byteshift.h> //get_unaligned_be32()
#include <linux/delay.h> //msleep
#include <scsi/scsi.h> //cmd consts (e.g. SERVICE_ACTION_IN), SCAN_WILD_CARD, and TYPE_DISK
#include <scsi/scsi_eh.h> //struct scsi_sense_hdr, scsi_sense_valid()
#include <scsi/scsi_host.h> //struct Scsi_Host, SYNO_PORT_TYPE_SATA
#include <scsi/scsi_transport.h> //struct scsi_transport_template
#include <scsi/scsi_device.h> //struct scsi_device, scsi_execute_req(), scsi_is_sdev_device()
extern struct bus_type scsi_bus_type; //SCSI bus type for driver scanning
/**
* Issues SCSI "READ CAPACITY (16)" command
* Make sure you read what this function returns!
*
* @param sdp
* @param buffer Pointer to a buffer of size SCSI_BUF_SIZE
* @param sshdr Sense header
* @return 0 on command success, >0 if command failed; if the command failed it MAY be repeated
*/
static int scsi_read_cap16(struct scsi_device *sdp, unsigned char *buffer, struct scsi_sense_hdr *sshdr)
{
unsigned char cmd[16];
memset(cmd, 0, 16);
cmd[0] = SCSI_SERVICE_ACTION_IN_16;
cmd[1] = SAI_READ_CAPACITY_16;
cmd[13] = SCSI_RC16_LEN;
memset(buffer, 0, SCSI_RC16_LEN);
return scsi_execute_req(sdp, cmd, DMA_FROM_DEVICE, buffer, SCSI_RC16_LEN, sshdr, SCSI_CMD_TIMEOUT,
SCSI_CMD_MAX_RETRIES, NULL);
}
/**
* Issues SCSI "READ CAPACITY (10)" command
* Make sure you read what this function returns!
*
* @param sdp
* @param buffer Pointer to a buffer of size SCSI_BUF_SIZE
* @param sshdr Sense header
* @return 0 on command success, >0 if command failed; if the command failed it MAY be repeated
*/
static int scsi_read_cap10(struct scsi_device *sdp, unsigned char *buffer, struct scsi_sense_hdr *sshdr)
{
unsigned char cmd[16];
cmd[0] = READ_CAPACITY;
memset(&cmd[1], 0, 9);
memset(buffer, 0, 8);
return scsi_execute_req(sdp, cmd, DMA_FROM_DEVICE, buffer, 8, sshdr, SCSI_CMD_TIMEOUT, SCSI_CMD_MAX_RETRIES, NULL);
}
long long opportunistic_read_capacity(struct scsi_device *sdp)
{
//some drives work only with the 16 version but older ones can only accept the older variant
//to prevent false-positive "command failed" we need to try both
bool use_cap16 = true;
unsigned char *buffer = NULL;
kmalloc_or_exit_int(buffer, SCSI_BUF_SIZE);
int out;
int sense_valid = 0;
struct scsi_sense_hdr sshdr;
int read_retry = SCSI_CAP_MAX_RETRIES;
do {
//It can return 0 or a positive integer; 0 means immediate success where 1 means an error. Depending on the error
//the command may be repeated.
out = (use_cap16) ? scsi_read_cap16(sdp, buffer, &sshdr) : scsi_read_cap10(sdp, buffer, &sshdr);
if (out == 0)
break; //command just succeeded
if (unlikely(out > 0)) { //it's technically an error but we may be able to recover
if (use_cap16) { //if we previously used CAP(16) and it failed we can try older CAP(10) [even on hard-fail]
use_cap16 = false;
continue;
}
//Some failures are hard-failure (e.g. drive doesn't support the cmd), some are soft-failures
//In soft failures some are known to take more time (e.g. spinning rust is spinning up) and some should be
//fast-repeat. We really only distinguish hard from soft and just wait some time for others
//In a normal scenario this path will be cold as the drive will respond to CAP(16) or CAP(10) right away.
sense_valid = scsi_sense_valid(&sshdr);
if (!sense_valid) {
pr_loc_dbg("Invalid sense - trying again");
continue; //Sense invalid, this can be repeated right away
}
//Drive deliberately rejected the request and indicated that this situtation will not change
if (sshdr.sense_key == ILLEGAL_REQUEST && (sshdr.asc == 0x20 || sshdr.asc == 0x24) && sshdr.ascq == 0x00) {
pr_loc_err("Drive refused to provide capacity");
kfree(buffer);
return -EINVAL;
}
//Drive is busy - wait for some time
if (sshdr.sense_key == UNIT_ATTENTION && sshdr.asc == 0x29 && sshdr.ascq == 0x00) {
pr_loc_dbg("Drive busy during capacity pre-read (%d attempts left), trying again", read_retry-1);
msleep(500); //if it's a spinning rust over USB we may need to wait
continue;
}
}
} while (--read_retry);
if (out != 0) {
pr_loc_err("Failed to pre-read capacity of the drive after %d attempts due to SCSI errors",
(SCSI_CAP_MAX_RETRIES - read_retry));
kfree(buffer);
return -EIO;
}
unsigned sector_size = get_unaligned_be32(&buffer[8]);
unsigned long long lba = get_unaligned_be64(&buffer[0]);
//Good up to 8192000000 pebibytes - good luck overflowing that :D
long long size_mb = ((lba+1) * sector_size) / 1024 / 1024; //sectors * sector size = size in bytes
kfree(buffer);
return size_mb;
}
bool is_scsi_disk(struct scsi_device *sdp)
{
return (likely(sdp) && (sdp)->type == TYPE_DISK);
}
bool is_sata_disk(struct device *dev)
{
//from the kernel's pov SCSI devices include SCSI hosts, "leaf" devices, and others - this filters real SCSI devices
if (!is_scsi_leaf(dev))
return false;
struct scsi_device *sdp = to_scsi_device(dev);
//end/leaf devices can be disks or other things - filter only real disks
//more than that use syno's private property (hey! not all of their kernel mods are bad ;)) to determine port which
//a given device uses (vanilla kernel doesn't care about silly ports - SCSI is SCSI)
if (!is_scsi_disk(sdp) || sdp->host->hostt->syno_port_type != SYNO_PORT_TYPE_SATA)
return false;
return true;
}
int scsi_force_replug(scsi_device *sdp)
{
if (unlikely(!is_scsi_leaf(&sdp->sdev_gendev))) {
pr_loc_bug("%s expected SCSI leaf - got something else", __FUNCTION__);
return -EINVAL;
}
struct Scsi_Host *host = sdp->host;
pr_loc_dbg("Removing device from host%d", host->host_no);
scsi_remove_device(sdp); //this will do locking for remove
//See drivers/scsi/scsi_sysfs.c:scsi_scan() for details
if (unlikely(host->transportt->user_scan)) {
pr_loc_dbg("Triggering template-based rescan of host%d", host->host_no);
return host->transportt->user_scan(host, SCAN_WILD_CARD, SCAN_WILD_CARD, SCAN_WILD_CARD);
} else {
pr_loc_dbg("Triggering generic rescan of host%d", host->host_no);
//this is unfortunately defined in scsi_scan.c, it can be emulated because it's just bunch of loops, but why?
//This will also most likely never be used anyway
return _scsi_scan_host_selected(host, SCAN_WILD_CARD, SCAN_WILD_CARD, SCAN_WILD_CARD, 1);
}
}
//We assume that if the sd was loaded once it will never unload (as on most kernels it's built in).
//If this assumption changes the cache can simply be removed
bool sd_driver_loaded = false;
struct device_driver *find_scsi_driver(void)
{
struct device_driver *drv = driver_find("sd", &scsi_bus_type);
if (IS_ERR(drv)) {
pr_loc_err("Failed to query sd driver status - error=%ld", PTR_ERR(drv));
return drv;
}
if (drv) {
sd_driver_loaded = true;
return drv;
}
return NULL;
}
int is_scsi_driver_loaded(void)
{
if (likely(sd_driver_loaded))
return true;
struct device_driver *drv = find_scsi_driver();
if (IS_ERR(drv)) //get_scsi_driver() will already print an error message
return PTR_ERR(drv);
return drv ? SCSI_DRV_LOADED : SCSI_DRV_NOT_LOADED;
}
/**
* Filters out all SCSI leafs and calls the callback prescribed
*/
static int for_each_scsi_leaf_filter(struct device *dev, on_scsi_device_cb cb)
{
if (!is_scsi_leaf(dev))
return 0;
return (cb)(to_scsi_device(dev));
}
/**
* Filters out all SCSI disks and calls the callback prescribed
*/
static int for_each_scsi_disk_filter(struct device *dev, on_scsi_device_cb cb)
{
if (!is_scsi_leaf(dev))
return 0;
struct scsi_device *sdp = to_scsi_device(dev);
if (!is_scsi_disk(sdp))
return 0;
return (cb)(to_scsi_device(dev));
}
static int inline for_each_scsi_x(on_scsi_device_cb *cb, int (*filter)(struct device *dev, on_scsi_device_cb cb))
{
if (!is_scsi_driver_loaded())
return -ENXIO;
int code = bus_for_each_dev(&scsi_bus_type, NULL, cb, (int (*)(struct device *, void *))filter);
return unlikely(code == -ENXIO) ? -EIO : code;
}
int for_each_scsi_leaf(on_scsi_device_cb *cb)
{
return for_each_scsi_x(cb, for_each_scsi_leaf_filter);
}
int for_each_scsi_disk(on_scsi_device_cb *cb)
{
return for_each_scsi_x(cb, for_each_scsi_disk_filter);
}

100
internal/scsi/scsi_toolbox.h Executable file
View File

@ -0,0 +1,100 @@
#ifndef REDPILL_SCSI_TOOLBOX_H
#define REDPILL_SCSI_TOOLBOX_H
#include <linux/types.h> //bool
typedef struct device device;
typedef struct scsi_device scsi_device;
typedef int (on_scsi_device_cb)(struct scsi_device *sdp);
#define SCSI_DRV_NAME "sd" //useful for triggering watchers
//To use this one import intercept_driver_register.h header (it's not imported here to avoid pollution)
#define watch_scsi_driver_register(callback, event_mask) \
watch_driver_register(SCSI_DRV_NAME, (callback), (event_mask))
#define IS_SCSI_DRIVER_ERROR(state) (unlikely((state) < 0))
typedef enum {
SCSI_DRV_NOT_LOADED = 0,
SCSI_DRV_LOADED = 1,
} scsi_driver_state;
/**
* From the kernel's pov SCSI devices include SCSI hosts, "leaf" devices, and others - this filters real SCSI devices
*
* This is simply an alias for scsi_is_sdev_device() which is more descriptive for people who aren't SCSI wizards.
*
* @param dev struct device*
*/
#define is_scsi_leaf(dev) scsi_is_sdev_device(dev)
/**
* Attempts to read capacity of a device assuming reasonably modern pathway
*
* This function (along with scsi_read_cap{10|16}) is loosely based on drivers/scsi/sd.c:sd_read_capacity(). However,
* this method cuts some corners to be faster as we're expecting rather modern hardware. Additionally, functions from
* sd.c cannot be used as they're static. Even that some of them can be called using kallsyms they aren't stateless and
* will cause a KP later on (as they modify the device passed to them).
* Thus this function should be seen as a way to quickly estimate (as it reports full mebibytes rounded down) the
* capacity without causing side effects.
*
* @param sdp
* @return capacity in full mebibytes, or -E on error
*/
long long opportunistic_read_capacity(struct scsi_device *sdp);
/**
* Checks if a SCSI device is a SCSI-complain disk (e.g. SATA, SAS, iSCSI etc)
*
* To be 101% sure and proper you should probably call is_scsi_leaf() first
*/
bool is_scsi_disk(struct scsi_device *sdp);
/**
* Checks if a given generic device is an SCSI disk connected to a SATA port/host controller
*
* Every SATA disk, by definition, will also be an SCSI disk (as SATA is a connector carrying SCSI commands)
*/
bool is_sata_disk(struct device *dev);
/**
* Triggers a re-probe of SCSI leaf device by forcefully "unplugging" and "replugging" the device
*
* WARNING: be careful what are you doing - this method is no different than yanking a power cable from a device, so if
* you do that with a disk which is used data loss may occur!
*
* @return 0 on success, -E on error
*/
int scsi_force_replug(scsi_device *sdp);
/**
* Locates & returns SCSI driver structure if loaded
*
* @return driver struct on success, NULL if driver is not loaded, ERR_PTR(-E) on error
*/
struct device_driver *find_scsi_driver(void);
/**
* Checks if SCSI driver is loaded or not
*
* This function is useful to make a decision whether to just watch for new devices or watch for new ones + scan
* existing ones. You cannot just scan blindly as this will cause an error.
*
* @return 0 if not loaded, 1 if loaded, -E on error; see scsi_driver_state enum for constants
*/
int is_scsi_driver_loaded(void);
/**
* Traverses list of all SCSI devices and calls the callback with every leaf/terminal device found
*
* @return 0 on success, -E on failure. -ENXIO is reserved to always mean that the driver is not loaded
*/
int for_each_scsi_leaf(on_scsi_device_cb *cb);
/**
* Traverses list of all SCSI devices and calls the callback with every SCSCI-complaint disk found
*
* @return 0 on success, -E on failure. -ENXIO is reserved to always mean that the driver is not loaded
*/
int for_each_scsi_disk(on_scsi_device_cb *cb);
#endif //REDPILL_SCSI_TOOLBOX_H

24
internal/scsi/scsiparam.h Executable file
View File

@ -0,0 +1,24 @@
/**
* This file contains a list of cherry-picked constants useful while dealing with SCSI subsystem
*/
#ifndef REDPILL_SCSIPARAM_H
#define REDPILL_SCSIPARAM_H
#include <linux/version.h> //KERNEL_VERSION_CODE, KERNEL_VERSION()
#include <scsi/scsi.h> //SERVICE_ACTION_IN or SERVICE_ACTION_IN_16
#define SCSI_RC16_LEN 32 //originally defined in drivers/scsi/sd.c as RC16_LEN
#define SCSI_CMD_TIMEOUT (30 * HZ) //originally defined in drivers/scsi/sd.h as SD_TIMEOUT
#define SCSI_CMD_MAX_RETRIES 5 //normal drives shouldn't fail the command even once
#define SCSI_CAP_MAX_RETRIES 3
#define SCSI_BUF_SIZE 512 //originally defined in drivers/scsi/sd.h as SD_BUF_SIZE
//Old kernels used ambiguous constant: https://github.com/torvalds/linux/commit/eb846d9f147455e4e5e1863bfb5e31974bb69b7c
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,19,0)
#define SCSI_SERVICE_ACTION_IN_16 SERVICE_ACTION_IN
#else
#define SCSI_SERVICE_ACTION_IN_16 SERVICE_ACTION_IN_16
#endif
#endif //REDPILL_SCSIPARAM_H

60
internal/stealth.c Executable file
View File

@ -0,0 +1,60 @@
#include "stealth.h"
#include "stealth/sanitize_cmdline.h"
#include <linux/module.h> //struct module (for list_del)
//TODO:
//https://github.com/xcellerator/linux_kernel_hacking/blob/master/3_RootkitTechniques/3.0_hiding_lkm/rootkit.c
//remove file which was used for insmod
//remove kernel taint
//remove module loading from klog
//delete module file from ramdisk
int initialize_stealth(void *config2)
{
struct runtime_config *config = config2;
int error = 0;
#if STEALTH_MODE <= STEALTH_MODE_OFF
//STEALTH_MODE_OFF shortcut
return error;
#endif
#if STEALTH_MODE > STEALTH_MODE_OFF
//These are STEALTH_MODE_BASIC ones
if ((error = register_stealth_sanitize_cmdline(config->cmdline_blacklist)) != 0)
return error;
#endif
#if STEALTH_MODE > STEALTH_MODE_BASIC
//These will be STEALTH_MODE_NORMAL ones
#endif
#if STEALTH_MODE > STEALTH_MODE_NORMAL
//These will be STEALTH_MODE_FULL ones
list_del(&THIS_MODULE->list);
#endif
return error;
}
int uninitialize_stealth(void)
{
int error;
#if STEALTH_MODE > STEALTH_MODE_NORMAL
//These will be STEALTH_MODE_FULL ones
#endif
#if STEALTH_MODE > STEALTH_MODE_BASIC
//These will be STEALTH_MODE_NORMAL ones
#endif
#if STEALTH_MODE > STEALTH_MODE_OFF
//These are STEALTH_MODE_BASIC ones
if ((error = unregister_stealth_sanitize_cmdline()) != 0)
return error;
#endif
//Mode set to STEALTH_MODE_OFF or nothing failed before
return error;
}

29
internal/stealth.h Executable file
View File

@ -0,0 +1,29 @@
/*
* This header file should be included as the first one before anything else so other header files can use STEALTH_MODE
*/
#ifndef REDPILLLKM_STEALTH_H
#define REDPILLLKM_STEALTH_H
#define STEALTH_MODE_OFF 0 //Nothing is hidden, useful for full-on debugging
#define STEALTH_MODE_BASIC 1 //Hides basic things like cmdline (which is more to prevent DSM code from complaining about unknown options etc.)
#define STEALTH_MODE_NORMAL 2 //Hides everything except making the module not unloadable
#define STEALTH_MODE_FULL 3 //Same as STEALTH_MODE_NORMAL + removes the module from list of loaded modules & all logs
//Define just after levels so other headers can use it if needed to e.g. replace some macros
#ifndef STEALTH_MODE
//#warning "Stealth mode not specified - using default"
#define STEALTH_MODE STEALTH_MODE_BASIC
#endif
//Some compile-time stealthiness
#if STEALTH_MODE > STEALTH_MODE_OFF //STEALTH_MODE_BASIC or above
#define VIRTUAL_UART_THREAD_FMT "irq/%d-serial" //pattern format for vUART kernel thread which spoofs IRQ one
#endif
struct runtime_config;
int initialize_stealth(void *config);
int uninitialize_stealth(void);
#endif //REDPILLLKM_STEALTH_H

View File

@ -0,0 +1,162 @@
/*
* This submodule removes blacklisted entries from /proc/cmdline to hide some options after the LKM is loaded
*
* OVERVIEW
* The main reason to sanitize cmdline is to avoid leaking information like "vid=..." or "pid=..." to userspace. Doing
* this may cause some other external modules parsing kernel cmdline to be confused why such options are present in the
* boot params.
*
* HOW IT WORKS?
* The module overrides cmdline_proc_show() from fs/proc/cmdline.c with a jump to our implementation. The implementation
* here serves a filtrated version of the cmdline.
*
* WHY OVERRIDE A STATIC METHOD?
* This module has actually been rewritten to hard-override cmdline_proc_show() instead of "gently" finding the dentry
* for /proc/cmdline, and then without modifying the dentry replacing the read operation in file_operations struct.
* While this method is much cleaner and less invasive it has two problems:
* - Requires "struct proc_dir_entry" (which is internal and thus not available in toolkit builds)
* - Doesn't work if the module is loaded as ioscheduler (as funny enough this code will execute BEFORE /proc/cmdline
* is created)
* This change has been made in commit "Rewrite cmdline sanitize to replace cmdline_proc_show".
*
* FILTRATION
* The second part of the code deals with the actual filtration. List of blacklisted entries is passed during
* registration (to allow flexibility). Usually it will be gathered form pre-generated config. Then a filtrated copy of
* cmdline is created once (as this is a quite expensive string-ridden operation).
* The only sort-of way to find the original implementation is to access the kmesg buffer where the original cmdline is
* baked into early on boot. Technically we can replace that too but this will get veeery messy and I doubt anyone will
* try dig through kmesg messages with a regex for cmdline (especially that with a small dmesg buffer it will roll over)
*/
#include "sanitize_cmdline.h"
#include "../../common.h"
#include "../../config/cmdline_delegate.h" //get_kernel_cmdline() & CMDLINE_MAX
#include "../override/override_symbol.h" //override_symbol() & restore_symbol()
#include <linux/seq_file.h> //seq_file, seq_printf()
/**
* Pre-generated filtered cmdline (by default it's an empty string in case it's somehow printed before filtration)
* See filtrate_cmdline() for details
*/
static char *filtrated_cmdline = NULL;
/**
* Check if a given cmdline token is on the blacklist
*/
static bool
is_token_blacklisted(const char *param_pointer, cmdline_token *cmdline_blacklist[MAX_BLACKLISTED_CMDLINE_TOKENS]) {
for (int i = 0; i < MAX_BLACKLISTED_CMDLINE_TOKENS; i++) {
if (!cmdline_blacklist[i])
return false;
if (strncmp(param_pointer, (char *)cmdline_blacklist[i], strlen((char *)cmdline_blacklist[i])) == 0)
return true;
}
return false;
}
/**
* Filters-out all blacklisted entries from the cmdline string (fetched from /proc/cmdline)
*/
static int filtrate_cmdline(cmdline_token *cmdline_blacklist[MAX_BLACKLISTED_CMDLINE_TOKENS])
{
char *raw_cmdline;
kmalloc_or_exit_int(raw_cmdline, strlen_to_size(CMDLINE_MAX));
long cmdline_len = get_kernel_cmdline(raw_cmdline, CMDLINE_MAX);
if(unlikely(cmdline_len < 0)) { //if <0 it's an error code
pr_loc_dbg("get_kernel_cmdline failed with %ld", cmdline_len);
kfree(raw_cmdline);
return (int) cmdline_len;
}
filtrated_cmdline = kmalloc(strlen_to_size(cmdline_len), GFP_KERNEL);
if (unlikely(!filtrated_cmdline)) {
kfree(raw_cmdline);
kalloc_error_int(filtrated_cmdline, strlen_to_size(cmdline_len));
}
char *single_param_chunk; //Pointer to the beginning of the cmdline token
char *filtrated_ptr = &filtrated_cmdline[0]; //Pointer to the current position in filtered
size_t curr_param_len;
while ((single_param_chunk = strsep(&raw_cmdline, CMDLINE_SEP)) != NULL) {
if (single_param_chunk[0] == '\0') //Skip empty
continue;
if (is_token_blacklisted(single_param_chunk, cmdline_blacklist)) {
pr_loc_dbg("Cmdline param \"%s\" blacklisted - skipping", single_param_chunk);
continue;
}
curr_param_len = strlen(single_param_chunk);
memcpy(filtrated_ptr, single_param_chunk, curr_param_len);
filtrated_ptr += curr_param_len;
*(filtrated_ptr++) = ' ';
}
*(filtrated_ptr-1) = '\0'; //Terminate whole param string (removing the trailing space)
kfree(raw_cmdline);
pr_loc_dbg("Sanitized cmdline to: %s", filtrated_cmdline);
return 0;
}
/**
* Handles fs/proc/ semantics for reading. See include/linux/fs.h:file_operations.read for details.
*/
static int cmdline_proc_show_filtered(struct seq_file *m, void *v)
{
seq_printf(m, "%s\n", filtrated_cmdline);
return 0;
}
static override_symbol_inst *ov_cmdline_proc_show = NULL;
int register_stealth_sanitize_cmdline(cmdline_token *cmdline_blacklist[MAX_BLACKLISTED_CMDLINE_TOKENS])
{
if (unlikely(ov_cmdline_proc_show)) {
pr_loc_bug("Attempted to %s while already registered", __FUNCTION__);
return 0; //Technically it succeeded
}
int out;
//This has to be done once (we're assuming cmdline doesn't change without reboot). In case this submodule is
// re-registered the filtrated_cmdline is left as-is and reused
if (!filtrated_cmdline && (out = filtrate_cmdline(cmdline_blacklist)) != 0)
return out;
ov_cmdline_proc_show = override_symbol("cmdline_proc_show", cmdline_proc_show_filtered);
if (unlikely(IS_ERR(ov_cmdline_proc_show))) {
out = PTR_ERR(ov_cmdline_proc_show);
pr_loc_err("Failed to override cmdline_proc_show - error %d", out);
ov_cmdline_proc_show = NULL;
return out;
}
pr_loc_inf("/proc/cmdline sanitized");
return 0;
}
int unregister_stealth_sanitize_cmdline(void)
{
if (unlikely(!ov_cmdline_proc_show)) {
pr_loc_bug("Attempted to %s while it's not registered", __FUNCTION__);
return 0; //Technically it succeeded
}
int out = restore_symbol(ov_cmdline_proc_show);
//We deliberately fall through here without checking as we have to free stuff at this point no matter what
kfree(filtrated_cmdline);
filtrated_cmdline = NULL;
if (likely(out == 0))
pr_loc_inf("Original /proc/cmdline restored");
else
pr_loc_err("Failed to restore original /proc/cmdline: org_cmdline_proc_show failed - error %d", out);
return out;
}

View File

@ -0,0 +1,23 @@
#ifndef REDPILL_SANITIZE_CMDLINE_H
#define REDPILL_SANITIZE_CMDLINE_H
#include "../../config/cmdline_delegate.h" //MAX_BLACKLISTED_CMDLINE_TOKENS
/**
* Register submodule sanitizing /proc/cmdline
*
* After registration /proc/cmdline will be non-destructively cleared from entries listed in cmdline_blacklist param.
* It can be reversed using unregister_stealth_sanitize_cmdline()
*
* @return 0 on success, -E on error
*/
int register_stealth_sanitize_cmdline(cmdline_token *cmdline_blacklist[MAX_BLACKLISTED_CMDLINE_TOKENS]);
/**
* Reverses what register_stealth_sanitize_cmdline() did
*
* @return 0 on success, -E on error
*/
int unregister_stealth_sanitize_cmdline(void);
#endif //REDPILL_SANITIZE_CMDLINE_H

472
internal/uart/uart_swapper.c Executable file
View File

@ -0,0 +1,472 @@
/**
* This tool is an isolated UART port-swapping utility allowing you to swap any two ports on runtime
*
* REASONING
* Some kernels are compiled with CONFIG_SYNO_X86_SERIAL_PORT_SWAP set which effectively swaps first two serial ports.
* This function reverses that. It also makes sure to move kernel console output between them (if configured).
*
*
* OVERVIEW
* Swapping the serials involves two things: swapping console drivers and swapping kernel console printk itself.
* The first one can be done on any kernel by modifying exported "console_drivers". The second one requires an access
* to either struct serial8250_ports (drivers/tty/serial/8250/8250_core.c) or to struct console_cmdline
* (kernel/printk/printk.c). Both of them are static so they're no-go directly.
* Kernels before v4.1 had a convenient method update_console_cmdline(). Unfortunately this method was removed:
* https://github.com/torvalds/linux/commit/c7cef0a84912cab3c9df8949b034e4aa62982ec9 so there's currently no method
* of un-swapping on v4. Even worse calling this method on lower kernels is a combination of luck and timing (as this is
* a init-only method).
*
*
* IMPLEMENTATION
* Things we've tried and failed:
* - Set the new console as preferred (making it default for /dev/console) -> /dev/ttyS0 and 1 are still wrong
* - Unregistering both ports and re-registering them -> we tried a bit and it's a nightmare to re-do and crashes the
* kernel
* - Unregistering and re-registering consoles -> will fail with KP or do nothing (and even if it worked if a
* non-console port is involved it will be broken)
* - Recreating the flow for serial8250_isa_init_ports() -> it appears to work (i.e. doesn't crash) but the serial port
* is dead afterwards and doesn't pass any traffic (we've never discovered why)
* => Hours wasted trying to reverse stupid ports: 37
*
* What actually did work was carefully split-stopping the port (stopping the driver/hardware end of but not the higher
* level ttyS# side), exchanging the iobase & IRQ (+ some other internal things generated during init), restarting the
* port and hoping for the best. It does work BUT it's ridden with edge cases. Properly implementing and testing this
* took ~3 full days and two people... so if you're not sure what you're doing here you can easily break it.
*
*
* INTERNAL DETAILS
* What we are doing here may not be intuitive if you don't exactly know how the 8250 driver works internally. Let's go
* over this first. Each port is composed of outer uart_8250_port which contains various driver-specific information.
* Inside it there's an uart_port struct which contains the information about the actual physical UART channel.
*
* When the ports were flipped their position in the internal 8250 list, their line# in uart_8250_port and other
* internal properties were kept intact. The only two things which were changed are ->port.iobase and ->port.irq
* Flipping these is enough to make port sort-of-working. The port will pass data BUT only if the other port triggers
* interrupts (e.g. you type something on ttyS0 and nothing happens, you hold a space bar on ttyS1 afterwards and
* stuff you've typed on ttyS0 starts appearing). This strange effect is caused by how 8250 driver implements IRQ
* handling (serial8250_interrupt in 8250_core.c). It does not iterate over ALL ports looking for ports which match
* the IRQ triggering the function. [FROM NOW ON READ CAREFULLY!] Instead, it uses and struct irq_info passed as
* "user data" to the IRQ handler function. That struct contains a POINTER to a list_head of the first port, making
* the irq_struct the list owner. That doubly-linked list ties together multiple uart_8250_port structures (->list)
* which share the same IRQ. Here's the major problem: changing the NUMERIC IRQ value in uart_8250_port->port.irq
* does nothing to the actual IRQ handling for that port.
*
* When an IRQ happens and serial8250_interrupt() fires, it looks at the irq_struct, gets the pointer to list_head
* containing all active ports sharing a given IRQ and then just iterates over them triggering their internal
* handling of stuff. The irq_struct list_head pointer is just a memory address and our swaps of anything will never
* change it. This means two things: we need to fix the shared IRQ lists in uart_8250_port for ttyS0 and ttyS1 AND
* fix the mapping of IRQ => uart_8250_port element. Keep in mind that uart_8250_port->list contains only the ports
* which are currently active (i.e. enabled AND open). Kernel registers for IRQs only if something actually opens
* the port (as there's no point to receive data where you don't place to deliver them).
*
* Unfortunately, fixing these lists isn't really possible. The irq_struct contains a pointer to list_head which can
* be in any of the uart_8250_port. If it points to ttyS0 and/or ttyS1 (spoiler: most likely yes, as they're open)
* deleting an element from that list will break it completely (as irq_struct only knows the address of that one
* uart_8250_port in practice). If we replace prev/next to point to a different port it will still break because
* the interrupt handler calls container_of() which make prev/next irrelevant for the first fetch.
* struct_irq is contained in two places: internally cached in the 8250_core.c and as a pointer in the kernel IRQ
* handling. It will be unwise to try to modify it (ok, we're saying this only because we found a better way :D).
*
* Normally the IRQ manipulation is enabled/disabled by serial_link_irq_chain/serial_unlink_irq_chain in 8250_core.c
* but they're static. However, we an exploit the fact that 8250 driver is modular and operations on the Linux port
* are separated from operations on the hardware (which makes sense). We can command the UART chip to shutdown
* before we touch iobase or irq (which naturally has to remove IRQ if present) and then command the chip to startup
* which will register IRQ if needed (for the new irq value of course ;))
*
* References:
* - Linux kernel sources (mainly drivers/tty/serial/8250/8250_core.c and drivers/tty/serial/serial_core.c)
* - https://linux-kernel-labs.github.io/refs/heads/master/labs/interrupts.html
* - https://www.ti.com/lit/ug/sprugp1/sprugp1.pdf
*/
#include "../../common.h"
#include "../call_protected.h" //early_serial_setup()
#include "../override/override_symbol.h" //overriding uart_match_port()
#include "../../config/uart_defs.h" //struct uart_port, COM ports definition, UART_NR
#include <linux/serial_8250.h> //struct uart_8250_port
#include <linux/console.h> //console_lock(), console_unlock()
#include <linux/hardirq.h> //synchronize_irq()
#include <linux/list.h> //LIST_POISON1, LIST_POISON2
#include <linux/timer.h> //timer_pending()
#include <linux/interrupt.h> //disable_irq()/enable_irq()
#include <linux/irq.h> // irq_common_data
#include <linux/irqdesc.h> //irq_has_action
#define pause_irq_save(irq) ({bool __state = irq_has_action(irq); if (__state) { disable_irq(irq); } __state; })
#define resume_irq_saved(irq, saved) if (saved) { enable_irq(irq); }
/*********************************************** Extracting 8250 ports ************************************************/
static struct uart_8250_port *serial8250_ports[UART_NR] = { NULL }; //recovered ptrs to serial8250_ports structure
static override_symbol_inst *ov_uart_match_port = NULL;
/**
* Fake uart_match_port() which always returns "no match" but collects all passing ports to serial8250_ports
*
* See recover_serial8250_ports() for usage. This is a very specific thing and shouldn't be used standalone.
*
* @return 0
*/
static int uart_match_port_collector(struct uart_port *port1, struct uart_port *port2)
{
//our fake trigger calls with one port being NULL, that's how we can easily detect which one is the one provided by
//the driver ;]
struct uart_port *port = port1 ? port1:port2;
pr_loc_dbg("Found ptr to line=%d iobase=0x%03lx irq=%d", port->line, port->iobase, port->irq);
serial8250_ports[port->line] = container_of(port, struct uart_8250_port, port);
return 0;
}
/**
* Enables collecting of 8250 serial port structures
*
* Warning: before you do that you MUST disable IRQs or you're risking a serious crash or a silent corruption of the
* kernel!
*
* @return 0 on success, -E on failure
*/
static int __must_check enable_collector_matcher(void)
{
if (unlikely(ov_uart_match_port))
return 0; //it's not a problem is we already enabled it as it's enabled all the time
ov_uart_match_port = override_symbol("uart_match_port", uart_match_port_collector);
if (unlikely(IS_ERR(ov_uart_match_port))) {
int out = PTR_ERR(ov_uart_match_port);
ov_uart_match_port = NULL;
return out;
}
return 0;
}
/**
* Disabled collecting of 8250 serial port structures (reverses enable_collector_matcher())
*
* @return 0 on success or noop, -E on failure
*/
static int disable_collector_matcher(void)
{
if (unlikely(!ov_uart_match_port))
return 0; //it's not a problem is we already disabled it
int out = restore_symbol(ov_uart_match_port);
ov_uart_match_port = NULL;
if (unlikely(out != 0))
pr_loc_err("Failed to disable collector matcher, error=%d", out);
return out;
}
/**
* Fish-out 8250 serial driver ports from its internal structures
*
* The 8250 serial driver is very secretive of its ports and doesn't allow anyone to access them. This is for a good
* reason - it's very easy to cause a deadlock, KP, or a runaway CPU-hogging process. However, we must access them as
* we're intentionally messing up with the structures of them (as SOMEONE had a BRILLIANT idea to break them by swapping
* iobases and IRQs defined since the 1970s).
*
* Ports will be populated in a serial8250_ports.
*/
static int recover_serial8250_ports(void)
{
int out = 0;
//Stops and buffers printks while pulling console semaphore down (in case console is active on any of the ports)
console_lock();
preempt_disable();
//We cannot acquire any locks as we don't have ports information. The most we can do is ensure nothing tirggers
// while we collect them. It's imperfect as some ports are timer based etc. However, the chance is abysmal that
// with preempt disabled and IRQs disabled something magically triggers ports lookup (which is rare by itself).
//While there may be more than 4 ports their IRQs aren't well defined by the platform nor kernel.
//Some of these may be shared but we don't make assumptions here (as it will be a noop if we call it twice)
bool com1_irq_state = pause_irq_save(STD_COM1_IRQ);
bool com2_irq_state = pause_irq_save(STD_COM2_IRQ);
bool com3_irq_state = pause_irq_save(STD_COM3_IRQ);
bool com4_irq_state = pause_irq_save(STD_COM4_IRQ);
if (unlikely((out = enable_collector_matcher()) != 0)) { //Install a fake matching function
pr_loc_err("Failed to enable collector!");
goto out;
}
_serial8250_find_port(NULL); //Force the driver to iterate over all its ports... using our fake matching function
if (unlikely((out = disable_collector_matcher()) != 0)) //Restore normal matcher
pr_loc_err("Failed to enable collector!");
//Other processes will use spinlocks with IRQ-save as we now know the ports
out:
resume_irq_saved(STD_COM1_IRQ, com1_irq_state);
resume_irq_saved(STD_COM2_IRQ, com2_irq_state);
resume_irq_saved(STD_COM3_IRQ, com3_irq_state);
resume_irq_saved(STD_COM4_IRQ, com4_irq_state);
preempt_enable();
console_unlock();
return out;
}
/**
* Gets an internal 8250 driver port structure for the line/ttyS specified
*
* Things to know:
* - line = ttyS#, so line=0 = ttyS0 (this is universal across Linux UART subsystem)
* - this function returns things as-is in the 8250 driver, so if ports are already reversed you will get them reversed
* - this function only runs scanning once but only ptrs are stored, so if you flip ports the re-scan is not needed as
* 8250 builds its internal array (to which elements we get ptrs) only once during boot
*
* @return ptr to a port OR error ptr with -E
*/
static __must_check struct uart_8250_port *get_8250_port(unsigned int line)
{
if (unlikely(line >= UART_NR)) {
pr_loc_bug("Requested UART line %u but kernel supports up to %u", line, UART_NR);
return ERR_PTR(-EINVAL);
}
if (!serial8250_ports[0]) //Port not recovered or port 0 doesn't exist (HIGHLY unlikely)
recover_serial8250_ports(); //there's no point in checking the return code here - it will fail below
return (likely(serial8250_ports[line])) ? serial8250_ports[line] : ERR_PTR(-ENODEV);
}
/****************************************** Shutting down & restarting ports ******************************************/
#define is_irq_port(uart_port_ptr) ((uart_port_ptr)->irq != 0)
/**
* Check if IRQ-based port is active (i.e. open and running)
*
* To use this function the caller is responsible for obtaining a port spinlock.
*
* Warning: it's up to the CALLER to check type of the port (is_irq_port()). Passing a timer-based port here will
* always return false, as timer ports don't register for IRQs and are not listed in IRQ-sharing list.
*/
static bool __always_inline is_irq_port_active(struct uart_8250_port *up)
{
struct uart_port *port = &up->port;
//if the kernel doesn't have an action for the IRQ there's no way 8250 has the port active in interrupt mode
if (!irq_has_action(port->irq)) {
pr_loc_dbg("IRQ=%d not active => port not active", port->irq);
return false;
}
//IRQ port list was never initialized, or it was deleted (which poisons it) => list element is invalid
// We don't care where prev/next point - they can point both at us (=we're the only ones active on that IRQ),
// can both point at a single other element (=two element list with us included), or can point to two different
// elements (=list with >2 elements). Either way WE are active.
if (!up->list.prev || !up->list.next) {
pr_loc_dbg("IRQ sharing list not initialized => port not active");
return false;
}
if (up->list.next == LIST_POISON1 && up->list.prev == LIST_POISON2) {
pr_loc_dbg("IRQ sharing list poisoned/deleted => port not active");
return false;
}
pr_loc_dbg("Port is active (IRQ=%d active, list valid p=%p/n=%p)", port->irq, up->list.prev, up->list.next);
return true;
}
/**
* Checks if a timer-based port is active (i.e. open and running)
*
* To use this function the caller is responsible for obtaining a port spinlock.
*
* For the timer-based port to be active it must: have a function set (=it was configured at least once), and be
* in active or pending state. We only care about the pending one as time timer cannot be active (=currently
* executing handler function) when we have a lock on the port.
*
* Warning: it's up to the CALLER to check type of the port (is_irq_port()). Passing IRQ port here will always return
* false, as IRQ ports don't use timers.
*/
static bool __always_inline is_timer_port_active(struct uart_8250_port *up)
{
return (likely(up->timer.function) && timer_pending(&up->timer));
}
/**
* Checks if a given port is active (i.e. open and running)
*
* The startup & shutdown of the port is needed any time the port is active/open. The port is formally shut down if
* there's nooone using it. The 8250 driver doesn't really know that (ok, it does if you try to probe the chip etc)
* directly, as only the TTY serial layer tracks that (drivers/tty/serial/serial_core.c).
*
* The trick here is that We cannot check if the driver has the
* interrupt for a given port directly (as the irq_lists is static). However, we can derive this by checking if the
* kernel has the IRQ handler registered for the given IRQ# *AND* if the port in question is part of the list for
* the IRQ. We can cheat here as we only need to know our own state. So in practice we need to just check if our
* list element (embedded list_head) is valid. See code for details.
* While technically we CAN re-shutdown a port as many times as we want AS LONG AS it's not using the IRQ subsystem we
* shouldn't re-start a port which wasn't started before we tinkered with it! This is why we take care of IRQ and non-
* IRQ ports in the same. If you attempt to shutdown already shutdown port which is an IRQ one it will result in a
* kernel BUG() as the driver detects that something went wrong as it expects the IRQ to be running. If you do the
* same with timer-based port it will simply re-clear registries on the UART chip which will be a noop hardware-wise.
* This is because the 8250 and derivates cannot be really turned off once they start/reset. They can only be set in a
* way that they don't deliver interrupts for new data (and any new data will just override existing one). With timer-
* based port the kernel simply don't ask the chip if there's any data but the chip is still running. This is exactly
* why the 8250 driver will always attempt a read before "starting" the port and clear FIFOs on it.
*/
static bool is_port_active(struct uart_8250_port *up)
{
bool out;
struct uart_port *port = &up->port;
pr_loc_dbg("Checking if port iobase=0x%03lx irq=%d (mapped to ttyS%d) active", port->iobase, port->irq, port->line);
//Most of the ports will be IRQs unless something's broken/special about the platform
if (likely(is_irq_port(port)))
out = is_irq_port_active(up);
else
out = is_timer_port_active(up);
return out;
}
/**
* Shuts down the port if it's active
*
* You should NOT call this function with a lock active!
*
* @return 0 if the operation resulted in noop, 1 if the port was actually shut down; currently there are no error
* conditions
*/
static inline int try_shutdown_port(struct uart_8250_port *up)
{
struct uart_port *port = &up->port;
pr_loc_dbg("Shutting down physical port iobase=0x%03lx (mapped to ttyS%d)", port->iobase, port->line);
if (!is_port_active(up)) {
pr_loc_dbg("Port not active - noop");
return 0;
}
port->ops->shutdown(port); //this must be called with the lock released or otherwise a deadlock may occur
if (is_irq_port(port))
synchronize_irq(port->irq); //Make sure interrupt handler is not running on another CPU/core
pr_loc_dbg("Port iobase=0x%03lx ttyS%d is now DOWN", port->iobase, port->line);
return 1;
}
/**
* Restart previously stopped port
*
* Warnings:
* - you shouldn't attempt to restart ports which weren't configured; this can lead to a KP
* - you should NOT call this function when holding a lock
*
*/
static inline void restart_port(struct uart_8250_port *up)
{
struct uart_port *port = &up->port;
pr_loc_dbg("Restarting physical port iobase=0x%03lx (mapped to ttyS%d)", port->iobase, port->line);
//We are not checking if the port is active here due to an edge case of swap between one port which is active where
// another one isn't. In such case when we shut down that active port and try to activate the other (to keep the
// userland state happy) the check will lead to a false-negative state saying the port is already active. This is
// because we did swap IRQ values. However, we MUST restart such port not to reinit the hardware (which doesn't
// care) but to fix the interrupt mapping in the kernel!
//skip extensive tests - it was working before
#if LINUX_VERSION_CODE <= KERNEL_VERSION(5,0,0)
port->flags |= UPF_NO_TXEN_TEST;
#else
port->quirks |= UPQ_NO_TXEN_TEST;
#endif
port->flags |= UPF_SKIP_TEST;
port->ops->startup(port); //this must be called with the lock released or otherwise a deadlock may occur
pr_loc_dbg("Port iobase=0x%03lx ttyS%d is now UP", port->iobase, port->line);
}
/*************************************************** Swapping logic ***************************************************/
/**
* Swaps two UART data lines with proper locking
*
* This function assumes ports are already stopped.
*/
static inline void swap_uart_lanes(struct uart_8250_port *a, struct uart_8250_port *b)
{
unsigned long flags_a, flags_b;
spin_lock_irqsave(&a->port.lock, flags_a);
spin_lock_irqsave(&b->port.lock, flags_b);
swap(a->port.iobase, b->port.iobase);
swap(a->port.irq, b->port.irq);
swap(a->port.uartclk, b->port.uartclk); //Just to be complete we should move flags & clock
swap(a->port.flags, b->port.flags); // (they're probably the same anyway)
swap(a->timer, b->timer); //if one port was timer based and another wasn't this ensures they aren't broken
spin_unlock_irqrestore(&a->port.lock, flags_b); //flags_a were a property of B
spin_unlock_irqrestore(&b->port.lock, flags_a);
}
int uart_swap_hw_output(unsigned int from, unsigned int to)
{
if (unlikely(from == to))
return -EINVAL;
pr_loc_dbg("Swapping ttyS%d<=>ttyS%d started", from, to);
struct uart_8250_port *port_a = get_8250_port(from);
struct uart_8250_port *port_b = get_8250_port(to);
if (unlikely(!port_a)) {
pr_loc_err("Failed to locate ttyS%d port", from);
return PTR_ERR(port_a);
}
if (unlikely(!port_b)) {
pr_loc_err("Failed to locate ttyS%d port", to);
return PTR_ERR(port_b);
}
pr_loc_dbg("Disabling preempt & locking console");
pr_loc_inf("======= OUTPUT ON THIS PORT WILL STOP AND CONTINUE ON ANOTHER ONE (swapping ttyS%d & ttyS%d) =======",
from, to); //That will be the last message user sees before swap on the "old" port
pr_loc_dbg("### LAST MESSAGE BEFORE SWAP ON \"OLD\" PORT ttyS%d<=>ttyS%d", from, to);
preempt_disable(); //we cannot be rescheduled here due to timing constraint and possibly IRQ interactions
console_lock(); //We don't want stray messages landing somewhere randomly when we swap, + the ports will be down
//this will be the first message after port unlocks after swapping
pr_loc_dbg("### FIRST MESSAGE AFTER SWAP ON \"NEW\" PORT ttyS%d<=>ttyS%d", from, to);
//This is an edge case when swapping two ports where one is active and another one is not. Since the active status
// is a property of the software (i.e. port opened/used by something) and shutting down/starting alters the state
// of the hardware we may have a problem with restarting the previously inactive port. If WE did shut it down there
// is no issue as we know the hardware is initialized. But if it wasn't and we try to just start it up without
// reinit we can either crash the driver or leave the port in inactive state.
pr_loc_dbg("Disabling ports");
int port_a_was_running = try_shutdown_port(port_a);
int port_b_was_running = try_shutdown_port(port_b);
if (unlikely(port_a_was_running != port_b_was_running))
pr_loc_wrn("Swapping hw data paths of ttyS%d (was %sactive) and ttyS%d (was %sactive). We will attempt to "
"reactivate inactive one but this may fail.", port_a->port.line, port_a_was_running ? "" : "in",
port_b->port.line, port_b_was_running ? "" : "in");
swap_uart_lanes(port_a, port_b);
//This code IS CORRECT - make sure to read comment next to port_a_was_running/port_b_was_running vars initialization
//We swapped the data paths but we need to restore the state as the userland expects it.
pr_loc_dbg("Restarting ports");
if (port_a_was_running)
restart_port(port_a);
if (port_b_was_running)
restart_port(port_b);
console_unlock();
preempt_enable();
pr_loc_inf("======= OUTPUT ON THIS PORT CONTINUES FROM A DIFFERENT ONE (swapped ttyS%d & ttyS%d) =======", from,
to);
pr_loc_dbg("Swapping ttyS%d (curr_iob=0x%03lx) <=> ttyS%d (curr_iob=0x%03lx) finished successfully", from,
port_a->port.iobase, to, port_b->port.iobase);
return 0;
}

18
internal/uart/uart_swapper.h Executable file
View File

@ -0,0 +1,18 @@
#ifndef REDPILL_UART_SWAPPER_H
#define REDPILL_UART_SWAPPER_H
/**
* Swaps two given UARTs/serial prots so that their data paths are exchanged without the change of /dev/tty#
*
* This method is blind to whether UARTs were swapped during kernel build. However, it's the reason it exists to un-swap
* these stupid ports. You can swap any ports you want. It's not recommended to swap ports which are in different run
* state (i.e. one is active/open/running and the other one is not). In such cases the swap will be attempted BUT the
* port which was active may not be usable until re-opened (usually it will be, but there's a chance).
*
* @param from Line number (line = ttyS#, so line=0 = ttyS0; this is universal across Linux UART subsystem))
* @param to Line number
* @return 0 on success or -E on error
*/
int uart_swap_hw_output(unsigned int from, unsigned char to);
#endif //REDPILL_UART_SWAPPER_H

1032
internal/uart/virtual_uart.c Executable file

File diff suppressed because it is too large Load Diff

126
internal/uart/virtual_uart.h Executable file
View File

@ -0,0 +1,126 @@
#ifndef REDPILL_VIRTUAL_UART_H
#define REDPILL_VIRTUAL_UART_H
#include <linux/types.h> //bool
/**
* Length of the RX/TX FIFO in bytes
* Do NOT change this value just because you want to inject more data at once - it's a hardware-defined property
*/
#define VUART_FIFO_LEN 16
/**
* Defines maximum threshold possible; in practice this means you will never get any THRESHOLD events but only ID:E and
* FULL ones.
*/
#define VUART_THRESHOLD_MAX INT_MAX
/**
* Specified the reason why the vUART flushed the buffer
*
* This value carries one guarantee: they will be evaluated in that order of priority. If you set a threshold to exactly
* VUART_FIFO_LEN and the application sends exactly VUART_FIFO_LEN bytes you will get a reason of VUART_FLUSH_THRESHOLD
* even thou all three conditions are met. If you set the threshold to 10 and the app sends 12 bytes you will get a call
* with VUART_FLUSH_THRESHOLD after 10 bytes, then another one with two bytes and VUART_FLUSH_IDLE.
*/
typedef enum {
//Threshold specified while setting the callback has been reached
VUART_FLUSH_THRESHOLD,
//Kernel put the transmitter in an idle mode, which most of the time indicated end of transmission/packet
VUART_FLUSH_IDLE,
//FIFO was full before threshold has been reached and the transmission isn't finished yet
VUART_FLUSH_FULL,
} vuart_flush_reason ;
/**
* Represents a callback signature
*
* @param line UART# where the data arrived; you can ignore it if you registered only one UART
* @param buffer Place where you can read the data from; as of now this is the same buffer but don't rely on this!
* @param len Number of bytes you're allowed to read from beginning of the buffer
* @param reason Denotes why the vUART decided to flush the buffer to the callback
*/
typedef void (vuart_callback_t)(int line, const char *buffer, unsigned int len, vuart_flush_reason reason);
/**
* Adds a virtual UART device
*
* Calling this function will immediately yank the port from the real one and began capturing its output, so that no
* data will leave through the real one. However, by itself the data will not be delivered anywhere until you call
* vuart_set_tx_callback(), which you can do before or after calling vuart_add_device().
*
* @param line UART number to replace, e.g. 0 for ttyS0. On systems with inverted UARTs you should use the real one, so
* even if ttyS0 points to 2nd physical port this method will ALWAYS use the one corresponding to ttyS*
*
* @return 0 on success or -E on error
*/
int vuart_add_device(int line);
/**
* Removes a virtual UART device
*
* Calling this function restores previously replaced port. Unlike vuart_add_device() this function WILL alter TX
* callbacks by removing all of them. The reasoning behind this is that adding a device and later on adding/changing
* callbacks makes sense while removing the device and potentially leaving broken pointers can lead to nasty and hard to
* trace bugs.
*
* @param line UART number to replace, e.g. 0 for ttyS0. On systems with inverted UARTs you should use the real one, so
* even if ttyS0 points to 2nd physical port this method will ALWAYS use the one corresponding to ttyS*
*
* @return 0 on success or -E on error
*/
int vuart_remove_device(int line);
/**
* Injects data into RX stream of the port
*
* It may be confusing at first what's TX and RX in the context here. Imagine a physical chip connected to a computer
* with some bus (not UART). The chip's RX is what the chip would get from *something*. So injecting data into RX of the
* chip causes the data to be processed by the chip and arrive in the kernel and then in the application which opened
* the port. So while TX implies "transmission" from the perspective of the chip and the app opening the port it's an
* RX side. This naming is consistent with what the whole 8250 subsystem uses.
*
* @param line UART number to replace, e.g. 0 for ttyS0. On systems with inverted UARTs you should use the real one, so
* even if ttyS0 points to 2nd physical port this method will ALWAYS use the one corresponding to ttyS*
* @param buffer Pointer to a buffer where we will read from. There's no assumption as to what the buffer contains.
* @param length Length to read from the buffer up to VUART_FIFO_LEN
*
* @return 0 on success or -E on error
*/
int vuart_inject_rx(int line, const char *buffer, int length);
/**
* Set a function which will be called upon data transmission by the port opener
*
* In short you will get data which some app (e.g. cat file > /dev/ttyS0) sent. If you're confused by the RX/TX read the
* comment for inject_rx().
*
* Example of the callback usage:
* //The len is the real number of bytes available to read. The buffer ptrs is the same as you gave to set_tx_
* void dummy_tx_callback(int line, const char *buffer, int len, vuart_flush_Reason reason) {
* pr_loc_inf("TX @ ttyS%d: |%.*s|", line, len, buffer);
* }
* //....
* char buf[VUART_FIFO_LEN]; //Your buffer should be able to accommodate at least VUART_FIFO_LEN
* vuart_set_tx_callback(TRY_PORT, dummy_tx_callback, buf, VUART_FIFO_LEN);
*
* WARNING:
* You callback should be multithreading-aware. It may be called from different contexts. You shouldn't do a lot of work
* on the thread where your callback has been called. If you need something more copy the buffer and process it on a
* separate thread.
*
* @param line UART number to replace, e.g. 0 for ttyS0. On systems with inverted UARTs you should use the real one, so
* even if ttyS0 points to 2nd physical port this method will ALWAYS use the one corresponding to ttyS*
* @param cb Function to be called; call it with a NULL ptr to remove callback, see docblock for vuart_callback_t
* @param buffer A pointer to a buffer where data will be placed. The buffer should be able to accommodate
* VUART_FIFO_LEN number of bytes. The buffer you pass will be the same one as passed back during a call
* @param threshold a *HINT* how many bytes at minimum should be deposited in the FIFO before callback is called. Keep
* in mind that this is just a hint and you callback may be called sooner (e.g. when a client program
* wrote only a single byte using e.g. echo -n X > /dev/ttyS0).
* @return 0 on success or -E on error
*/
int vuart_set_tx_callback(int line, vuart_callback_t *cb, char *buffer, int threshold);
#endif //REDPILL_VIRTUAL_UART_H

73
internal/uart/vuart_internal.h Executable file
View File

@ -0,0 +1,73 @@
#ifndef REDPILL_VUART_INTERNAL_H
#define REDPILL_VUART_INTERNAL_H
#include <linux/spinlock.h>
#ifndef VUART_USE_TIMER_FALLBACK
#include <linux/wait.h>
#endif
//Lock/unlock vdev for registries operations
#define lock_vuart(vdev) spin_lock_irqsave((vdev)->lock, (vdev)->lock_flags);
#define unlock_vuart(vdev) spin_unlock_irqrestore((vdev)->lock, (vdev)->lock_flags);
//In some circumstances operations may be performed on the chip before or after the chip is initialized. If it is
// initialized we need a lock first; otherwise we do not. This is a shortcut for this opportunistic/conditional locking.
#define lock_vuart_oppr(vdev) if ((vdev)->initialized) { lock_vuart(vdev); }
#define unlock_vuart_oppr(vdev) if ((vdev)->initialized) { unlock_vuart(vdev); }
#define validate_isa_line(line) \
if (unlikely((line) > SERIAL8250_LAST_ISA_LINE)) { \
pr_loc_bug("%s failed - requested line %d but kernel supports only %d", __FUNCTION__, line, \
SERIAL8250_LAST_ISA_LINE); \
return -EINVAL; \
}
/**
* An emulated 16550A chips internal state
*
* See http://caro.su/msx/ocm_de1/16550.pdf for details; registers are on page 9 (Table 2)
*/
struct serial8250_16550A_vdev {
//Port properties
u8 line;
u16 iobase;
u8 irq;
unsigned int baud;
//The 8250 driver port structure - it will be populated as soon as 8250 gives us the real pointer
struct uart_port *up;
//Chip emulated FIFOs
struct kfifo *tx_fifo; //character to be sent (aka what we've got from the OS)
struct kfifo *rx_fifo; //characters received (aka what we want the OS to get from us)
//Chip registries (they're considered volatile but there's a spinlock protecting them)
u8 rhr; //Receiver Holding Register (characters received)
u8 thr; //Transmitter Holding Register (characters REQUESTED to be sent, TSR will contain these to be TRANSMITTED)
u8 ier; //Interrupt Enable Register
u8 iir; //Interrupt ID Register (same as ISR/Interrupt Status Register)
u8 fcr; //FIFO Control Register (not really used but holds values written to it)
u8 lcr; //Line Control Register (not really used but holds values written to it)
u8 mcr; //Modem Control Register (used to control autoflow)
u8 lsr; //Line Status Register
u8 msr; //Modem Status Register
u8 scr; //SCratch pad Register (in the original docs refered to as SPR, but linux uses SCR name)
u8 dll; //Divisor Lat Least significant byte (not really used but holds values written to it)
u8 dlm; //Divisor Lat Most significant byte (not really used but holds values written to it; also called DLH)
u8 psd; //Prescaler Division (not really used but holds values written to it)
//Some operations (e.g. FIFO access) must be locked
bool initialized:1;
bool registered:1; //whether the vdev is actually registered with 8250 subsystem
spinlock_t *lock;
unsigned long lock_flags;
#ifndef VUART_USE_TIMER_FALLBACK
//We emulate (i.e. self-trigger) interrupts on threads
struct task_struct *virq_thread; //where fake interrupt code is executed
wait_queue_head_t *virq_queue; //wait queue used to put thread to sleep
#endif
};
#endif //REDPILL_VUART_INTERNAL_H

153
internal/uart/vuart_virtual_irq.c Executable file
View File

@ -0,0 +1,153 @@
#ifndef VUART_USE_TIMER_FALLBACK
#include "vuart_virtual_irq.h"
#include "vuart_internal.h"
#include "../../common.h"
#include "../../debug/debug_vuart.h"
#include <linux/serial_reg.h> //UART_* consts
#include <linux/kthread.h> //running vIRQ thread
#include <linux/wait.h> //wait queue handling (init_waitqueue_head etc.)
#include <linux/serial_8250.h> //serial8250_handle_irq
//Default name of the thread for vIRQ
#ifndef VUART_THREAD_FMT
#define VUART_THREAD_FMT "vuart/%d-ttyS%d"
#endif
/**
* Function running on a separate kernel thread responsible for simulating the IRQ call (normally done via hardware
* interrupt triggering CPU to invoke Linux IRQ subsystem)
*
* There's no sane way to trigger IRQs in the low range used by 8250 UARTs. A pure asm call of "int $4" will result in a
* crash (yes, we did try first ;)). So instead of hacking around the kernel we simply used the 8250 public interface to
* trigger interrupt routines and implemented a small IRQ handling subsystem on our own.
* @param data
* @return
*/
static int virq_thread(void *data)
{
allow_signal(SIGKILL);
int out = 0;
struct serial8250_16550A_vdev *vdev = data;
uart_prdbg("%s started for ttyS%d pid=%d", __FUNCTION__, vdev->line, current->pid);
while(likely(!kthread_should_stop())) {
wait_event_interruptible(*vdev->virq_queue, !(vdev->iir & UART_IIR_NO_INT) || unlikely(kthread_should_stop()));
if (unlikely(signal_pending(current))) {
uart_prdbg("%s started for ttyS%d pid=%d received signal", __FUNCTION__, vdev->line, current->pid);
out = -EPIPE;
break;
}
if (unlikely(kthread_should_stop()))
break;
if (unlikely(!vdev->up)) {
pr_loc_bug("Cannot call serial8250 interrupt handler - port not captured (yet?)");
continue;
}
uart_prdbg("Calling serial8250 interrupt handler");
serial8250_handle_irq(vdev->up, vdev->iir);
}
uart_prdbg("%s stopped for ttyS%d pid=%d exit=%d", __FUNCTION__, vdev->line, current->pid, out);
//that can lead to a small memory leak for virq_queue if thread is killed outisde disable_interrupts() but this
// shouldn't normally happen unless something goes horribly wrong
vdev->virq_thread = NULL;
return out;
}
int vuart_enable_interrupts(struct serial8250_16550A_vdev *vdev)
{
int out;
pr_loc_dbg("Enabling vIRQ for ttyS%d", vdev->line);
lock_vuart(vdev);
if (unlikely(!vdev->initialized)) {
pr_loc_bug("ttyS%d is not initialized as vUART", vdev->line);
out = -ENODEV;
goto error_unlock_free;
}
if (unlikely(vuart_virq_active(vdev))) {
pr_loc_bug("Interrupts are already enabled & scheduled for ttyS%d", vdev->line);
out = -EBUSY;
goto error_unlock_free;
}
if (!(vdev->virq_queue = kmalloc(sizeof(wait_queue_head_t), GFP_KERNEL)) ||
!(vdev->virq_thread = kmalloc(sizeof(struct task_struct), GFP_KERNEL))) {
out = -ENOMEM;
pr_loc_crt("kernel memory alloc failure - tried to reserve memory for vIRQ structures");
goto error_unlock_free;
}
init_waitqueue_head(vdev->virq_queue);
unlock_vuart(vdev); //we can safely unlock after reserving memory but before starting thread (so we're not atomic)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-extra-args"
//VUART_THREAD_FMT can resolve to anonymized version without line or even IRQ#
vdev->virq_thread = kthread_run(virq_thread, vdev, VUART_THREAD_FMT, vdev->irq, vdev->line);
#pragma GCC diagnostic pop
if (IS_ERR(vdev->virq_thread)) {
out = PTR_ERR(vdev->virq_thread);
pr_loc_bug("Failed to start vIRQ thread");
goto error_free;
}
pr_loc_dbg("vIRQ fully enabled for for ttyS%d", vdev->line);
return 0;
error_unlock_free:
unlock_vuart(vdev);
error_free:
if (vdev->virq_queue) {
kfree(vdev->virq_queue);
vdev->virq_queue = NULL;
}
if (vdev->virq_thread) {
kfree(vdev->virq_thread);
vdev->virq_thread = NULL;
}
return out;
}
int vuart_disable_interrupts(struct serial8250_16550A_vdev *vdev)
{
int out;
pr_loc_dbg("Disabling vIRQ for ttyS%d", vdev->line);
lock_vuart(vdev);
if (unlikely(!vdev->initialized)) {
pr_loc_bug("ttyS%d is not initialized as vUART", vdev->line);
out = -ENODEV;
goto out_unlock;
}
if (unlikely(!vuart_virq_active(vdev))) {
pr_loc_bug("Interrupts are not enabled/scheduled for ttyS%d", vdev->line);
out = -EBUSY;
goto out_unlock;
}
out = kthread_stop(vdev->virq_thread);
if (out < 0) {
pr_loc_bug("Failed to stop vIRQ thread");
goto out_unlock;
}
kfree(vdev->virq_thread);
vdev->virq_thread = NULL;
pr_loc_dbg("vIRQ disabled for ttyS%d", vdev->line);
out_unlock:
unlock_vuart(vdev);
return 0;
}
#endif

View File

@ -0,0 +1,20 @@
#ifndef REDPILL_VUART_VIRTUAL_IRQ_H
#define REDPILL_VUART_VIRTUAL_IRQ_H
#ifdef VUART_USE_TIMER_FALLBACK
#define vuart_virq_supported() 0
#define vuart_virq_wake_up(dummy) //noop
#define vuart_enable_interrupts(dummy) (0)
#define vuart_disable_interrupts(dummy) (0)
#else //VUART_USE_TIMER_FALLBACK
#include "vuart_internal.h"
#define vuart_virq_supported() 1
#define vuart_virq_active(vdev) (!!(vdev)->virq_thread)
#define vuart_virq_wake_up(vdev) if (vuart_virq_active(vdev)) { wake_up_interruptible(vdev->virq_queue); }
int vuart_enable_interrupts(struct serial8250_16550A_vdev *vdev);
int vuart_disable_interrupts(struct serial8250_16550A_vdev *vdev);
#endif //VUART_USE_TIMER_FALLBACK
#endif //REDPILL_VUART_VIRTUAL_IRQ_H

575
internal/virtual_pci.c Executable file
View File

@ -0,0 +1,575 @@
/*
* This file is a SIMPLE (yes, this IS simple) software emulation layer for PCI devices.
*
* Before you even start reading it you need to get familiar with references listed below. As the kernel people put it
* mildly "The world of PCI is vast and full of (mostly unpleasant) surprises.". This module tries to abstract hardware
* space emulation into highest level API possible.
*
*
* QUICK INTRODUCTION
* ------------------
* To use it you need to supply a descriptor (e.g. struct pci_dev_descriptor) and give it domain-unique combination of
* {bus#, device#, function#}. The domain for most (all?) physical devices is usually 0x0000. This module uses 0x0001 to
* avoid conflicts.
* Fast PCI facts (read this to add devices):
* - every device is in the system has a location of BDF (256 buses max, 32 devices/bus, 8 functions/device = 65536)
* - every device MUST contain a function 0 (and may contain 1-7)
* - function is kind-of a subdevice (e.g. a quad-port network card will usually have functions 0-3)
* - you should (but you don't HAVE to) set "master bus" (.command |= PCI_COMMAND_MASTER) for every function 0 device
* instance
* - every device MUST have a valid VID/DEV. None of the fields can be 0x0000 or 0xFFFF (they have special meanings)
* - this module does NOT have any support for capabilities (CAPs) as they're variable length and we don't want to
* force every device struct to take 256K of memory (todo if needed?)
* - there are three types of headers: PCI device, PCI-PCI bridge, PCI-CardBus bridge. Only the first one was tested.
* The second one allows for more levels of the tree and should work if configured properly (see struct
* pci_pci_bridge_descriptor) but it wasn't needed yet. The third one is practically a bitrot now.
* - EVERYTHING IN PCI IS LITTLE ENDIAN no matter what your CPU says. Triple check if you're setting values correctly.
* Then you realize you set them incorrectly.
* - "devfn" in Linux terminology does NOT mean "device function" but rather "device# and function#". It is described
* in drivers/pci/search.c as "encodes number of PCI slot in which the desired PCI device resides and the logical
* device number within that slot in case of multi-function devices".
* You can use macros PCI_SLOT() and PCI_FUNC() to get dev# and fn# from that field.
* - Linux provides class & subclass constants (PCI_CLASS_* in include/linux/pci_ids.h). However they're defined as
* either:
* - 8 bit class
* - e.g. PCI_BASE_CLASS_SERIAL [0x0c]
* - can be put into pci_dev_descriptor.class directly
* - 16 bit class+subclass
* - e.g. PCI_CLASS_SERIAL_USB [0x0c03]
* - use U16_CLASS_TO_U8_CLASS(PCI_CLASS_SERIAL_USB) for pci_dev_descriptor.class [0x0c]
* - use U16_CLASS_TO_U8_SUBCLASS(PCI_CLASS_SERIAL_USB) for pci_dev_descriptor.subclass [0x03]
* - 24 bit (sic!) class+subclass+prog_if
* - e.g. PCI_CLASS_SERIAL_USB_EHCI (0x0c0320)
* - use U24_CLASS_TO_U8_CLASS(PCI_CLASS_SERIAL_USB) for pci_dev_descriptor.class [0x0c]
* - use U24_CLASS_TO_U8_SUBCLASS(PCI_CLASS_SERIAL_USB) for pci_dev_descriptor.subclass [0x03]
* - use U24_CLASS_TO_U8_PROGIF(PCI_CLASS_SERIAL_USB) for pci_dev_descriptor.prog_if [0x03]
* - "pci_dev_conf_default_normal_dev" provides a sane-default device where you need to only set: vid, dev, class,
* and subclass.
*
*
* DEBUGGING DEVICES
* -----------------
* To see the tree you can use "lspci -tvnn". Here's a quick cheat-sheet from the output format:
* 0001:0a:00.0 Class 0000: Device 1b4b:9235 (rev ff)
* ^ ^ ^ ^ ^ ^ ^ ^
* | | | | | | | |_______ pci_dev_descriptor.class_revision (lower 24 bits)
* | | | | | | |_________________ pci_dev_descriptor.dev (device ID)
* | | | | | |______________________ pci_dev_descriptor.vid (vendor ID)
* | | | | |___________________________________ pci_dev_descriptor.class_revision (higher 24 bits)
* | | | |___________________________________________ PCI device function
* | | |______________________________________________ device num on the bus
* | |_________________________________________________ PCI bus no
* |_____________________________________________________ PCIBUS_VIRTUAL_DOMAIN
*
*
* To debug the Linux PCI subsytem side of things these will be useful:
* echo 'file probe.c +p' > /sys/kernel/debug/dynamic_debug/control
* echo 'file search.c +p' > /sys/kernel/debug/dynamic_debug/control
* echo 'file delete.c +p' > /sys/kernel/debug/dynamic_debug/control
*
*
* INTERNAL STRUCTURE
* ------------------
* The module emulates PCI on the lowest possible level - it literally fakes the otherwise-physical memory of
* configuration registries.
*
* The two header types are memory-mapped as follows: (PCI-CardBus isn't shown as nobody uses that)
* HEADER TYPE 0x00 (Normal Device) HEADER TYPE 0x01 (PCI-PCI Bridge)
* 31 16 15 0 hh 31 16 15 0 hh
*
* Device ID Vendor ID 00 Device ID Vendor ID 00
*
* Status Command 04 Status Command 04
*
* Class Subclass ProgIF Rev. ID 08 Class Subclass ProgIF Rev. ID 08
*
* BIST HeaderT Lat.Tmr. Cache LS 0c BIST HeaderT Lat.Tmr. Cache LS 0c
*
* BAR0 10 BAR0 10
*
* BAR1 14 BAR1 14
*
* BAR2 18 SecLatT SubordB# SecBus# PriBus# 18
*
* BAR3 1c Secondary Status I/O Limit I/O Base 1c
*
* BAR4 20 Memory limit Memory base 20
*
* BAR5 24 Prefetch. Mem. L. Prefetch. Mem. B. 24
*
* Cardbus CIS ptr 28 Prefetchable Base Upper 32 bit 28
*
* Subsys ID Subsys VID 2c Prefetchable Limit Upper 32 bit 2c
*
* Exp. ROM Base Addr. 30 I/O Lim. Up. 16b I/O Base Up. 16b 30
*
* *RSV* Cap. ptr 34 *RSV* Cap. ptr 34
*
* *RSV* 38 Exp. ROM Base Addr. 38
*
* Max Lat. Min Gnt. Int. pin Int. lin. 3c Bridge Control Int. pin Int. lin. 3c
*
* Optional Dev.-Dep. Config (192 bytes) 40-100 Optional Dev.-Dep. Config (192 bytes) 40-100
*
*
*
* LINUX PCI SUBSYSTEM SCANNING ROUTINE
* ------------------------------------
* The kernel has a surprisingly readable code for the PCI scanning. We recommend starting from drivers/pci/probe.c and
* "struct pci_bus *pci_scan_bus()" function.
* In a big simplification it goes something like this:
* probe.c
* pci_scan_bus()
* => pci_scan_child_bus
* => loop pci_scan_slot(bus, devfn) with devfn=<0,0x100> every 8 bytes
* => pci_scan_single_device
* => pci_get_slot to check if device already exists
* => pci_scan_device to probe the device
* => pci_bus_read_dev_vendor_id
* => .... [and others]
* => pci_device_add if device probe succeeded
*
* THE ACPI SAGA
* -------------
* If you were thinking PCI is hard you haven't heard about ACPI. Kernels starting from v3.13 require ACPI companion
* for PCI devices when the system was configured to run on an ACPI-complain x86 platform. This isn't an unusual
* assumption. Before v3.13 the struct x86_sysdata contained a simple ACPI handle, which could be NULL. Now it should
* contain a structure. However it still PROBABLY can be NULL.
* See https://github.com/torvalds/linux/commit/7b1998116bbb2f3e5dd6cb9a8ee6db479b0b50a9 for details of that change.
*
* When the structure (=ACPI data) is NULL the error "ACPI: \: failed to evaluate _DSM (0x1001)" will be logged upon
* scanning. However it seems to be harmless. There are two ways to get rid of this error: 1) Implement a proper ACPI
* _DSM [no, just NO], or 2) user override_symbol() for acpi_evaluate_dsm() with a function doing the following (for the
* time of scanning ONLY):
* union acpi_object *obj = kmalloc(sizeof(union acpi_object), GFP_KERNEL);
* obj->type = ACPI_TYPE_INTEGER;
* obj->integer.value = 1;
* return obj;
*
* x86 BUS SCANNING BUG (>=v4.1)
* -----------------------------
* Since v4.1 adding a new bus under a different domain will cause devices on the bus to not be fully populated. See the
* comment in "vpci_add_single_device()" here for details & a simple fix.
*
* KNOWN BUGS
* ----------
* Under Linux v3.10 once bus is added it cannot be fully removed (or we didn't find the correct way). When you do the
* initial add and scan everything works correctly. You can later even remove that bus BUT the kernel leaves some sysfs
* stuff behind in /sys/devices (while /sys/bus/pci/devices are cleaned up). This means that if you try to re-register
* the same bus it explode with sysfs duplication errors.
* As of now we have no idea how to go around that.
*
*
* References:
* - https://stackoverflow.com/a/31465293 (how PCI subsystem works)
* - https://docs.oracle.com/cd/E19120-01/open.solaris/819-3196/hwovr-25/index.html (PCI working theory)
* - https://elixir.bootlin.com/linux/v3.10.108/source/include/uapi/linux/pci_regs.h (Linux PCI registers)
* - https://elixir.bootlin.com/linux/v3.10.108/source/drivers/pci/probe.c (PCI scanning code; very readable)
* - https://blog.csdn.net/moon146/article/details/18988849 (scanning process)
* - https://wiki.osdev.org/PCI (details regarding flags & commands)
*/
#include "virtual_pci.h"
#include "../common.h"
#include "../config/vpci_types.h" //MAX_VPCI_BUSES
#include <linux/pci.h>
#include <linux/pci_regs.h> //PCI device header constants
#include <linux/pci_ids.h> //Constants for vendors, classes, and other
#include <linux/list.h> //list_for_each
#include <linux/device.h> //device_del
#define PCIBUS_VIRTUAL_DOMAIN 0x0001 //normal PC buses are (always?) on domain 0, this is just a next one
#define PCI_DEVICE_NOT_FOUND_VID_DID 0xFFFFFFFF //A special case to detect non-existing devices (per PCI spec)
/* As per PCI spec
* If a single function device is detected (i.e., bit 7 in the Header
* Type register of function 0 is 0), no more functions for that
* Device Number will be checked. If a multi-function device is
* detected (i.e., bit 7 in the Header Type register of function 0
* is 1), then all remaining Function Numbers will be checked.
* This helper converts single-function header type to multifunction header type
*/
#define PCI_HEADER_TO_MULTI(x) ((1 << 7) | (x))
#define IS_PCI_HEADER_MULTI(x) (!!((x) & 0x80))
//Model of a default config for a device
const struct pci_dev_descriptor pci_dev_conf_default_normal_dev = {
.vid = 0xDEAD, //set me!
.dev = 0xBEEF, //set me!
.command = 0x0000,
.status = 0x0000,
.rev_id = PCI_DSC_REV_NONE,
.prog_if = PCI_DSC_PROGIF_NONE,
.subclass = U16_CLASS_TO_U8_CLASS(PCI_CLASS_NOT_DEFINED), //set me!
.class = U16_CLASS_TO_U8_CLASS(PCI_CLASS_NOT_DEFINED), //set me!
.cache_line_size = 0x00,
.latency_timer = 0x00,
.header_type = PCI_HEADER_TYPE_NORMAL,
.bist = PCI_DSC_BIST_NONE, //Built-In Self Test
.bar0 = PCI_DSC_NULL_BAR,
.bar1 = PCI_DSC_NULL_BAR,
.bar2 = PCI_DSC_NULL_BAR,
.bar3 = PCI_DSC_NULL_BAR,
.bar4 = PCI_DSC_NULL_BAR,
.bar5 = PCI_DSC_NULL_BAR,
.cardbus_cis = 0x00000000,
.subsys_vid = 0x0000, //you probably want to set this
.subsys_id = 0x0000, //you probably want to set this
.exp_rom_base_addr = 0x00000000,
.cap_ptr = PCI_DSC_NULL_CAP,
.reserved_34_8_15 = PCI_DSC_RSV8,
.reserved_34_16_31 = PCI_DSC_RSV16,
.reserved_38h = 0x00000000,
.interrupt_line = PCI_DSC_NO_INT_LINE,
.interrupt_pin = PCI_DSC_NO_INT_PIN,
.min_gnt = PCI_DSC_ZERO_BURST,
.max_lat = PCI_DSC_INF_LATENCY,
};
struct virtual_device {
unsigned char *bus_no; //same as bus->number, used when bus is not initialized yet (e.g. during scanning)
unsigned char dev_no;
unsigned char fn_no;
struct pci_bus* bus;
void *descriptor;
};
static unsigned int free_bus_idx = 0; //Used to find next free bus and for indexing other arrays
static struct pci_bus *buses[MAX_VPCI_BUSES] = { NULL }; //All virtual buses
static unsigned int free_dev_idx = 0; //Used to find next free bus and for indexing other arrays
static struct virtual_device *devices[MAX_VPCI_DEVS] = { NULL }; //All virtual devices
//Macros to easily iterate over lists above
#define for_each_bus_idx() for (int i = 0, last_bus_idx = free_bus_idx-1; i <= last_bus_idx; i++)
#define for_each_dev_idx() for (int i = 0, last_dev_idx = free_dev_idx-1; i <= last_dev_idx; i++)
/**
* Prints pci_dev_descriptor or pci_pci_bridge_descriptor
*/
void print_pci_descriptor(void *test_dev)
{
pr_loc_dbg("Printing PCI descriptor @ %p", test_dev);
pr_loc_dbg_raw("\n31***********0***ADDR*******************\n");
u8 *ptr = (u8 *)test_dev;
DBG_ALLOW_UNUSED(*ptr);
for (int row = 3; row < 64; row += 4) {
for (int byte = 0; byte > -4; byte--) {
pr_loc_dbg_raw("%02x ", *(ptr + row + byte));
if (byte == -1) pr_loc_dbg_raw(" ");
}
pr_loc_dbg_raw(" | 0x%02X\n", row - 3);
}
//The following format will be useful when/if CAPs are implemented
// printk("\n--------------Device Private--------------\n");
// printk("00000000 00000000 00000000 00000000 | xxx\n");
// printk("******************************************\n");
}
/**
* @param bus The bus (may be under first scan so only its number may be present in virtual_device)
* @param devfn Device AND its function; it's a 0-256 number allowing for 32 devices with 8 functions each
* @param where Offset in the device structure to read
* @param size How many BYTES (not bits) to read
* @param val Pointer to save read bytes
* @return PCIBIOS_*
*/
static int pci_read_cfg(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *val)
{
//devfn is a combination of device number on bus and function number (Bus/Device/Function addressing)
//Each device which exists MUST implement function 0. So every 8th value of devfn we have a new device.
unsigned char vdev_no = PCI_SLOT(devfn);
unsigned char vdev_fn = PCI_FUNC(devfn);
void *pci_descriptor = NULL;
//Very noisy!
//pr_loc_dbg("Read SYN wh=0x%d sz=%d B / %d for vDEV @ bus=%02x dev=%02x fn=%02x", where, size, size * 8,
// bus->number, vdev_no, vdev_fn);
for_each_dev_idx() {
//Very noisy!
//pr_loc_dbg("Checking vDEV @ bus=%02x dev=%02x fn=%02x", *devices[i]->bus_no, devices[i]->dev_no,
// devices[i]->fn_no);
//We cannot use devices[i]->bus->number during scan as the bus may just being created and no ->bus is available
if(*devices[i]->bus_no == bus->number && devices[i]->dev_no == vdev_no && devices[i]->fn_no == vdev_fn) {
//Very noisy!
//pr_loc_dbg("Found matching vDEV @ bus=%02x dev=%02x fn=%02x => vidx=%d mf=%d", bus->number, vdev_no,
// vdev_fn, i,
// IS_PCI_HEADER_MULTI(((struct pci_dev_descriptor *) devices[i]->descriptor)->header_type) ? 1:0);
pci_descriptor = devices[i]->descriptor;
break;
}
};
if (!pci_descriptor) { //This is not a hack - this is per PCI spec to return special "not found pid/vid"
if (where == PCI_VENDOR_ID || where == PCI_DEVICE_ID)
*val = PCI_DEVICE_NOT_FOUND_VID_DID;
//Very noisy!
//pr_loc_dbg("Read NAK wh=0x%d sz=%d B / %d for vDEV @ bus=%02x dev=%02x fn=%02x", where, size, size * 8, bus->number,
// vdev_no, vdev_fn);
return PCIBIOS_DEVICE_NOT_FOUND;
}
//Very noisy!
//pr_loc_dbg("Read ACK wh=0x%d sz=%d B / %d for vDEV @ bus=%02x dev=%02x fn=%02x", where, size, size * 8, bus->number,
// vdev_no, vdev_fn);
memcpy(val, (u8 *)pci_descriptor + where, size);
return PCIBIOS_SUCCESSFUL;
}
static int pci_write_cfg(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 val)
{
return PCIBIOS_SET_FAILED;
}
//Definition of callbacks the PCI subsystem uses to query the root bus
static struct pci_ops pci_shim_ops = {
.read = pci_read_cfg,
.write = pci_write_cfg
};
//x86-specific sysdata which is expected to be present while running on x86 (if it's not you will get a KP)
static struct pci_sysdata x86_sysdata = {
.domain = PCIBUS_VIRTUAL_DOMAIN,
#ifdef CONFIG_ACPI
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,13,0)
.companion = NULL, //See https://github.com/torvalds/linux/commit/7b1998116bbb2f3e5dd6cb9a8ee6db479b0b50a9
#else
.acpi = NULL,
#endif //LINUX_VERSION_CODE
#endif //CONFIG_ACPI
.iommu = NULL
};
//_NO => number according to the PCI spec
//_IDX => index in arrays (internal to this emulation layer only)
#define BUS_NO_VALID(x) ((x) >= 0 && (x) <= 0xFF) //Check if a given bus# is valid according to the PCI spec
#define DEV_NO_VALID(x) ((x) >= 0 && (x) <= 32) //Check if a given dev# is valid according to the PCI spec
#define FN_NO_VALID(x) ((x) >= 0 && (x) <= 7) //Check if a given function# is valid according to the PCI spec
#define VBUS_IDX_VALID(x) ((x) >= 0 && (x) < MAX_VPCI_BUSES-1) //Check if virtual bus INDEX is valid for this emulator
#define VBUS_IDX_USED(x) ((x) >= 0 && (x) < free_bus_idx) //Check if a given bus index is used now in the emulator
#define VDEV_IDX_VALID(x) ((x) >= 0 && (x) < MAX_VPCI_DEVS-1) //Check if virtual device INDEX is valid for this emulator
#define VDEV_IDX_USED(x) ((x) >= 0 && (x) < free_dev_idx) //Check if a given bus index is used now in the emulator
static inline int validate_bdf(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no)
{
if (unlikely(!BUS_NO_VALID(bus_no))) {
pr_loc_err("%02x is not a valid PCI bus number", bus_no);
return -EINVAL;
}
if (unlikely(!DEV_NO_VALID(dev_no))) {
pr_loc_err("%02x is not a valid PCI device number", dev_no);
return -EINVAL;
}
if (unlikely(!FN_NO_VALID(fn_no))) {
pr_loc_err("%02x is not a valid PCI device function number", fn_no);
return -EINVAL;
}
//if the free device index is not valid it means we're out of free IDs for devices
if (unlikely(!VDEV_IDX_VALID(free_dev_idx))) {
pr_loc_bug("No more device indexes are available (max devs: %d)", MAX_VPCI_DEVS);
return -ENOMEM;
}
//If the device has the same B/D/F address it is a duplicate
for_each_dev_idx() {
if (
likely(*devices[i]->bus_no == bus_no) &&
unlikely(devices[i]->dev_no == dev_no && devices[i]->fn_no == fn_no)
) {
pr_loc_err("Device bus=%02x dev=%02x fn=%02x already exists in vidx=%d", bus_no, dev_no, fn_no, i);
return -EEXIST;
}
};
return 0;
}
static inline struct pci_bus *get_vbus_by_number(unsigned char bus_no)
{
for_each_bus_idx() { //Determine whether we need to rescan existing bus after adding a device OR scan a new root bus
if (buses[i]->number == bus_no) {
pr_loc_dbg("Found existing bus_no=%d @ bidx=%d", bus_no, i);
return buses[i];
break;
}
};
return NULL;
}
const __must_check struct virtual_device *
vpci_add_device(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, void *descriptor)
{
pr_loc_dbg("Attempting to add vPCI device [printed below] @ bus=%02x dev=%02x fn=%02x", bus_no, dev_no, fn_no);
print_pci_descriptor(descriptor);
int error = validate_bdf(bus_no, dev_no, fn_no);
if (error != 0)
return ERR_PTR(error);
struct pci_bus *bus = get_vbus_by_number(bus_no);
//At this point we know the device can be added either to a new or existing bus so we have to populate their struct
struct virtual_device *device;
kmalloc_or_exit_ptr(device, sizeof(struct virtual_device));
device->dev_no = dev_no;
device->fn_no = fn_no;
device->descriptor = descriptor;
if (bus) { //We have an existing bus to use
device->bus_no = &bus->number;
devices[free_dev_idx++] = device;
//We cannot use "pci_scan_single_device" here in case there are mf devices
pci_rescan_bus(bus); //this cannot fail - it simply return max device num
pr_loc_err("Added device with existing bus @ bus=%02x dev=%02x fn=%02x", *device->bus_no, device->dev_no,
device->fn_no);
return device;
}
//No existing bus - check if we can add a new one
//if the free bus index is not valid it means we're out of free IDs for buses
if (unlikely(!VBUS_IDX_VALID(free_bus_idx))) {
pr_loc_bug("No more bus indexes are available (max buses: %d)", MAX_VPCI_BUSES);
return ERR_PTR(-ENOMEM);
}
//Since we don't have a bus so we need to add the device with a mock dev_no and trigger scanning (which actually
// creates the bus). While it sounds counter-intuitive it is how the PCI subsystem works.
unsigned char tmp_bus_no = bus_no; //It will be valid for the time of initial scan
device->bus_no = &tmp_bus_no;
devices[free_dev_idx++] = device;
bus = pci_scan_bus(*device->bus_no, &pci_shim_ops, &x86_sysdata);
if (!bus) {
pr_loc_err("pci_scan_bus failed - cannot add new bus");
devices[free_dev_idx--] = NULL; //Reverse adding & ensure idx is still free
kfree(device); //Free memory for the device itself
return ERR_PTR(-EIO);
}
device->bus_no = &bus->number; //Replace temp bus number pointer with the actual bus struct pointer
device->bus = bus;
buses[free_bus_idx++] = bus;
/*
* There was a commit in v4.1 which made "subtle" change aimed to "cleanup control flow" by moving
* pci_bus_add_devices(bus) from drivers/pci/probe.c:pci_scan_bus() to a higher order
* arch/x86/pci/common.c:pcibios_scan_root().
* However this means that adding a bus with a domain different than 0 as used on x86 with BIOS/ACPI causes some
* resources to not be created (e.g. /sys/bus/pci/devices/..../config) which in turn breaks a ton of tools (lspci
* included). This is because pci_bus_add_devices() calls pci_create_sysfs_dev_files().
* It's important to mention that this is broken only for new buses - pci_rescan_bus() calls pci_bus_add_devices().
*
* Don't even fucking ask how long we looked for that...
*
* See https://github.com/torvalds/linux/commit/8e795840e4d89df3d594e736989212ee8a4a1fca#
*/
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,1,0)
pr_loc_dbg("Linux >=v4.1 quirk: calling pci_bus_add_devices(bus) manually");
pci_bus_add_devices(bus);
#endif
pr_loc_inf("Added device with new bus @ bus=%02x dev=%02x fn=%02x", *device->bus_no, device->dev_no, device->fn_no);
return device;
}
const struct virtual_device *
vpci_add_single_device(unsigned char bus_no, unsigned char dev_no, struct pci_dev_descriptor *descriptor)
{
if (unlikely(IS_PCI_HEADER_MULTI(descriptor->header_type))) {
pr_loc_bug("Attempted to use %s() to add multifunction device."
"Did you mean to use vpci_add_multifunction_device()?", __FUNCTION__);
return ERR_PTR(-EINVAL);
}
return vpci_add_device(bus_no, dev_no, 0x00, descriptor);
}
const struct virtual_device *
vpci_add_multifunction_device(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no,
struct pci_dev_descriptor *descriptor)
{
descriptor->header_type = PCI_HEADER_TO_MULTI(descriptor->header_type);
return vpci_add_device(bus_no, dev_no, fn_no, descriptor);
}
const struct virtual_device *
vpci_add_single_bridge(unsigned char bus_no, unsigned char dev_no, struct pci_pci_bridge_descriptor *descriptor)
{
if (unlikely(IS_PCI_HEADER_MULTI(descriptor->header_type))) {
pr_loc_bug("Attempted to use %s() to add multifunction device."
"Did you mean to use vpci_add_multifunction_device()?", __FUNCTION__);
return ERR_PTR(-EINVAL);
}
return vpci_add_device(bus_no, dev_no, 0x00, descriptor);
}
const struct virtual_device *
vpci_add_multifunction_bridge(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no,
struct pci_pci_bridge_descriptor *descriptor)
{
descriptor->header_type = PCI_HEADER_TO_MULTI(descriptor->header_type);
return vpci_add_device(bus_no, dev_no, fn_no, descriptor);
}
int vpci_remove_all_devices_and_buses(void)
{
//The order here is crucial - kernel WILL NOT remove references to devices on bus removal (and cause a KP)
//Doing this in any other order will cause an instant KP when PCI subsys tries to access its structures (e.g. lspci)
//However, this is still leaving dangling things in /sys/devices which cannot be removed (kernel bug?)
struct pci_dev *pci_dev, *pci_dev_n;
for_each_bus_idx() {
list_for_each_entry_safe(pci_dev, pci_dev_n, &buses[i]->devices, bus_list) {
pr_loc_dbg("Detaching vDEV dev=%02x fn=%02x from bus=%02x [add=%d]", PCI_SLOT(pci_dev->devfn),
#if LINUX_VERSION_CODE <= KERNEL_VERSION(5,0,0)
PCI_FUNC(pci_dev->devfn), buses[i]->number, pci_dev->is_added);
#else
PCI_FUNC(pci_dev->devfn), buses[i]->number, 0); // Not found a replacement for pci_dev->is_added
#endif
pci_stop_and_remove_bus_device(pci_dev);
}
}
for_each_dev_idx() {
pr_loc_dbg("Removing PCI vDEV @ didx %d", i);
kfree(devices[i]);
devices[i] = NULL;
};
free_dev_idx = 0;
for_each_bus_idx() {
pr_loc_dbg("Removing child PCI vBUS @ bidx %d", i);
pci_rescan_bus(buses[i]);
pci_remove_bus(buses[i]);
buses[i] = NULL;
}
free_bus_idx = 0;
pr_loc_inf("All vPCI devices and buses removed");
return -EIO; //This is hardcoded to return an error as there's a known bug (see "KNOWN BUGS" in the file header)
}

202
internal/virtual_pci.h Executable file
View File

@ -0,0 +1,202 @@
#ifndef REDPILL_VIRTUAL_PCI_H
#define REDPILL_VIRTUAL_PCI_H
#include <linux/types.h>
/*
* The following macros are useful for converting PCI_CLASS_* constants to individual values in structs
* 31 .................. 0
* [class][sub][prog][rev] (each is 8 bit number, often times class-sub-prog is represented as on 24-bit int)
*
* For more information see header comment in the corresponding .c file.
*/
#define U24_CLASS_TO_U8_CLASS(x) (((x) >> 16) & 0xFF)
#define U24_CLASS_TO_U8_SUBCLASS(x) (((x) >> 8) & 0xFF)
#define U24_CLASS_TO_U8_PROGIF(x) ((x) & 0xFF)
#define U16_CLASS_TO_U8_CLASS(x) (((x) >> 8) & 0xFF)
#define U16_CLASS_TO_U8_SUBCLASS(x) ((x) & 0xFF)
//Some helpful constants on top of what's in linux/pci_ids.h & linux/pci_regs.h
#define PCI_DSC_NO_INT_LINE 0xFF
#define PCI_DSC_NO_INT_PIN 0x00
#define PCI_DSC_PROGIF_NONE 0x00
#define PCI_DSC_REV_NONE 0x00
#define PCI_DSC_NULL_BAR 0x00000000
#define PCI_DSC_NULL_CAP 0x00
#define PCI_DSC_RSV8 0x00
#define PCI_DSC_RSV16 0x0000
#define PCI_DSC_INF_LATENCY 0xFF //i.e. accepts any latency in access
#define PCI_DSC_ZERO_BURST 0xFF //i.e. doesn't need any length of burst
#define PCI_DSC_BIST_NONE 0x00
//See https://en.wikipedia.org/wiki/PCI_configuration_space#/media/File:Pci-config-space.svg
//This struct MUST be packed to allow for easy reading, see https://kernelnewbies.org/DataAlignment
struct pci_dev_descriptor {
u16 vid; //Vendor ID
u16 dev; //Device ID
u16 command; //see PCI_COMMAND_*. Simply do "dev.command |= PCI_COMMAND_xxx" to set a flag.
u16 status; //see PCI_STATUS_*. Simply do "dev.status |= PCI_STATUS_xxx" to set a flag.
u8 rev_id;
u8 prog_if; // ]
u8 subclass; // ]-> prof_if, subclass, and class are normally represented as 24-bit class code
u8 class; // ]
u8 cache_line_size;
u8 latency_timer;
u8 header_type; //see PCI_HEADER_TYPE_*
u8 bist; //see PCI_BIST_*
u32 bar0;
u32 bar1;
u32 bar2;
u32 bar3;
u32 bar4;
u32 bar5;
u32 cardbus_cis;
u16 subsys_vid;
u16 subsys_id;
u32 exp_rom_base_addr; //see PCI_ROM_* (esp PCI_ROM_ADDRESS_MASK)
u8 cap_ptr;
u8 reserved_34_8_15; //should be 0x00
u16 reserved_34_16_31; //should be 0x00
u32 reserved_38h;
u8 interrupt_line;
u8 interrupt_pin;
u8 min_gnt;
u8 max_lat;
} __packed;
extern const struct pci_dev_descriptor pci_dev_conf_default_normal_dev; //See details in the .c file
//Support for bridges wasn't tested
struct pci_pci_bridge_descriptor {
u16 vid; //Vendor ID
u16 dev; //Device ID
u16 command; //see PCI_COMMAND_*. Simply do "dev.command |= PCI_COMMAND_xxx" to set a flag.
u16 status; //see PCI_STATUS_*. Simply do "dev.status |= PCI_STATUS_xxx" to set a flag.
u8 rev_id;
u8 prog_if; // ]
u8 subclass; // ]-> prof_if, subclass, and class are normally represented as 24-bit class code
u8 class; // ]
u8 cache_line_size;
u8 latency_timer;
u8 header_type; //see PCI_HEADER_TYPE_*
u8 bist; //see PCI_BIST_*
u32 bar0;
u32 bar1;
u8 pri_bus_no;
u8 sec_bus_no;
u8 subord_bus_no;
u8 sec_lat_timer;
u8 io_base;
u8 io_limit;
u16 sec_status;
u16 mem_base;
u16 mem_limit;
u16 prefetch_mem_base;
u16 prefetch_mem_limit;
u32 prefetch_base_up32b;
u32 prefetch_limit_up32b;
u16 io_base_up16b;
u16 io_limit_up16b;
u8 cap_ptr;
u8 reserved_34_8_15; //should be 0x00
u16 reserved_34_16_31; //should be 0x00
u32 exp_rom_base_addr; //see PCI_ROM_* (esp PCI_ROM_ADDRESS_MASK)
u8 interrupt_line;
u8 interrupt_pin;
u16 bridge_ctrl;
} __packed;
//This is currently not implemented
struct pci_dev_capability {
u8 cap_id; //see PCI_CAP_ID_*, set to 0x00 to denote null-capability
u8 cap_next; //offset where next capability exists, set to 0x00 to denote null-capability
u8 cap_data[];
} __packed;
/**
* Adds a single new device (along with the bus if needed)
*
* If you don't want to create the descriptor from scratch you can use "const struct pci_dev_conf_default_normal_dev"
* while setting some missing params (see .c file header for details).
* Note: you CAN reuse the same descriptor under multiple BDFs (bus_no/dev_no/fn_no)
*
* @param bus_no (0x00 - 0xFF)
* @param dev_no (0x00 - 0x20)
* @param descriptor Pointer to pci_dev_descriptor or pci_pci_bridge_descriptor
* @return virtual_device ptr or error pointer (ERR_PTR(-E))
*/
const struct virtual_device *
vpci_add_single_device(unsigned char bus_no, unsigned char dev_no, struct pci_dev_descriptor *descriptor);
/**
* See vpci_add_single_device() for details
*/
const struct virtual_device *
vpci_add_single_bridge(unsigned char bus_no, unsigned char dev_no, struct pci_pci_bridge_descriptor *descriptor);
/*
* Adds a new multifunction device (along with the bus if needed)
*
* Warning about multifunctional devices
* - this function has a slight limitation due to how Linux scans devices. You HAVE TO add fn_no=0 entry as the LAST
* one when calling it multiple times. Kernel scans devices only once for changes and if it finds fn=0 and it's the
* only one (i.e. you added fn=0 first) adding more functions will not populate them (as kernel will never re-scan
* the device).
* - As per PCI spec Linux doesn't allow devices to have fn>0 if they don't have corresponding fn=0 entry
*
* @param bus_no (0x00 - 0xFF)
* @param dev_no (0x00 - 0x20)
* @param fn_no (0x00 - 0x07)
* @param descriptor Pointer to pci_dev_descriptor or pci_pci_bridge_descriptor
* @return virtual_device ptr or error pointer (ERR_PTR(-E))
*/
const struct virtual_device *
vpci_add_multifunction_device(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no,
struct pci_dev_descriptor *descriptor);
/**
* See vpci_add_multifunction_device() for details
*/
const struct virtual_device *
vpci_add_multifunction_bridge(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no,
struct pci_pci_bridge_descriptor *descriptor);
/**
* Removes all previously added devices and buses
*
* Known bug: while you can remove things and they will be gone from the system you CANNOT re-add the same under the
* same BFD coordinates. This will cause the kernel to complain about duplicated internal sysfs entries. It's most
* likely an old kernel bug (we tried everything... it doesn't work).
*
* @param bus_no
* @param dev_no
* @param fn_no
* @param descriptor
* @return
*/
int vpci_remove_all_devices_and_buses(void);
#endif //REDPILL_VIRTUAL_PCI_H

126
redpill_main.c Executable file
View File

@ -0,0 +1,126 @@
#include "internal/stealth.h"
#include "redpill_main.h"
#include "config/runtime_config.h"
#include "common.h" //commonly used headers in this module
#include "internal/intercept_execve.h" //Handling of execve() replacement
#include "internal/scsi/scsi_notifier.h" //the missing pub/sub handler for SCSI driver
#include "internal/ioscheduler_fixer.h" //reset_elevator() to correct elevator= boot cmdline
#include "config/cmdline_delegate.h" //Parsing of kernel cmdline
#include "shim/boot_device_shim.h" //Registering & deciding between boot device shims
#include "shim/bios_shim.h" //Shimming various mfgBIOS functions to make them happy
#include "shim/block_fw_update_shim.h" //Prevent firmware update from running
#include "shim/disable_exectutables.h" //Disable common problematic executables
#include "shim/pci_shim.h" //Handles PCI devices emulation
#include "shim/storage/smart_shim.h" //Handles emulation of SMART data for devices without it
#include "shim/storage/sata_port_shim.h" //Handles VirtIO & SAS storage devices/disks peculiarities
#include "shim/uart_fixer.h" //Various fixes for UART weirdness
#include "shim/pmu_shim.h" //Emulates the platform management unit
#include "internal/helper/symbol_helper.h" //kln_func
//Handle versioning stuff
#ifndef RP_VERSION_POSTFIX
#define RP_VERSION_POSTFIX "(NULL)"
#endif
#define RP_VERSION_MAJOR 0
#define RP_VERSION_MINOR 6
#define STRINGIFY(x) #x
#define VERSIONIFY(major,minor,postfix) "v" STRINGIFY(major) "." STRINGIFY(minor) "-" postfix
#define RP_VERSION_STR VERSIONIFY(RP_VERSION_MAJOR, RP_VERSION_MINOR, RP_VERSION_POSTFIX)
/**
* Force panic to land on a stack trace
*
* This ensures we always get this on the stack trace so that we know it was an intentional crash due to a detected
* error rather than an accidental bug.
*/
void noinline __noreturn rp_crash(void) {
//Deliberately not reveling any context in case we're running in stealth mode
//This message is a generic one from arch/x86/kernel/dumpstack.c
panic("Fatal exception");
}
static int __init init_(void)
{
int out = 0;
pr_loc_dbg("================================================================================================");
pr_loc_inf("RedPill %s loading...", RP_VERSION_STR);
if (
get_kln_p() < 0 //Find pointer of kallsyms_lookup_name function, This MUST be the first entry
|| (out = extract_config_from_cmdline(&current_config)) != 0 //This MUST be the second entry
|| (out = populate_runtime_config(&current_config)) != 0 //This MUST be third
|| (out = register_uart_fixer(current_config.hw_config)) != 0 //Fix consoles ASAP
|| (out = register_scsi_notifier()) != 0 //Load SCSI notifier so that boot shim (& others) can use it
|| (out = register_sata_port_shim()) //This should be bfr boot shim as it can fix some things need by boot
|| (out = register_boot_shim(&current_config.boot_media)) //Make sure we're quick with this one
|| (out = register_execve_interceptor()) != 0 //Register this reasonably high as other modules can use it blindly
|| (out = register_bios_shim(current_config.hw_config)) != 0
|| (out = register_disable_executables_shim()) != 0
|| (out = register_fw_update_shim()) != 0
#ifndef DBG_DISABLE_UNLOADABLE
|| (out = register_pci_shim(current_config.hw_config)) != 0 //it's a core hw but it's not checked early
#endif
|| (out = register_disk_smart_shim()) != 0 //provide fake SMART to userspace
|| (out = register_pmu_shim(current_config.hw_config)) != 0 //this is used as early as mfgBIOS loads (=late)
|| (out = initialize_stealth(&current_config)) != 0 //Should be after any shims to let shims have real stuff
|| (out = reset_elevator()) != 0 //Cosmetic, can be the last one
)
goto error_out;
pr_loc_inf("RedPill %s loaded successfully (stealth=%d)", RP_VERSION_STR, STEALTH_MODE);
return 0;
error_out:
pr_loc_crt("RedPill %s cannot be loaded, initializer error=%d", RP_VERSION_STR, out);
#ifdef KP_ON_LOAD_ERROR
rp_crash();
#else
return out;
#endif
}
module_init(init_);
#if STEALTH_MODE < STEALTH_MODE_FULL //module cannot be unloaded in full-stealth anyway
static void __exit cleanup_(void)
{
pr_loc_inf("RedPill %s unloading...", RP_VERSION_STR);
int (*cleanup_handlers[])(void ) = {
uninitialize_stealth,
unregister_pmu_shim,
unregister_disk_smart_shim,
#ifndef DBG_DISABLE_UNLOADABLE
unregister_pci_shim,
#endif
unregister_fw_update_shim,
unregister_disable_executables_shim,
unregister_bios_shim,
unregister_execve_interceptor,
unregister_boot_shim,
unregister_sata_port_shim,
unregister_scsi_notifier,
unregister_uart_fixer
};
int out;
for (int i = 0; i < ARRAY_SIZE(cleanup_handlers); i++) {
pr_loc_dbg("Calling cleanup handler %pF<%p>", cleanup_handlers[i], cleanup_handlers[i]);
out = cleanup_handlers[i]();
if (out != 0)
pr_loc_wrn("Cleanup handler %pF failed with code=%d", cleanup_handlers[i], out);
}
free_runtime_config(&current_config); //A special snowflake ;)
pr_loc_inf("RedPill %s is dead", RP_VERSION_STR);
pr_loc_dbg("================================================================================================");
}
module_exit(cleanup_);
MODULE_AUTHOR("TTG");
MODULE_VERSION(RP_VERSION_STR);
#endif
MODULE_LICENSE("GPL");

4
redpill_main.h Executable file
View File

@ -0,0 +1,4 @@
#ifndef REDPILLLKM_REDPILL_MAIN_H
#define REDPILLLKM_REDPILL_MAIN_H
#endif //REDPILLLKM_REDPILL_MAIN_H

117
shim/bios/bios_hwcap_shim.c Executable file
View File

@ -0,0 +1,117 @@
/**
* Overrides GetHwCapability to provide additional capabilities for older platforms (e.g. 3615xs)
*/
#include "bios_hwcap_shim.h"
#include "../../common.h"
#include "../shim_base.h"
#include "../../internal/override/override_symbol.h" //overriding GetHWCapability
#include "../../config/platform_types.h" //hw_config, platform_has_hwmon_*
#include <linux/synobios.h> //CAPABILITY_*, CAPABILITY
#define SHIM_NAME "mfgBIOS HW Capability"
static const struct hw_config *hw_config = NULL;
static override_symbol_inst *GetHwCapability_ovs = NULL;
static void dbg_compare_cap_value(SYNO_HW_CAPABILITY id, int computed_support)
{
#ifdef DBG_HWCAP
int org_fout = -1;
CAPABILITY org_cap = { '\0' };
org_cap.id = id;
int ovs_fout = call_overridden_symbol(org_fout, GetHwCapability_ovs, &org_cap);
pr_loc_dbg("comparing GetHwCapability(id=%d)->support => computed=%d vs. real=%d [org_fout=%d, ovs_fout=%d]", id,
computed_support, org_cap.support, org_fout, ovs_fout);
#endif
}
static int GetHwCapability_shim(CAPABILITY *cap)
{
if (unlikely(!cap)) {
pr_loc_err("Got NULL-ptr to %s", __FUNCTION__);
return -EINVAL;
}
switch (cap->id) {
case CAPABILITY_THERMAL:
cap->support = platform_has_hwmon_thermal(hw_config) ? 1 : 0;
dbg_compare_cap_value(cap->id, cap->support);
return 0;
case CAPABILITY_CPU_TEMP:
cap->support = hw_config->has_cpu_temp;
dbg_compare_cap_value(cap->id, cap->support);
return 0;
case CAPABILITY_FAN_RPM_RPT:
cap->support = platform_has_hwmon_fan_rpm(hw_config) ? 1 : 0;
dbg_compare_cap_value(cap->id, cap->support);
return 0;
case CAPABILITY_DISK_LED_CTRL:
case CAPABILITY_AUTO_POWERON:
case CAPABILITY_S_LED_BREATH:
case CAPABILITY_MICROP_PWM:
case CAPABILITY_CARDREADER:
case CAPABILITY_LCM: {
if (unlikely(!GetHwCapability_ovs)) {
pr_loc_bug("%s() was called with proxy need when no OVS was available", __FUNCTION__);
return -EIO;
}
int org_fout = -1;
int ovs_fout = call_overridden_symbol(org_fout, GetHwCapability_ovs, cap);
pr_loc_dbg("proxying GetHwCapability(id=%d)->support => real=%d [org_fout=%d, ovs_fout=%d]", cap->id,
cap->support, org_fout, ovs_fout);
return org_fout;
}
default:
pr_loc_err("unknown GetHwCapability(id=%d) => out=-EINVAL", cap->id);
return -EINVAL;
}
}
int register_bios_hwcap_shim(const struct hw_config *hw)
{
shim_reg_in();
if (unlikely(GetHwCapability_ovs))
shim_reg_already();
hw_config = hw;
override_symbol_or_exit_int(GetHwCapability_ovs, "GetHwCapability", GetHwCapability_shim);
shim_reg_ok();
return 0;
}
int unregister_bios_hwcap_shim(void)
{
shim_ureg_in();
if (unlikely(!GetHwCapability_ovs))
return 0; //this is deliberately a noop
int out = restore_symbol(GetHwCapability_ovs);
if (unlikely(out != 0)) {
pr_loc_err("Failed to restore GetHwCapability - error=%d", out);
return out;
}
GetHwCapability_ovs = NULL;
shim_ureg_ok();
return 0;
}
int reset_bios_hwcap_shim(void)
{
shim_reset_in();
put_overridden_symbol(GetHwCapability_ovs);
GetHwCapability_ovs = NULL;
shim_reset_ok();
return 0;
}

23
shim/bios/bios_hwcap_shim.h Executable file
View File

@ -0,0 +1,23 @@
#ifndef REDPILL_BIOS_HWCAP_SHIM_H
#define REDPILL_BIOS_HWCAP_SHIM_H
#include <linux/types.h> //bool
struct hw_config;
int register_bios_hwcap_shim(const struct hw_config *hw);
/**
* This function should be called when we're unloading cleanly (=mfgBIOS is alive, we're going away). If the bios went
* away on its own call reset_bios_hwcap_shim()
*/
int unregister_bios_hwcap_shim(void);
/**
* This function should be called when we're unloading because mfgBIOS went away. If the unload should be clean and
* restore all mfgBIOS elements to its original state (i.e. the mfgBIOS is still loaded and not currently unloading)
* call unregister_bios_hwcap_shim() instead.
*/
int reset_bios_hwcap_shim(void);
#endif //REDPILL_BIOS_HWCAP_SHIM_H

379
shim/bios/bios_hwmon_shim.c Executable file
View File

@ -0,0 +1,379 @@
/**
* Responds to all HWMON ("hardware monitor") calls coming to the mfgBIOS
*
* This submodule emulates both legitimate HWMON calls as well as "legacy" hardware monitoring calls get_fan_status()
*/
#include "bios_hwmon_shim.h"
#include "../shim_base.h" //shim_reg_in(), shim_reg_ok(), shim_reset_in(), shim_reset_ok()
#include "bios_shims_collection.h" //_shim_bios_module_entry()
#include "../../common.h"
#include "../../internal/helper/math_helper.h" //prandom_int_range_stable
#include "mfgbios_types.h" //HWMON_*
#include "../../config/platform_types.h" //HWMON_*_ID
#define SHIM_NAME "mfgBIOS HW Monitor"
#ifdef DBG_HWMON
#define hwmon_pr_loc_dbg(...) pr_loc_dbg(__VA_ARGS__)
#else
#define hwmon_pr_loc_dbg(...) //noop
#endif
/************************************* Standards for generating fake sensor readings **********************************/
//Standard deviations for ongoing sensor readings
#define FAN_SPEED_DEV 50 //Fan speed (RPM) deviation
#define VOLT_DEV 5 //Voltage (mV) deviation
#define TEMP_DEV 2 //Temperature (°C) deviation
#define FAKE_SURFACE_TEMP_MIN 25
#define FAKE_SURFACE_TEMP_MAX 35
#define FAKE_CPU_TEMP_MIN 55
#define FAKE_CPU_TEMP_MAX 65
#define FAKE_RPM_MIN 980
#define FAKE_RPM_MAX 1000
//These percentages are precalculated as we cannot use FPU [safely and easily] in kernel space
#define FAKE_V33_MIN 3135 // mV (-5% of 3.3V)
#define FAKE_V33_MAX 3465 // mV (+5% of 3.3V)
#define FAKE_V5_MIN 4750 // mV (-5% of 5.0V)
#define FAKE_V5_MAX 5250 // mV (+5% of 5.0V)
#define FAKE_V12_MIN 11400 // mV (-5% of 12.0V)
#define FAKE_V12_MAX 12600 // mV (+5% of 12.0V)
#define fake_volt_min(type) (hwmon_sys_vsens_type_base[(type)][0]) // mV
#define fake_volt_max(type) (hwmon_sys_vsens_type_base[(type)][1]) // mV
/************************************* Maps between hwmon sensor types & their names **********************************/
static const char *hwmon_sys_thermal_zone_id_map[] = {
[HWMON_SYS_TZONE_NULL_ID] = "",
[HWMON_SYS_TZONE_REMOTE1_ID] = HWMON_SYS_TZONE_REMOTE1_NAME,
[HWMON_SYS_TZONE_REMOTE2_ID] = HWMON_SYS_TZONE_REMOTE2_NAME,
[HWMON_SYS_TZONE_LOCAL_ID] = HWMON_SYS_TZONE_LOCAL_NAME,
[HWMON_SYS_TZONE_SYSTEM_ID] = HWMON_SYS_TZONE_SYSTEM_NAME,
[HWMON_SYS_TZONE_ADT1_LOC_ID] = HWMON_SYS_TZONE_ADT1_LOC_NAME,
[HWMON_SYS_TZONE_ADT2_LOC_ID] = HWMON_SYS_TZONE_ADT2_LOC_NAME,
};
static const char *hwmon_sys_vsens_id_map[] = {
[HWMON_SYS_VSENS_NULL_ID] = "",
[HWMON_SYS_VSENS_VCC_ID] = HWMON_SYS_VSENS_VCC_NAME,
[HWMON_SYS_VSENS_VPP_ID] = HWMON_SYS_VSENS_VPP_NAME,
[HWMON_SYS_VSENS_V33_ID] = HWMON_SYS_VSENS_V33_NAME,
[HWMON_SYS_VSENS_V5_ID] = HWMON_SYS_VSENS_V5_NAME,
[HWMON_SYS_VSENS_V12_ID] = HWMON_SYS_VSENS_V12_NAME,
[HWMON_SYS_VSENS_ADT1_V33_ID] = HWMON_SYS_VSENS_ADT1_V33_NAME,
[HWMON_SYS_VSENS_ADT2_V33_ID] = HWMON_SYS_VSENS_ADT2_V33_NAME,
};
static const int hwmon_sys_vsens_type_base[][2] = {
[HWMON_SYS_VSENS_NULL_ID] = {0, 0},
[HWMON_SYS_VSENS_VCC_ID] = { FAKE_V12_MIN, FAKE_V12_MAX }, //todo: this is probably per-model
[HWMON_SYS_VSENS_VPP_ID] = { 100, 500 }, //todo: if this is really peak-to-peak it should be small
[HWMON_SYS_VSENS_V33_ID] = { FAKE_V33_MIN, FAKE_V33_MAX },
[HWMON_SYS_VSENS_V5_ID] = { FAKE_V5_MIN, FAKE_V5_MAX },
[HWMON_SYS_VSENS_V12_ID] = { FAKE_V12_MIN, FAKE_V12_MAX },
[HWMON_SYS_VSENS_ADT1_V33_ID] = { FAKE_V33_MIN, FAKE_V33_MAX },
[HWMON_SYS_VSENS_ADT2_V33_ID] = { FAKE_V33_MIN, FAKE_V33_MAX },
};
static const char *hwmon_sys_fan_id_map[] = {
[HWMON_SYS_FAN_NULL_ID] = "",
[HWMON_SYS_FAN1_ID] = HWMON_SYS_FAN1_RPM,
[HWMON_SYS_FAN2_ID] = HWMON_SYS_FAN2_RPM,
[HWMON_SYS_FAN3_ID] = HWMON_SYS_FAN3_RPM,
[HWMON_SYS_FAN4_ID] = HWMON_SYS_FAN4_RPM,
};
static const char *hwmon_hdd_bp_id_map[] = {
[HWMON_SYS_HDD_BP_NULL_ID] = "",
[HWMON_SYS_HDD_BP_DETECT_ID] = HWMON_HDD_BP_DETECT,
[HWMON_SYS_HDD_BP_ENABLE_ID] = HWMON_HDD_BP_ENABLE,
};
//todo: it's defined as __used as we know the structure but don't implement it yet
static const __used char *hwmon_psu_id_map[] = {
[HWMON_PSU_NULL_ID] = "",
[HWMON_PSU_PWR_IN_ID] = HWMON_PSU_SENSOR_PIN,
[HWMON_PSU_PWR_OUT_ID] = HWMON_PSU_SENSOR_POUT,
#if RP_MODULE_TARGET_VER == 6
[HWMON_PSU_TEMP_ID] = HWMON_PSU_SENSOR_TEMP,
#elif RP_MODULE_TARGET_VER == 7
[HWMON_PSU_TEMP1_ID] = HWMON_PSU_SENSOR_TEMP1,
[HWMON_PSU_TEMP2_ID] = HWMON_PSU_SENSOR_TEMP2,
[HWMON_PSU_TEMP3_ID] = HWMON_PSU_SENSOR_TEMP3,
[HWMON_PSU_FAN_VOLT] = HWMON_PSU_SENSOR_FAN_VOLT,
#endif
[HWMON_PSU_FAN_RPM_ID] = HWMON_PSU_SENSOR_FAN,
[HWMON_PSU_STATUS_ID] = HWMON_PSU_SENSOR_STATUS,
};
//todo: it's defined as __used as we know the structure but don't implement it yet
static const __used char *hwmon_current_id_map[] = {
[HWMON_SYS_CURR_NULL_ID] = "",
[HWMON_SYS_CURR_ADC_ID] = HWMON_SYS_CURR_ADC_NAME,
};
/************************************************ Various small tools *************************************************/
static const struct hw_config_hwmon *hwmon_cfg = NULL;
#define guard_hwmon_cfg() \
if (unlikely(!hwmon_cfg)) { \
pr_loc_bug("Called %s without hwmon_cfg context being populated", __FUNCTION__); \
return -EIO; \
}
#define guarded_strscpy(dest, src, count) \
if (unlikely(strscpy(dest, src, count) == -E2BIG)) { \
pr_loc_err("Failed to copy %lu bytes string", count); \
return -EFAULT; \
}
/******************************************* mfgBIOS LKM replacement functions ****************************************/
/**
* Provides fan status
*
* Currently the fan is always assumed to be running
*/
static int bios_get_fan_state(int no, enum MfgCompatFanStatus *status)
{
hwmon_pr_loc_dbg("mfgBIOS: GET_FAN_STATE(%d) => MFGC_FAN_RUNNING", no);
*status = MFGC_FAN_RUNNING;
return 0;
}
static int cur_cpu_temp = 0;
/**
* Returns CPU temperature across all cores
*
* Currently it always returns a fake value. However, it should only do so if running under hypervisor. In bare-metal
* scenario we can simply proxy to syno_cpu_temperature() [or not override that part at all].
*/
static int bios_get_cpu_temp(SYNOCPUTEMP *temp)
{
int fake_temp = prandom_int_range_stable(&cur_cpu_temp, TEMP_DEV, FAKE_CPU_TEMP_MIN, FAKE_CPU_TEMP_MAX);
temp->cpu_num = MAX_CPU;
for(int i=0; i < MAX_CPU; ++i)
temp->cpu_temp[i] = fake_temp;
hwmon_pr_loc_dbg("mfgBIOS: GET_CPU_TEMP(surf=%d, cpuNum=%d) => %d°C", temp->blSurface, temp->cpu_num, fake_temp);
return 0;
}
static int *hwmon_thermals = NULL;
/**
* Returns various HWMON temperatures
*
* @param reading Pointer to save results
* @return 0 on success, -E on error
*/
static int bios_hwmon_get_thermal(SYNO_HWMON_SENSOR_TYPE *reading)
{
guard_hwmon_cfg();
if (unlikely(!hwmon_thermals))
kzalloc_or_exit_int(hwmon_thermals, sizeof(int) * HWMON_SYS_THERMAL_ZONE_IDS);
guarded_strscpy(reading->type_name, HWMON_SYS_THERMAL_NAME, sizeof(reading->type_name));
hwmon_pr_loc_dbg("mfgBIOS: => %s(type=%s)", __FUNCTION__, reading->type_name);
reading->sensor_num = 0;
for (int i = 0; i < HWMON_SYS_THERMAL_ZONE_IDS; i++) {
if (hwmon_cfg->sys_thermal[i] == HWMON_SYS_TZONE_NULL_ID)
break;
guarded_strscpy(reading->sensor[i].sensor_name, hwmon_sys_thermal_zone_id_map[hwmon_cfg->sys_thermal[i]],
sizeof(reading->sensor[i].sensor_name)); //Save the name of the sensor
hwmon_thermals[i] = prandom_int_range_stable(&hwmon_thermals[i], TEMP_DEV, FAKE_SURFACE_TEMP_MIN,
FAKE_SURFACE_TEMP_MAX);
snprintf(reading->sensor[i].value, sizeof(reading->sensor[i].value), "%d", hwmon_thermals[i]);
++reading->sensor_num;
hwmon_pr_loc_dbg("mfgBIOS: <= %s() %s->%d °C", __FUNCTION__,
hwmon_sys_thermal_zone_id_map[hwmon_cfg->sys_thermal[i]], hwmon_thermals[i]);
}
return 0;
}
static int *hwmon_voltages = NULL;
/**
* Returns various HWMON voltages
*
* @param reading Pointer to save results
* @return 0 on success, -E on error
*/
static int bios_hwmon_get_voltages(SYNO_HWMON_SENSOR_TYPE *reading)
{
guard_hwmon_cfg();
if (unlikely(!hwmon_voltages))
kzalloc_or_exit_int(hwmon_voltages, sizeof(int) * HWMON_SYS_VOLTAGE_SENSOR_IDS);
guarded_strscpy(reading->type_name, HWMON_SYS_VOLTAGE_NAME, sizeof(reading->type_name));
hwmon_pr_loc_dbg("mfgBIOS: => %s(type=%s)", __FUNCTION__, reading->type_name);
reading->sensor_num = 0;
for (int i = 0; i < HWMON_SYS_VOLTAGE_SENSOR_IDS; i++) {
if (hwmon_cfg->sys_voltage[i] == HWMON_SYS_VSENS_NULL_ID)
break;
guarded_strscpy(reading->sensor[i].sensor_name, hwmon_sys_vsens_id_map[hwmon_cfg->sys_voltage[i]],
sizeof(reading->sensor[i].sensor_name)); //Save the name of the sensor
hwmon_voltages[i] = prandom_int_range_stable(&hwmon_voltages[i], VOLT_DEV,
fake_volt_min(hwmon_cfg->sys_voltage[i]),
fake_volt_max(hwmon_cfg->sys_voltage[i]));
snprintf(reading->sensor[i].value, sizeof(reading->sensor[i].value), "%d", hwmon_voltages[i]);
++reading->sensor_num;
hwmon_pr_loc_dbg("mfgBIOS: <= %s() %s->%d mV", __FUNCTION__, hwmon_sys_vsens_id_map[hwmon_cfg->sys_voltage[i]],
hwmon_voltages[i]);
}
return 0;
}
static int *hwmon_fans_rpm = NULL;
/**
* Returns HWMON fan speeds
*
* @param reading Pointer to save results
* @return 0 on success, -E on error
*/
static int bios_hwmon_get_fans_rpm(SYNO_HWMON_SENSOR_TYPE *reading)
{
guard_hwmon_cfg();
if (unlikely(!hwmon_fans_rpm))
kzalloc_or_exit_int(hwmon_fans_rpm, sizeof(int) * HWMON_SYS_FAN_RPM_IDS);
guarded_strscpy(reading->type_name, HWMON_SYS_FAN_RPM_NAME, sizeof(reading->type_name));
hwmon_pr_loc_dbg("mfgBIOS: => %s(type=%s)", __FUNCTION__, reading->type_name);
reading->sensor_num = 0;
for (int i = 0; i < HWMON_SYS_FAN_RPM_IDS; i++) {
if (hwmon_cfg->sys_fan_speed_rpm[i] == HWMON_SYS_FAN_NULL_ID)
break;
// Save the name of the sensor
guarded_strscpy(reading->sensor[i].sensor_name,
hwmon_sys_fan_id_map[hwmon_cfg->sys_fan_speed_rpm[i]],
sizeof(reading->sensor[i].sensor_name));
hwmon_fans_rpm[i] = prandom_int_range_stable(&hwmon_fans_rpm[i], FAN_SPEED_DEV, FAKE_RPM_MIN, FAKE_RPM_MAX);
snprintf(reading->sensor[i].value, sizeof(reading->sensor[i].value), "%d", hwmon_fans_rpm[i]);
++reading->sensor_num;
hwmon_pr_loc_dbg("mfgBIOS: <= %s() %s->%d RPM", __FUNCTION__,
hwmon_sys_fan_id_map[hwmon_cfg->sys_fan_speed_rpm[i]], hwmon_fans_rpm[i]);
}
return 0;
}
/**
* Returns HWMON disk backplane status
*
* Currently values here are just a guesstimation - we don't have a platform to see the real values but based on their
* names it's assumed these are number of detected and enabled disks.
* This probably should ask the SCSI driver for the number of disks overall (as no PC architecture has any clue about
* number of disks present physically if they don't register with the system). On a real hardware it's probably checked
* by some contact switch/IR sensor to check if a given slot for a disk isn't empty.
*
* @param reading Pointer to save results
* @return 0 on success, -E on error
*/
static int bios_hwmon_get_hdd_backplane(SYNO_HWMON_SENSOR_TYPE *reading)
{
guard_hwmon_cfg();
const int hdd_num = 1; //todo: this should be taken from SCSI layer
guarded_strscpy(reading->type_name, HWMON_HDD_BP_STATUS_NAME, sizeof(reading->type_name));
hwmon_pr_loc_dbg("mfgBIOS: => %s(type=%s)", __FUNCTION__, reading->type_name);
reading->sensor_num = 0;
for (int i = 0; i < HWMON_SYS_HDD_BP_IDS; i++) {
if (hwmon_cfg->hdd_backplane[i] == HWMON_SYS_HDD_BP_NULL_ID)
break;
guarded_strscpy(reading->sensor[i].sensor_name, hwmon_hdd_bp_id_map[hwmon_cfg->hdd_backplane[i]],
sizeof(reading->sensor[i].sensor_name)); //Save the name of the sensor
snprintf(reading->sensor[i].value, sizeof(reading->sensor[i].value), "%d", hdd_num);
++reading->sensor_num;
hwmon_pr_loc_dbg("mfgBIOS: <= %s() %s->%d", __FUNCTION__, hwmon_hdd_bp_id_map[hwmon_cfg->hdd_backplane[i]],
hdd_num);
}
return 0;
}
/**
* (Should) Return HWMON power supplies status
*
* Currently this command is not implemented and always return an error as we haven't yet seen any devices using it.
*
* @param reading Pointer to save results
* @return 0 on success, -E on error
*/
static int bios_hwmon_get_psu_status(struct hw_config_hwmon *hwc, SYNO_HWMON_SENSOR_TYPE *reading)
{
pr_loc_wrn("mfgBIOS: **UNIMPLEMENTED** %s(type=%s)", __FUNCTION__, HWMON_PSU_STATUS_NAME);
return -EIO; //todo: we haven't [yet] seen a device using this
}
/**
* (Should) Return HWMON power consumption
*
* Currently this command is not implemented and always return an error as we haven't yet seen any devices using it.
*
* @param hwc Platform HWMON configuration
* @param reading Pointer to save results
*
* @return 0 on success, -E on error
*/
static int bios_hwmon_get_current(struct hw_config_hwmon *hwc, SYNO_HWMON_SENSOR_TYPE *reading)
{
pr_loc_wrn("mfgBIOS: **UNIMPLEMENTED** %s(type=%s)", __FUNCTION__, HWMON_SYS_CURRENT_NAME);
return -EIO; //todo: we haven't [yet] seen a device using this
}
/************************************************ mfgBIOS shim interface **********************************************/
int shim_bios_module_hwmon_entries(const struct hw_config *hw)
{
shim_reg_in();
hwmon_cfg = &hw->hwmon;
_shim_bios_module_entry(VTK_GET_FAN_STATE, bios_get_fan_state);
if (hw->has_cpu_temp)
_shim_bios_module_entry(VTK_GET_CPU_TEMP, bios_get_cpu_temp);
if (platform_has_hwmon_thermal(hw))
_shim_bios_module_entry(VTK_GET_HWMON_THERMAL, bios_hwmon_get_thermal);
if (platform_has_hwmon_voltage(hw))
_shim_bios_module_entry(VTK_GET_HWMON_VOLTAGE, bios_hwmon_get_voltages);
if (platform_has_hwmon_fan_rpm(hw))
_shim_bios_module_entry(VTK_GET_HWMON_FAN_RPM, bios_hwmon_get_fans_rpm);
if (platform_has_hwmon_hdd_bpl(hw))
_shim_bios_module_entry(VTK_GET_HWMON_HDD_BKPLANE, bios_hwmon_get_hdd_backplane);
if (platform_has_hwmon_psu_status(hw))
_shim_bios_module_entry(VTK_GET_HWMON_PSU_STATUS, bios_hwmon_get_psu_status);
if (platform_has_hwmon_current_sens(hw))
_shim_bios_module_entry(VTK_GET_HWMON_CURRENT, bios_hwmon_get_current);
shim_reg_ok();
return 0;
}
int reset_bios_module_hwmon_shim(void)
{
shim_reset_in();
hwmon_cfg = NULL;
cur_cpu_temp = 0;
try_kfree(hwmon_thermals);
try_kfree(hwmon_voltages);
try_kfree(hwmon_fans_rpm);
shim_reset_ok();
return 0;
}

12
shim/bios/bios_hwmon_shim.h Executable file
View File

@ -0,0 +1,12 @@
#ifndef REDPILL_BIOS_HWMON_SHIM_H
#define REDPILL_BIOS_HWMON_SHIM_H
#include <linux/synobios.h>
struct hw_config;
//This should be called from shim_bios_module() as it depends on the state of the vtable; it can be called many times
int shim_bios_module_hwmon_entries(const struct hw_config *hw);
int reset_bios_module_hwmon_shim(void);
#endif //REDPILL_BIOS_HWMON_SHIM_H

View File

@ -0,0 +1,208 @@
/**
* Overrides HWMONGetPSUStatusByI2C to provide fake psu status for SA6400, RS4021xsp and FS2500
* to find the required symbol you can consult the mfgbios output while booting the dev LKM
*/
#include "bios_psu_status_shim.h"
#include "../../common.h"
#include "../shim_base.h"
#include "../../internal/override/override_symbol.h" //overriding HWMONGetPSUStatusByI2C
#include "../../config/platform_types.h" //hw_config, platform_has_hwmon_*
#include <linux/synobios.h> //CAPABILITY_*, CAPABILITY
#define SHIM_NAME "mfgBIOS HWMONGetPSUStatusByI2C"
static const struct hw_config *hw_config = NULL;
static override_symbol_inst *HWMONGetPSUStatusByI2C_ovs = NULL;
static override_symbol_inst *RS4021xspI2CGetPowerInfo_ovs = NULL;
static override_symbol_inst *RS4022xspI2CGetPowerInfo_ovs = NULL;
static override_symbol_inst *RS4023xspI2CGetPowerInfo_ovs = NULL;
static override_symbol_inst *RS4024xspI2CGetPowerInfo_ovs = NULL;
static override_symbol_inst *FS3410I2CGetPowerInfo_ovs = NULL;
static override_symbol_inst *FS6400I2CGetPowerInfo_ovs = NULL;
static override_symbol_inst *FS6500I2CGetPowerInfo_ovs = NULL;
static override_symbol_inst *HD6500I2CGetPowerInfo_ovs = NULL;
static override_symbol_inst *SA6500I2CGetPowerInfo_ovs = NULL;
static int HWMONGetPSUStatusByI2C_shim(void)
{
return 0;
}
static int HWMONI2CGetPowerInfo_shim(void)
{
return 0;
}
int I2CSmbusReadPowerStatus(void)
{
return 0;
}
int register_bios_psu_status_shim(const struct hw_config *hw)
{
shim_reg_in();
if (unlikely(HWMONGetPSUStatusByI2C_ovs))
shim_reg_already();
if (unlikely(RS4021xspI2CGetPowerInfo_ovs))
shim_reg_already();
if (unlikely(RS4022xspI2CGetPowerInfo_ovs))
shim_reg_already();
if (unlikely(RS4023xspI2CGetPowerInfo_ovs))
shim_reg_already();
if (unlikely(RS4024xspI2CGetPowerInfo_ovs))
shim_reg_already();
if (unlikely(FS3410I2CGetPowerInfo_ovs))
shim_reg_already();
if (unlikely(FS6400I2CGetPowerInfo_ovs))
shim_reg_already();
if (unlikely(FS6500I2CGetPowerInfo_ovs))
shim_reg_already();
if (unlikely(HD6500I2CGetPowerInfo_ovs))
shim_reg_already();
if (unlikely(SA6500I2CGetPowerInfo_ovs))
shim_reg_already();
hw_config = hw;
override_symbol_or_exit_int(HWMONGetPSUStatusByI2C_ovs, "HWMONGetPSUStatusByI2C", HWMONGetPSUStatusByI2C_shim);
override_symbol_or_exit_int(RS4021xspI2CGetPowerInfo_ovs, "RS4021xspI2CGetPowerInfo", HWMONI2CGetPowerInfo_shim);
override_symbol_or_exit_int(RS4022xspI2CGetPowerInfo_ovs, "RS4022xspI2CGetPowerInfo", HWMONI2CGetPowerInfo_shim);
override_symbol_or_exit_int(RS4023xspI2CGetPowerInfo_ovs, "RS4023xspI2CGetPowerInfo", HWMONI2CGetPowerInfo_shim);
override_symbol_or_exit_int(RS4024xspI2CGetPowerInfo_ovs, "RS4024xspI2CGetPowerInfo", HWMONI2CGetPowerInfo_shim);
override_symbol_or_exit_int(FS3410I2CGetPowerInfo_ovs, "FS3410I2CGetPowerInfo", HWMONI2CGetPowerInfo_shim);
override_symbol_or_exit_int(FS6400I2CGetPowerInfo_ovs, "FS6400I2CGetPowerInfo", HWMONI2CGetPowerInfo_shim);
override_symbol_or_exit_int(FS6500I2CGetPowerInfo_ovs, "FS6500I2CGetPowerInfo", HWMONI2CGetPowerInfo_shim);
override_symbol_or_exit_int(HD6500I2CGetPowerInfo_ovs, "HD6500I2CGetPowerInfo", HWMONI2CGetPowerInfo_shim);
override_symbol_or_exit_int(SA6500I2CGetPowerInfo_ovs, "SA6500I2CGetPowerInfo", HWMONI2CGetPowerInfo_shim);
shim_reg_ok();
return 0;
}
int unregister_bios_psu_status_shim(void)
{
int out = 0;
shim_ureg_in();
if (unlikely(!HWMONGetPSUStatusByI2C_ovs))
return 0; //this is deliberately a noop
if (unlikely(!RS4021xspI2CGetPowerInfo_ovs))
return 0; //this is deliberately a noop
if (unlikely(!RS4022xspI2CGetPowerInfo_ovs))
return 0; //this is deliberately a noop
if (unlikely(!RS4023xspI2CGetPowerInfo_ovs))
return 0; //this is deliberately a noop
if (unlikely(!RS4024xspI2CGetPowerInfo_ovs))
return 0; //this is deliberately a noop
if (unlikely(!FS3410I2CGetPowerInfo_ovs))
return 0; //this is deliberately a noop
if (unlikely(!FS6400I2CGetPowerInfo_ovs))
return 0; //this is deliberately a noop
if (unlikely(!FS6500I2CGetPowerInfo_ovs))
return 0; //this is deliberately a noop
if (unlikely(!HD6500I2CGetPowerInfo_ovs))
return 0; //this is deliberately a noop
if (unlikely(!SA6500I2CGetPowerInfo_ovs))
return 0; //this is deliberately a noop
out = restore_symbol(HWMONGetPSUStatusByI2C_ovs);
if (unlikely(out != 0)) {
pr_loc_err("Failed to restore HWMONGetPSUStatusByI2C_ovs - error=%d", out);
return out;
}
HWMONGetPSUStatusByI2C_ovs = NULL;
out = restore_symbol(RS4021xspI2CGetPowerInfo_ovs);
if (unlikely(out != 0)) {
pr_loc_err("Failed to restore RS4021xspI2CGetPowerInfo_ovs - error=%d", out);
return out;
}
RS4021xspI2CGetPowerInfo_ovs = NULL;
out = restore_symbol(RS4022xspI2CGetPowerInfo_ovs);
if (unlikely(out != 0)) {
pr_loc_err("Failed to restore RS4022xspI2CGetPowerInfo_ovs - error=%d", out);
return out;
}
RS4022xspI2CGetPowerInfo_ovs = NULL;
out = restore_symbol(RS4023xspI2CGetPowerInfo_ovs);
if (unlikely(out != 0)) {
pr_loc_err("Failed to restore RS4023xspI2CGetPowerInfo_ovs - error=%d", out);
return out;
}
RS4023xspI2CGetPowerInfo_ovs = NULL;
out = restore_symbol(RS4024xspI2CGetPowerInfo_ovs);
if (unlikely(out != 0)) {
pr_loc_err("Failed to restore RS4024xspI2CGetPowerInfo_ovs - error=%d", out);
return out;
}
RS4024xspI2CGetPowerInfo_ovs = NULL;
out = restore_symbol(FS3410I2CGetPowerInfo_ovs);
if (unlikely(out != 0)) {
pr_loc_err("Failed to restore FS3410I2CGetPowerInfo_ovs - error=%d", out);
return out;
}
FS3410I2CGetPowerInfo_ovs = NULL;
out = restore_symbol(FS6400I2CGetPowerInfo_ovs);
if (unlikely(out != 0)) {
pr_loc_err("Failed to restore FS6400I2CGetPowerInfo_ovs - error=%d", out);
return out;
}
FS6400I2CGetPowerInfo_ovs = NULL;
out = restore_symbol(FS6500I2CGetPowerInfo_ovs);
if (unlikely(out != 0)) {
pr_loc_err("Failed to restore FS6500I2CGetPowerInfo_ovs - error=%d", out);
return out;
}
FS6500I2CGetPowerInfo_ovs = NULL;
out = restore_symbol(HD6500I2CGetPowerInfo_ovs);
if (unlikely(out != 0)) {
pr_loc_err("Failed to restore HD6500I2CGetPowerInfo_ovs - error=%d", out);
return out;
}
HD6500I2CGetPowerInfo_ovs = NULL;
out = restore_symbol(SA6500I2CGetPowerInfo_ovs);
if (unlikely(out != 0)) {
pr_loc_err("Failed to restore SA6500I2CGetPowerInfo_ovs - error=%d", out);
return out;
}
SA6500I2CGetPowerInfo_ovs = NULL;
shim_ureg_ok();
return 0;
}
int reset_bios_psu_status_shim(void)
{
shim_reset_in();
put_overridden_symbol(HWMONGetPSUStatusByI2C_ovs);
HWMONGetPSUStatusByI2C_ovs = NULL;
put_overridden_symbol(RS4021xspI2CGetPowerInfo_ovs);
RS4021xspI2CGetPowerInfo_ovs = NULL;
put_overridden_symbol(RS4022xspI2CGetPowerInfo_ovs);
RS4022xspI2CGetPowerInfo_ovs = NULL;
put_overridden_symbol(RS4023xspI2CGetPowerInfo_ovs);
RS4023xspI2CGetPowerInfo_ovs = NULL;
put_overridden_symbol(RS4024xspI2CGetPowerInfo_ovs);
RS4024xspI2CGetPowerInfo_ovs = NULL;
put_overridden_symbol(FS3410I2CGetPowerInfo_ovs);
FS3410I2CGetPowerInfo_ovs = NULL;
put_overridden_symbol(FS6400I2CGetPowerInfo_ovs);
FS6400I2CGetPowerInfo_ovs = NULL;
put_overridden_symbol(FS6500I2CGetPowerInfo_ovs);
FS6500I2CGetPowerInfo_ovs = NULL;
put_overridden_symbol(HD6500I2CGetPowerInfo_ovs);
HD6500I2CGetPowerInfo_ovs = NULL;
put_overridden_symbol(SA6500I2CGetPowerInfo_ovs);
SA6500I2CGetPowerInfo_ovs = NULL;
shim_reset_ok();
return 0;
}

View File

@ -0,0 +1,23 @@
#ifndef REDPILL_BIOS_PSU_STATUS_SHIM_H
#define REDPILL_BIOS_PSU_STATUS_SHIM_H
#include <linux/types.h> //bool
struct hw_config;
int register_bios_psu_status_shim(const struct hw_config *hw);
/**
* This function should be called when we're unloading cleanly (=mfgBIOS is alive, we're going away). If the bios went
* away on its own call reset_bios_psu_status_shim()
*/
int unregister_bios_psu_status_shim(void);
/**
* This function should be called when we're unloading because mfgBIOS went away. If the unload should be clean and
* restore all mfgBIOS elements to its original state (i.e. the mfgBIOS is still loaded and not currently unloading)
* call unregister_bios_psu_status_shim() instead.
*/
int reset_bios_psu_status_shim(void);
#endif //REDPILL_BIOS_PSU_STATUS_SHIM_H

347
shim/bios/bios_shims_collection.c Executable file
View File

@ -0,0 +1,347 @@
#include "bios_shims_collection.h"
#include "../../config/platform_types.h"
#include "rtc_proxy.h"
#include "bios_hwmon_shim.h"
#include "../../common.h"
#include "../../internal/helper/symbol_helper.h" //kernel_has_symbol()
#include "../../internal/override/override_symbol.h" //shimming leds stuff
#define DECLARE_NULL_ZERO_INT(for_what) \
static __used int bios_##for_what##_null_zero_int(void) \
{ \
pr_loc_dbg("mfgBIOS: nullify zero-int for " #for_what); \
return 0; \
}
#define SHIM_TO_NULL_ZERO_INT(for_what) _shim_bios_module_entry(for_what, bios_##for_what##_null_zero_int);
/********************************************* mfgBIOS LKM static shims ***********************************************/
static unsigned long org_shimmed_entries[VTK_SIZE] = {'\0'}; // original entries which were shimmed by custom entries
static unsigned long cust_shimmed_entries[VTK_SIZE] = {'\0'}; // custom entries which were set as shims
static int bios_get_power_status(POWER_INFO *power)
{
power->power_1 = POWER_STATUS_GOOD;
power->power_2 = POWER_STATUS_GOOD;
return 0;
}
static int shim_get_gpio_pin_usable(int *pin)
{
pin[1] = 0;
return 0;
}
static int shim_set_gpio_pin_usable(int *pin)
{
pr_loc_dbg("set_gpio pin info 0 %d", pin[0]);
pr_loc_dbg("set_gpio pin info 1 %d", pin[1]);
pr_loc_dbg("set_gpio pin info 2 %d", pin[2]);
pr_loc_dbg("set_gpio pin info 3 %d", pin[3]);
return 0;
}
static int bios_get_buz_clr(unsigned char *state)
{
*state = 0;
return 0;
}
/***************************************** Debug shims for unknown bios functions **************************************/
DECLARE_NULL_ZERO_INT(VTK_SET_FAN_STATE);
DECLARE_NULL_ZERO_INT(VTK_SET_DISK_LED);
DECLARE_NULL_ZERO_INT(VTK_SET_PWR_LED);
// DECLARE_NULL_ZERO_INT(VTK_SET_GPIO_PIN);
DECLARE_NULL_ZERO_INT(VTK_SET_GPIO_PIN_BLINK);
DECLARE_NULL_ZERO_INT(VTK_SET_ALR_LED);
DECLARE_NULL_ZERO_INT(VTK_SET_BUZ_CLR);
DECLARE_NULL_ZERO_INT(VTK_SET_CPU_FAN_STATUS);
DECLARE_NULL_ZERO_INT(VTK_SET_PHY_LED);
DECLARE_NULL_ZERO_INT(VTK_SET_HDD_ACT_LED);
DECLARE_NULL_ZERO_INT(VTK_GET_MICROP_ID);
DECLARE_NULL_ZERO_INT(VTK_SET_MICROP_ID);
/********************************************** mfgBIOS shimming routines *********************************************/
static unsigned long *vtable_start = NULL; // set when shim_bios_module is called()
void _shim_bios_module_entry(const unsigned int idx, const void *new_sym_ptr)
{
if (unlikely(!vtable_start))
{
pr_loc_bug("%s called without vtable start populated - are you calling it outside of shim_bios_module scope?!",
__FUNCTION__);
return;
}
if (unlikely(idx > VTK_SIZE - 1))
{
pr_loc_bug("Attempted shim on index %d - out of range", idx);
return;
}
// The vtable entry is either not shimmed OR already shimmed with what we set before OR already *was* shimmed but
// external (i.e. mfgBIOS) code overrode the shimmed entry.
// We only save the original entry if it was set by the mfgBIOS (so not shimmed yet or ext. override situation)
// it was already shimmed and the shim is still there => noop
if (cust_shimmed_entries[idx] && cust_shimmed_entries[idx] == vtable_start[idx])
return;
pr_loc_dbg("mfgBIOS vtable [%d] originally %ps<%p> will now be %ps<%p>", idx, (void *)vtable_start[idx],
(void *)vtable_start[idx], new_sym_ptr, new_sym_ptr);
org_shimmed_entries[idx] = vtable_start[idx];
cust_shimmed_entries[idx] = (unsigned long)new_sym_ptr;
vtable_start[idx] = cust_shimmed_entries[idx];
}
/**
* Prints a table of memory between vtable_start and vtable_end, trying to resolve symbols as it goes
*/
static void print_debug_symbols(const unsigned long *vtable_end)
{
if (unlikely(!vtable_start))
{
pr_loc_dbg("Cannot print - no vtable address");
return;
}
int im = vtable_end - vtable_start; // Should be multiplies of 8 in general (64 bit alignment)
pr_loc_dbg("Will print %d bytes of memory from %p", im, vtable_start);
unsigned long *call_ptr = vtable_start;
unsigned char *byte_ptr = (char *)vtable_start;
for (int i = 0; i < im; i++, byte_ptr++)
{
pr_loc_dbg_raw("%02x ", *byte_ptr);
if ((i + 1) % 8 == 0)
{
pr_loc_dbg_raw(" [%02d] 0x%03x \t%p\t%pS\n", i / 8, i - 7, (void *)(*call_ptr), (void *)(*call_ptr));
call_ptr++;
}
}
pr_loc_dbg_raw("\n");
pr_loc_dbg("Finished printing memory at %p", byte_ptr);
}
/**
* Applies shims to the vtable used by the bios
*
* These calls may execute multiple times as the mfgBIOS is loading.
*
* @return true when shimming succeeded, false otherwise
*/
bool shim_bios_module(const struct hw_config *hw, struct module *mod, unsigned long *vt_start, unsigned long *vt_end)
{
if (unlikely(!vt_start || !vt_end))
{
pr_loc_bug("%s called without vtable start or vt_end populated?!", __FUNCTION__);
return false;
}
vtable_start = vt_start;
print_debug_symbols(vt_end);
SHIM_TO_NULL_ZERO_INT(VTK_SET_FAN_STATE);
SHIM_TO_NULL_ZERO_INT(VTK_SET_DISK_LED);
SHIM_TO_NULL_ZERO_INT(VTK_SET_PWR_LED);
// SHIM_TO_NULL_ZERO_INT(VTK_SET_GPIO_PIN);
_shim_bios_module_entry(VTK_GET_GPIO_PIN, shim_get_gpio_pin_usable);
_shim_bios_module_entry(VTK_SET_GPIO_PIN, shim_set_gpio_pin_usable);
SHIM_TO_NULL_ZERO_INT(VTK_SET_GPIO_PIN_BLINK);
SHIM_TO_NULL_ZERO_INT(VTK_SET_ALR_LED);
_shim_bios_module_entry(VTK_GET_BUZ_CLR, bios_get_buz_clr);
SHIM_TO_NULL_ZERO_INT(VTK_SET_BUZ_CLR);
_shim_bios_module_entry(VTK_GET_PWR_STATUS, bios_get_power_status);
SHIM_TO_NULL_ZERO_INT(VTK_SET_CPU_FAN_STATUS);
SHIM_TO_NULL_ZERO_INT(VTK_SET_PHY_LED);
SHIM_TO_NULL_ZERO_INT(VTK_SET_HDD_ACT_LED);
SHIM_TO_NULL_ZERO_INT(VTK_GET_MICROP_ID);
SHIM_TO_NULL_ZERO_INT(VTK_SET_MICROP_ID);
if (hw->emulate_rtc)
{
pr_loc_dbg("Platform requires RTC proxy - enabling");
register_rtc_proxy_shim();
_shim_bios_module_entry(VTK_RTC_GET_TIME, rtc_proxy_get_time);
_shim_bios_module_entry(VTK_RTC_SET_TIME, rtc_proxy_set_time);
_shim_bios_module_entry(VTK_RTC_INT_APWR, rtc_proxy_init_auto_power_on);
_shim_bios_module_entry(VTK_RTC_GET_APWR, rtc_proxy_get_auto_power_on);
_shim_bios_module_entry(VTK_RTC_SET_APWR, rtc_proxy_set_auto_power_on);
_shim_bios_module_entry(VTK_RTC_UINT_APWR, rtc_proxy_uinit_auto_power_on);
}
else
{
pr_loc_dbg("Native RTC supported - not enabling proxy (emulate_rtc=%d)", hw->emulate_rtc ? 1 : 0);
}
shim_bios_module_hwmon_entries(hw); // Shim all hardware environment stuff (temps, fans, etc.)
print_debug_symbols(vt_end);
return true;
}
bool unshim_bios_module(unsigned long *vt_start, unsigned long *vt_end)
{
for (int i = 0; i < VTK_SIZE; i++)
{
// make sure to check the cust_ one as org_ may contain NULL ptrs and we should restore them as NULL if they were
// so originally
if (!cust_shimmed_entries[i])
continue;
pr_loc_dbg("Restoring vtable [%d] from %ps<%p> to %ps<%p>", i, (void *)vt_start[i],
(void *)vt_start[i], (void *)org_shimmed_entries[i], (void *)org_shimmed_entries[i]);
vtable_start[i] = org_shimmed_entries[i];
}
reset_bios_shims();
return true;
}
void reset_bios_shims(void)
{
memset(org_shimmed_entries, 0, sizeof(org_shimmed_entries));
memset(cust_shimmed_entries, 0, sizeof(cust_shimmed_entries));
unregister_rtc_proxy_shim();
reset_bios_module_hwmon_shim();
}
/*
* Syno kernel has ifdefs for "MY_ABC_HERE" for syno_ahci_disk_led_enable() and syno_ahci_disk_led_enable_by_port() so
* we need to check if they really exist and we cannot determine it statically
*/
/*
* Only purley and epyc7002 not set CONFIG_SYNO_SATA_DISK_LED_CONTROL.
* So only FS6400, HD6500, SA6400 now
*/
#if !defined(CONFIG_SYNO_EPYC7002) && !defined(CONFIG_SYNO_PURLEY)
static override_symbol_inst *ov_funcSYNOSATADiskLedCtrl = NULL;
#endif
static override_symbol_inst *ov_syno_ahci_disk_led_enable = NULL;
static override_symbol_inst *ov_syno_ahci_disk_led_enable_by_port = NULL;
#if !defined(CONFIG_SYNO_EPYC7002) && !defined(CONFIG_SYNO_PURLEY)
/******************************** Kernel-level shims related to mfgBIOS functionality *********************************/
extern void *funcSYNOSATADiskLedCtrl; // if this explodes one day we need to do kernel_has_symbol() on it dynamically
static int funcSYNOSATADiskLedCtrl_shim(int host_num, SYNO_DISK_LED led)
{
pr_loc_dbg("Received %s with host=%d led=%d", __FUNCTION__, host_num, led);
// exit code is not used anywhere in the public code, so this value is an educated guess based on libata-scsi.c
return 0;
}
#endif
int syno_ahci_disk_led_enable_shim(const unsigned short host_num, const int value)
{
pr_loc_dbg("Received %s with host=%d val=%d", __FUNCTION__, host_num, value);
return 0;
}
int syno_ahci_disk_led_enable_by_port_shim(const unsigned short port, const int value)
{
pr_loc_dbg("Received %s with port=%d val=%d", __FUNCTION__, port, value);
return 0;
}
int shim_disk_leds_ctrl(const struct hw_config *hw)
{
// we're checking this here to remove knowledge of "struct hw_config" from bios_shim letting others know it's NOT
// the place to do BIOS shimming decisions
if (!hw->fix_disk_led_ctrl)
return 0;
pr_loc_dbg("Shimming disk led control API");
int out;
#if !defined(CONFIG_SYNO_EPYC7002) && !defined(CONFIG_SYNO_PURLEY)
// funcSYNOSATADiskLedCtrl exists on (almost?) all platforms, but it's null on some... go figure ;)
if (funcSYNOSATADiskLedCtrl)
{
ov_funcSYNOSATADiskLedCtrl = override_symbol("funcSYNOSATADiskLedCtrl", funcSYNOSATADiskLedCtrl_shim);
if (unlikely(IS_ERR(ov_funcSYNOSATADiskLedCtrl)))
{
out = PTR_ERR(ov_funcSYNOSATADiskLedCtrl);
ov_funcSYNOSATADiskLedCtrl = NULL;
pr_loc_err("Failed to shim funcSYNOSATADiskLedCtrl, error=%d", out);
return out;
}
}
#endif
if (kernel_has_symbol("syno_ahci_disk_led_enable"))
{
ov_syno_ahci_disk_led_enable = override_symbol("syno_ahci_disk_led_enable", syno_ahci_disk_led_enable_shim);
if (unlikely(IS_ERR(ov_syno_ahci_disk_led_enable)))
{
out = PTR_ERR(ov_syno_ahci_disk_led_enable);
ov_syno_ahci_disk_led_enable = NULL;
pr_loc_err("Failed to shim syno_ahci_disk_led_enable, error=%d", out);
return out;
}
}
if (kernel_has_symbol("syno_ahci_disk_led_enable_by_port"))
{
ov_syno_ahci_disk_led_enable_by_port = override_symbol("syno_ahci_disk_led_enable_by_port", syno_ahci_disk_led_enable_by_port_shim);
if (unlikely(IS_ERR(ov_syno_ahci_disk_led_enable_by_port)))
{
out = PTR_ERR(ov_syno_ahci_disk_led_enable_by_port);
ov_syno_ahci_disk_led_enable_by_port = NULL;
pr_loc_err("Failed to shim syno_ahci_disk_led_enable_by_port, error=%d", out);
return out;
}
}
pr_loc_dbg("Finished %s", __FUNCTION__);
return 0;
}
int unshim_disk_leds_ctrl(void)
{
pr_loc_dbg("Unshimming disk led control API");
int out;
bool failed = false;
#if !defined(CONFIG_SYNO_EPYC7002) && !defined(CONFIG_SYNO_PURLEY)
if (ov_funcSYNOSATADiskLedCtrl)
{
out = restore_symbol(ov_funcSYNOSATADiskLedCtrl);
ov_funcSYNOSATADiskLedCtrl = NULL;
if (unlikely(out != 0))
{ // falling through to try to unshim others too
pr_loc_err("Failed to unshim funcSYNOSATADiskLedCtrl, error=%d", out);
failed = true;
}
}
#endif
if (ov_syno_ahci_disk_led_enable)
{
out = restore_symbol(ov_syno_ahci_disk_led_enable);
ov_syno_ahci_disk_led_enable = NULL;
if (unlikely(out != 0))
{ // falling through to try to unshim others too
pr_loc_err("Failed to unshim syno_ahci_disk_led_enable, error=%d", out);
failed = true;
}
}
if (ov_syno_ahci_disk_led_enable_by_port)
{
out = restore_symbol(ov_syno_ahci_disk_led_enable_by_port);
ov_syno_ahci_disk_led_enable_by_port = NULL;
if (unlikely(out != 0))
{
pr_loc_err("Failed to unshim syno_ahci_disk_led_enable_by_port, error=%d", out);
failed = true;
}
}
out = failed ? -EINVAL : 0;
pr_loc_dbg("Finished %s (exit=%d)", __FUNCTION__, out);
return out;
}

View File

@ -0,0 +1,56 @@
#ifndef REDPILL_BIOS_SHIMS_COLLECTION_H
#define REDPILL_BIOS_SHIMS_COLLECTION_H
#include <linux/types.h> //bool
#include <linux/module.h> //struct module
typedef struct hw_config hw_config_bios_shim_col;
/**
* Insert all the shims to the mfgBIOS
*/
bool shim_bios_module(const hw_config_bios_shim_col *hw, struct module *mod, unsigned long *vtable_start, unsigned long *vtable_end);
/**
* Removes all shims from the mfgBIOS & uninitializes all components used to shim bios module
*/
bool unshim_bios_module(unsigned long *vtable_start, unsigned long *vtable_end);
/**
* Forcefully forgets all original calls used to do unshim_bios() & cleans-up all other components
*
* This function is useful when the BIOS unloads without this module being unloaded - then there's no point in keeping
* stale entries. This will also prevent warning regarding already-shimmed BIOS when it reloads.
*/
void reset_bios_shims(void);
/**
* Nullifies manual disks LED control
*
* The underlying reason for this isn't known but sometimes the manual LED control for disks (presumably used to blink
* to identify disks from the UI) will cause a kernel panic pointing to the internals of mfgBIOS. The functionality is
* implemented in the kernel (funcSYNOSATADiskLedCtrl) but it delegates the task to mfgBIOS via ioctl()
* To prevent the crash we replace the manual LED altering subsystem in models which support it (because not all do).
*
* The kernel panic is most likely caused by the gap between early and full bios shimming where the bios will be early
* shimmed, continue setting something and in between gets an ioctl to the LED api
*
* @return 0 on success or -E on error
*/
int shim_disk_leds_ctrl(const struct hw_config *hw);
/**
* Reverses what shim_disk_leds_ctrl did
*
* You CAN call this function any time, if shims weren't registered (yet) it will be a noop-call.
*
* @return 0 on success or -E on error
*/
int unshim_disk_leds_ctrl(void);
/**
* Used by mfgBIOS sub-shims. Should NOT be called from ANY other context as it depends on the internal state.
*/
void _shim_bios_module_entry(unsigned int idx, const void *new_sym_ptr);
#endif //REDPILL_BIOS_SHIMS_COLLECTION_H

283
shim/bios/mfgbios_types.h Executable file
View File

@ -0,0 +1,283 @@
/*
* This file contains structures/types used for compatibility with the mfg bios
*
* These are not original types used by the mfg BIOS, but rather recreations from available documentation unless
* available in GPLed source (in which case they're #include'd)
*/
#ifndef REDPILL_SYNOBIOS_COMPAT_H
#define REDPILL_SYNOBIOS_COMPAT_H
#include <linux/synobios.h> //SYNO_* & SYNO* types
//Missing HWMON types... only in some toolchains
#ifndef HWMON_PSU_SENSOR_TEMP1
#define HWMON_PSU_SENSOR_TEMP1 "temperature_1"
#endif
#ifndef HWMON_PSU_SENSOR_TEMP2
#define HWMON_PSU_SENSOR_TEMP2 "temperature_2"
#endif
#ifndef HWMON_PSU_SENSOR_TEMP3
#define HWMON_PSU_SENSOR_TEMP3 "temperature_3"
#endif
#ifndef HWMON_PSU_SENSOR_FAN_VOLT
#define HWMON_PSU_SENSOR_FAN_VOLT "fan_voltage"
#endif
//These aren't defined in synobios.h (while sensors from other categories are... go figure)
#define HWMON_SYS_TZONE_REMOTE1_NAME "Remote1"
#define HWMON_SYS_TZONE_REMOTE2_NAME "Remote2"
#define HWMON_SYS_TZONE_LOCAL_NAME "Local"
#define HWMON_SYS_TZONE_SYSTEM_NAME "system"
#define HWMON_SYS_TZONE_ADT1_LOC_NAME "ADT1 Local"
#define HWMON_SYS_TZONE_ADT2_LOC_NAME "ADT2 Local"
#define HWMON_SYS_VSENS_VCC_NAME "VCC"
#define HWMON_SYS_VSENS_VPP_NAME "VPP"
#define HWMON_SYS_VSENS_V33_NAME "V33"
#define HWMON_SYS_VSENS_V5_NAME "V5"
#define HWMON_SYS_VSENS_V12_NAME "V12"
#define HWMON_SYS_VSENS_ADT1_V33_NAME "ADT1 V33"
#define HWMON_SYS_VSENS_ADT2_V33_NAME "ADT2 V33"
#define HWMON_SYS_CURR_ADC_NAME "ADC"
struct MfgCompatTime {
unsigned char second;
unsigned char minute;
unsigned char hours;
unsigned char wkday;
unsigned char day;
unsigned char month;
unsigned char year;
};
enum MfgCompatFanStatus {
MFGC_FAN_UNKNOWN = -1,
MFGC_FAN_STOPPED = 0,
MFGC_FAN_RUNNING = 1,
};
enum MfgCompatFanSpeed {
MFGC_FAN_SPD_STOP1,
MFGC_FAN_SPD_STOP2,
//Fan speeds in 1-8 scale
MFGC_FAN_SPD_1,
MFGC_FAN_SPD_2,
MFGC_FAN_SPD_3,
MFGC_FAN_SPD_4,
MFGC_FAN_SPD_5,
MFGC_FAN_SPD_6,
MFGC_FAN_SPD_7,
MFGC_FAN_SPD_8,
//Fan speeds in 1-18 scale, used for testing only
MFGC_FAN_SPD_TST_1,
MFGC_FAN_SPD_TST_2,
MFGC_FAN_SPD_TST_3,
MFGC_FAN_SPD_TST_4,
MFGC_FAN_SPD_TST_5,
MFGC_FAN_SPD_TST_6,
MFGC_FAN_SPD_TST_7,
MFGC_FAN_SPD_TST_8,
MFGC_FAN_SPD_TST_9,
MFGC_FAN_SPD_TST_10,
MFGC_FAN_SPD_TST_11,
MFGC_FAN_SPD_TST_12,
MFGC_FAN_SPD_TST_13,
MFGC_FAN_SPD_TST_14,
MFGC_FAN_SPD_TST_15,
MFGC_FAN_SPD_TST_16,
MFGC_FAN_SPD_TST_17,
MFGC_FAN_SPD_TST_18,
MFGC_FAN_SPD_PWM = 1000
};
struct MfgCompatHddLedStatus {
int hdd_no;
SYNO_DISK_LED state;
int pos_name_len; //includes null terminator
char *pos_name;
};
enum MfgCompatGenericLedState {
MFGC_LED_OFF,
MFGC_LED_LIT,
MFGC_LED_BLINK,
};
struct MfgCompatMemoryByte {
unsigned char offset;
unsigned char value;
};
struct MfgCompatMemoryUInt {
unsigned char address;
unsigned char value;
};
struct MfgCompatCPLDReg {
unsigned char hddLedCtrl;
unsigned char hddPwrState;
unsigned char hwModelNum;
unsigned char fanState;
};
struct MfgCompatGPIOPin {
int num;
int val;
};
struct MfgCompatRtcEvent {
unsigned char minutes; //BCD format
unsigned char hours; //BCD format
unsigned char weekdays; //7 bit field, Sun => Sat
};
struct MfgCompatAutoPwrOn {
int num;
bool enabled;
struct MfgCompatRtcEvent events[100];
};
struct MfgCompatPowerStatus {
bool primary_ok;
bool secondary_ok;
};
enum MfgCompatBackplaneStatus {
MFGC_BKPLANE_UNK = -1,
MFGC_BKPLANE_ERR = 0,
MFGC_BKPLANE_OK = 1,
};
struct MfgCompatPWMState {
int channel;
int freq_hz;
int duty_cycle;
int rpm;
};
struct MfgCompatSuperIOMem {
unsigned char ldn;
unsigned char reg;
unsigned char val;
};
struct MfgCompatBusPacket {
long num;
long len;
char msg[128];
};
struct MfgCompatCPUState {
unsigned int cpu;
char clock[16];
#if defined(CONFIG_SYNO_GRANTLEY) || defined(CONFIG_SYNO_PURLEY)
unsigned int core[CONFIG_SYNO_MULTI_CPU_NUM];
#endif
};
enum MfgCompatCopyBtnState {
MFGC_BTN_DOWN = 0, //aka pressed
MFGC_BTN_UP = 1, //aka not pressed
};
typedef enum {
POWER_STATUS_BAD = 0,
POWER_STATUS_GOOD,
} SYNO_POWER_STATUS;
typedef struct _tag_POWER_INFO {
SYNO_POWER_STATUS power_1;
SYNO_POWER_STATUS power_2;
} POWER_INFO;
typedef int (*mfgc_void_cb)(void); //int f(void)
typedef int (*mfgc_time_cb)(struct MfgCompatTime *); //int f(MfgCompatTime *)
typedef int (*mfgc_get_fan_state_cb)(int, enum MfgCompatFanStatus *); //int f(int, MfgCompatFanStatus *)
typedef int (*mfgc_set_fan_state_cb)(enum MfgCompatFanStatus, enum MfgCompatFanSpeed); //int f(MfgCompatFanStatus, MfgCompatFanSpeed)
typedef int (*mfgc_hwmon_sensor_cb)(SYNO_HWMON_SENSOR_TYPE *); //int f(SYNO_HWMON_SENSOR_TYPE *)
//TODO: this list is not complete - add all callback types
#ifdef CONFIG_SYNO_PORT_MAPPING_V2
typedef int (*mfgc_set_hdd_led_cb)(struct MfgCompatHddLedStatus *status);
#else
typedef int (*mfgc_set_hdd_led_cb)(int, SYNO_DISK_LED state); //int f(void)
#endif
//List of known indexes in the mfgBIOS vtable. The table can be recovered by shim/bios_shim.c. Some of its entries are
// replaced by shim/bios/bios_shims_collection.c
//The following indexes were determined based on
// - Jun's module code
// - Looking at the symbols when BIOS is loaded
// - Observing logs from mfgBIOS
#define VTK_STRUCT_OWNER 0 //you shouldn't really modify this
#define VTK_GET_BRAND 1 //Sig: int f(void)
#define VTK_GET_MODEL 2 //Sig: int f(void)
#define VTK_GET_CPLD_VER 3 //Sig: int f(void)
#define VTK_RTC_GET_TIME 4 //Sig: int f(MfgCompatTime *)
#define VTK_RTC_SET_TIME 5 //Sig: int f(MfgCompatTime *)
#define VTK_GET_FAN_STATE 6 //Sig: int f(int, MfgCompatFanStatus *) | present in: DS918+; not: DS3615xs
#define VTK_SET_FAN_STATE 7 //Sig: int f(MfgCompatFanStatus, MfgCompatFanSpeed)
#define VTK_GET_SYS_TEMP 8 //Sig: int f(SYNO_THERMAL_TEMP *) | present in: DS3615xs; not: DS918+
#define VTK_GET_CPU_TEMP 9 //Sig: int f(SYNOCPUTEMP *)
#define VTK_SET_DISK_LED 10 //Sig: varies, see mfgc_set_hdd_led_cb type
#define VTK_SET_PWR_LED 11 //Sig: int f(MfgCompatGenericLedState)
#define VTK_GET_CPLD_REG 12 //Sig: int f(MfgCompatCPLDReg *)
#define VTK_SET_PMU_MEM_BYTE 13 //Sig: int f(MfgCompatMemoryByte *)
#define VTK_GET_PMU_MEM_BYTE 14 //Sig: int f(MfgCompatMemoryByte *)
#define VTK_SET_GPIO_PIN 15 //Sig: int f(MfgCompatGPIOPin *)
#define VTK_GET_GPIO_PIN 16 //Sig: int f(MfgCompatGPIOPin *)
#define VTK_SET_GPIO_PIN_BLINK 17 //Sig: int f(MfgCompatGPIOPin *)
#define VTK_RTC_SET_APWR 18 //Sig: int f(MfgCompatAutoPwrOn *) | set auto power on
#define VTK_RTC_GET_APWR 19 //Sig: int f(MfgCompatAutoPwrOn *) | get auto power on
#define VTK_RTC_INT_APWR 20 //Sig: int f(void) | initialize auto power on. present in: DS918+; not: DS3615xs
#define VTK_RTC_UINT_APWR 21 //Sig: int f(void) | uninitialize auto power on. present in: DS918+; not: DS3615xs
#define VTK_SET_ALR_LED 22 //Sig: int f(MfgCompatGenericLedState) | alarm led
#define VTK_GET_BUZ_CLR 23 //Sig: int f(unsigned char *)
#define VTK_SET_BUZ_CLR 24 //Sig: int f(unsigned char)
#define VTK_GET_PWR_STATUS 25 //Sig: int f(MfgCompatPowerStatus *)
#define VTK_GET_BKPLANE_STATUS 26 //Sig: int f(MfgCompatBackplaneStatus *) | backplane status
#define VTK_INT_MOD_TPE 27
#define VTK_UNINIT 28 //Sig: int f(void)
#define VTK_SET_CPU_FAN_STATUS 29 //Sig: int f(MfgCompatFanStatus, MfgCompatFanSpeed)
#define VTK_SET_PHY_LED 30 //Sig: int f(MfgCompatGenericLedState) | present in: DS620; not: DS3615xs, DS918+
#define VTK_SET_HDD_ACT_LED 31 //Sig: int f(MfgCompatGenericLedState)
#define VTK_SET_PWM 32 //Sig: int f(MfgCompatPWMState *)
#define VTK_GET_MICROP_ID 33
#define VTK_SET_MICROP_ID 34 //Sig: int f(void)
#define VTK_GET_SIO_MEM 35 //Sig: int f(MfgCompatSuperIOMem *)
#define VTK_SET_SIO_MEM 36 //Sig: int f(MfgCompatSuperIOMem *)
#define VTK_SEND_LCD_PKT 37 //Sig: int f(MfgCompatBusPacket *)
#define VTK_GET_MEM_UINT 38 //Sig: int f(MfgCompatMemoryUInt *)
#define VTK_SET_MEM_UINT 39 //Sig: int f(MfgCompatMemoryUInt *)
#define VTK_GET_CPU_INF 40 //Sig: void f(MfgCompatCPUState*, uint)
#define VTK_SET_HA_LED 41 //present in: RC18015xs+ (and other HA units? don't have other); not: DS3615xs,DS918+
#define VTK_GET_CPY_BTN 42 //Sig: MfgCompatCopyBtnState f(void) | present in: DS718+
#if RP_MODULE_TARGET_VER == 6
#define VTK_SET_SAFE_REMOVE_LED 43 //Sig: int f(bool) | present in: DS3615xs; not: DS918+
#define VTK_GET_SYS_CURRENT 44 //Sig: int f(SYNO_DISK_INTF_INFO *)
#define VTK_GET_HWMON_FAN_RPM 45 //Sig: int f(SYNO_HWMON_SENSOR_TYPE *)
#define VTK_GET_HWMON_PSU_STATUS 46 //Sig: int f(SYNO_HWMON_SENSOR_TYPE *)
#define VTK_GET_HWMON_VOLTAGE 47 //Sig: int f(SYNO_HWMON_SENSOR_TYPE *)
#define VTK_GET_HWMON_HDD_BKPLANE 48 //Sig: int f(SYNO_HWMON_SENSOR_TYPE *)
#define VTK_GET_HWMON_THERMAL 49 //Sig: int f(SYNO_HWMON_SENSOR_TYPE *)
#define VTK_GET_HWMON_CURRENT 50 //Sig: int f(SYNO_HWMON_SENSOR_TYPE *)
#elif RP_MODULE_TARGET_VER == 7 //moved VTK_SET_SAFE_REMOVE_LED and VTK_GET_SYS_CURRENT below HWMON_ stuff (sic!)
#define VTK_GET_HWMON_FAN_RPM 43 //Sig: int f(SYNO_HWMON_SENSOR_TYPE *)
#define VTK_GET_HWMON_PSU_STATUS 44 //Sig: int f(SYNO_HWMON_SENSOR_TYPE *)
#define VTK_GET_HWMON_VOLTAGE 45 //Sig: int f(SYNO_HWMON_SENSOR_TYPE *)
#define VTK_GET_HWMON_HDD_BKPLANE 46 //Sig: int f(SYNO_HWMON_SENSOR_TYPE *)
#define VTK_GET_HWMON_THERMAL 47 //Sig: int f(SYNO_HWMON_SENSOR_TYPE *)
#define VTK_GET_HWMON_CURRENT 48 //Sig: int f(SYNO_HWMON_SENSOR_TYPE *)
#define VTK_SET_SAFE_REMOVE_LED 49 //Sig: int f(bool) | present in: DS3615xs; not: DS918+
#define VTK_GET_SYS_CURRENT 50 //Sig: int f(SYNO_DISK_INTF_INFO *)
#endif //RP_MODULE_TARGET_VER
#define VTK_GET_HDD_IFACE 51
#define VTK_SIZE 52
#endif //REDPILL_SYNOBIOS_COMPAT_H

270
shim/bios/rtc_proxy.c Executable file
View File

@ -0,0 +1,270 @@
/*
* Proxy between an ACPI RTC and mfgBIOS calls
*
* Some platforms don't use a standard RTC chip but implement a custom platform-specific one. To handle different chips
* mfgBIOS uses a standardized interface. This works perfectly fine when mfgBIOS expects an ACPI-complaint RTC to be
* present. However, it does not work when a given platform is expected to contain some 3rd-party I2C clock chip.
*
* Motorola MC146818 was a de facto standard RTC chip when PC/AT emerged. Later on other clones started emulating the
* interface. This become so prevalent that ACPI standardized the basic interface of RTC on PC-compatibile systems as
* MC146818 interface. Thus, this module assumes that mfgBIOS calls can be proxied to MC146818 interface (which will
* work on any ACPI-complaint system and any sane hypervisor).
*
* As some of the functions are rarely used (and often even completely broken on many systems), like RTC wakeup they're
* not really implemented but instead mocked to look "just good enough".
*
* References:
* - https://www.kernel.org/doc/html/latest/admin-guide/rtc.html
* - https://embedded.fm/blog/2018/6/5/an-introduction-to-bcd
*/
#include "../../common.h"
#include "rtc_proxy.h"
#include "../shim_base.h" //shim_*()
#include <linux/mc146818rtc.h>
#include <linux/bcd.h>
#define SHIM_NAME "RTC proxy"
//Confused? See https://slate.com/technology/2016/02/the-math-behind-leap-years.html
#define year_is_leap(year) !((year)%((year)%25?4:16))
#define mfg_year_to_full(val) ((val)+1900) //MfgCompatTime counts years as offset from 1900
#define mfg_month_to_normal(val) ((val)+1) //MfgCompatTime has 0-based months
#define normal_month_to_mfg(val) ((val)-1)
static const unsigned char months_to_days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
static struct MfgCompatAutoPwrOn *auto_power_on_mock = NULL;
inline static void debug_print_mfg_time(struct MfgCompatTime *mfgTime)
{
pr_loc_dbg("MfgCompatTime raw data: sec=%u min=%u hr=%u wkd=%u day=%u mth=%u yr=%u", mfgTime->second,
mfgTime->minute, mfgTime->hours, mfgTime->wkday, mfgTime->day, mfgTime->month, mfgTime->year);
}
/**
* Standardizes & abstracts RTC reading
*
* Reading & writing RTC requires conversion of values based on some registers and chips. This function simply accept
* pointers to YY-MM-DD WeekDay HHmmss values and does all the conversions for you after reading.
*/
static void read_rtc_num(unsigned char *yy, unsigned char *mm, unsigned char *dd, unsigned char *wd, unsigned char *hr,
unsigned char *mi, unsigned char *ss)
{
//As the clock uses IRQ 8 normally we need to atomically stop it to read all values and restore it later
unsigned long flags;
spin_lock_irqsave(&rtc_lock, flags);
const unsigned char rtc_control = CMOS_READ(RTC_CONTROL);
//There are two formats how RTCs can report time: normal numbers or an ancient BCD. Currently (at least in Linux v4)
//BCD is always used for MC146818 (but this can change). This we need to handle both cases.
if (likely(RTC_ALWAYS_BCD) || (rtc_control & RTC_DM_BINARY)) { //a common idiom, search for RTC_ALWAYS_BCD in kernel
pr_loc_dbg("Reading BCD-based RTC");
*yy = bcd2bin(CMOS_READ(RTC_YEAR));
*mm = bcd2bin(CMOS_READ(RTC_MONTH));
*dd = bcd2bin(CMOS_READ(RTC_DAY_OF_MONTH));
*wd = bcd2bin(CMOS_READ(RTC_DAY_OF_WEEK));
*hr = bcd2bin(CMOS_READ(RTC_HOURS));
*mi = bcd2bin(CMOS_READ(RTC_MINUTES));
*ss = bcd2bin(CMOS_READ(RTC_SECONDS));
} else {
pr_loc_dbg("Reading binary-based RTC");
*yy = CMOS_READ(RTC_YEAR);
*mm = CMOS_READ(RTC_MONTH);
*dd = CMOS_READ(RTC_DAY_OF_MONTH);
*wd = CMOS_READ(RTC_DAY_OF_WEEK);
*hr = CMOS_READ(RTC_HOURS);
*mi = CMOS_READ(RTC_MINUTES);
*ss = CMOS_READ(RTC_SECONDS);
}
spin_unlock_irqrestore(&rtc_lock, flags);
}
/**
* Standardizes & abstracts RTC time setting
*
* Reading & writing RTC requires conversion of values based on some registers and chips. This function simply accepts
* values to be set in YY-MM-DD WeekDay HHmmss format and does all the conversions/locking/freq resets for you.
*/
static void write_rtc_num(unsigned char yy, unsigned char mm, unsigned char dd, unsigned char wd, unsigned char hr,
unsigned char mi, unsigned char ss)
{
unsigned long flags;
spin_lock_irqsave(&rtc_lock, flags);
unsigned char rtc_control = CMOS_READ(RTC_CONTROL); //RTC control register value locked for us
unsigned char rtc_freq_tick = CMOS_READ(RTC_FREQ_SELECT);
CMOS_WRITE((rtc_control|RTC_SET), RTC_CONTROL); //enter clock setting state
CMOS_WRITE(rtc_freq_tick|RTC_DIV_RESET2, RTC_FREQ_SELECT); //this should reset the ticks
if (likely(RTC_ALWAYS_BCD) || (rtc_control & RTC_DM_BINARY)) { //a common idiom, search for RTC_ALWAYS_BCD in kernel
pr_loc_dbg("Writing BCD-based RTC");
CMOS_WRITE(bin2bcd(yy), RTC_YEAR);
CMOS_WRITE(bin2bcd(mm), RTC_MONTH);
CMOS_WRITE(bin2bcd(dd), RTC_DAY_OF_MONTH);
CMOS_WRITE(bin2bcd(wd), RTC_DAY_OF_WEEK);
CMOS_WRITE(bin2bcd(hr), RTC_HOURS);
CMOS_WRITE(bin2bcd(mi), RTC_MINUTES);
CMOS_WRITE(bin2bcd(ss), RTC_SECONDS);
} else {
pr_loc_dbg("Writing binary-based RTC");
CMOS_WRITE(yy, RTC_YEAR);
CMOS_WRITE(mm, RTC_MONTH);
CMOS_WRITE(dd, RTC_DAY_OF_MONTH);
CMOS_WRITE(wd, RTC_DAY_OF_WEEK);
CMOS_WRITE(hr, RTC_HOURS);
CMOS_WRITE(mi, RTC_MINUTES);
CMOS_WRITE(ss, RTC_SECONDS);
}
CMOS_WRITE(rtc_control, RTC_CONTROL); //restore original control register
CMOS_WRITE(rtc_freq_tick, RTC_FREQ_SELECT); //...and the ticks too
spin_unlock_irqrestore(&rtc_lock, flags);
}
int rtc_proxy_get_time(struct MfgCompatTime *mfgTime)
{
if (mfgTime == NULL) {
pr_loc_wrn("Got an invalid call to %s", __FUNCTION__);
return -EPERM;
}
debug_print_mfg_time(mfgTime);
unsigned char rtc_year; //mfgTime uses offset from 1900 while RTC uses 2-digit format (see below)
unsigned char rtc_month; //mfgTime uses 0-11 while RTC uses 1-12
read_rtc_num(&rtc_year, &rtc_month, &mfgTime->day, &mfgTime->wkday, &mfgTime->hours, &mfgTime->minute,
&mfgTime->second);
//So yeah, it's 2021 and PC RTCs still use 2 digit year so we have to do a classic Y2K hack with epoch
//RTC nowadays is assumed to have a range of 1970-2069 which forces two assumptions:
// - Values 0-69 indicate 2000-2069
// - Values 70-99 indicate 1970-1999
//As the mfgTime->year uses value of years since 1900 without magic rollovers we need to correct it by 100 for the
//2000s epoch. Search for e.g. "mc146818_decode_year" in Linux, that's (sadly) a common method.
mfgTime->year = (likely(rtc_year < 70)) ? rtc_year + 100 : rtc_year;
mfgTime->month = normal_month_to_mfg(rtc_month);
pr_loc_inf("Time got from RTC is %4d-%02d-%02d %2d:%02d:%02d (UTC)", mfg_year_to_full(mfgTime->year),
mfg_month_to_normal(mfgTime->month), mfgTime->day, mfgTime->hours, mfgTime->minute, mfgTime->second);
debug_print_mfg_time(mfgTime);
return 0;
}
int rtc_proxy_set_time(struct MfgCompatTime *mfgTime)
{
if (mfgTime == NULL) {
pr_loc_wrn("Got an invalid call to %s", __FUNCTION__);
return -EPERM;
}
debug_print_mfg_time(mfgTime);
//Ok, this is PROBABLY not needed but we don't want to crash the RTC if an invalid value is passed to this function
//Also, we are aware of leap seconds but do you think 1984 hardware is? (spoiler alert: no)
if (unlikely(mfgTime->second > 59 || mfgTime->minute > 59 || mfgTime->hours > 24 || mfgTime->wkday > 6 ||
mfgTime->day == 0 || mfgTime->month > 11)) {
pr_loc_wrn("Got invalid generic RTC data in %s", __FUNCTION__);
return -EINVAL;
}
//Year validation needs to take leap years into account. This code can be shorter but it's expended for readability
if (unlikely(mfgTime->month == 1 && year_is_leap(mfgTime->year))) {
if (mfgTime->day > (months_to_days[mfgTime->month] + 1)) {
pr_loc_wrn("Invalid RTC leap year day (%u > %u) of month %u in %s", mfgTime->day,
(months_to_days[mfgTime->month] + 1), mfgTime->month, __FUNCTION__);
return -EINVAL;
}
} else if (mfgTime->day > months_to_days[mfgTime->month]) {
pr_loc_wrn("Invalid RTC regular year day (%u > %u) of month %u in %s", mfgTime->day,
months_to_days[mfgTime->month], mfgTime->month, __FUNCTION__);
return -EINVAL;
}
//mfgTime->year uses a positive offset since 1900. However, ACPI-complain RTC cannot handle range higher than
//1970-2069 (see comment in rtc_proxy_get_time()).
unsigned char rtc_year = mfgTime->year; //mfgTime uses offset from 1900 while RTC uses 2-digit format (see below)
if (unlikely(rtc_year > 169)) { //This cannot be valid as RTC cannot handle >2069
pr_loc_wrn("Year overflow in %s", __FUNCTION__);
return -EINVAL;
} else if(likely(rtc_year > 100)) {
rtc_year -= 100; //RTC uses 0-69 for 2000s so we need to shift mfgTime 1900-now offset by 100
}
unsigned char rtc_month = mfg_month_to_normal(mfgTime->month); //mfgTime uses 0-11 while RTC uses 1-12
write_rtc_num(rtc_year, rtc_month, mfgTime->day, mfgTime->wkday, mfgTime->hours, mfgTime->minute, mfgTime->second);
pr_loc_inf("RTC time set to %4d-%02d-%02d %2d:%02d:%02d (UTC)", mfg_year_to_full(mfgTime->year),
mfg_month_to_normal(mfgTime->month), mfgTime->day, mfgTime->hours, mfgTime->minute, mfgTime->second);
return 0;
}
int rtc_proxy_init_auto_power_on(void)
{
pr_loc_dbg("RTC power-on \"enabled\" via %s", __FUNCTION__);
return 0;
}
int rtc_proxy_get_auto_power_on(struct MfgCompatAutoPwrOn *mfgPwrOn)
{
if (unlikely(!auto_power_on_mock)) {
pr_loc_bug("Auto power-on mock is not initialized - did you forget to call register?");
return -EINVAL;
}
pr_loc_dbg("Mocking auto-power GET on RTC");
memcpy(mfgPwrOn, auto_power_on_mock, sizeof(struct MfgCompatAutoPwrOn));
return 0;
}
int rtc_proxy_set_auto_power_on(struct MfgCompatAutoPwrOn *mfgPwrOn)
{
if (!mfgPwrOn || mfgPwrOn->num < 0) { //That's just either a bogus call or a stupid call
pr_loc_wrn("Got an invalid call to %s", __FUNCTION__);
return -EINVAL;
}
pr_loc_dbg("Mocking auto-power SET on RTC");
memcpy(auto_power_on_mock, mfgPwrOn, sizeof(struct MfgCompatAutoPwrOn));
return 0;
}
int rtc_proxy_uinit_auto_power_on(void)
{
pr_loc_dbg("RTC power-on \"disabled\" via %s", __FUNCTION__);
return 0;
}
int unregister_rtc_proxy_shim(void)
{
shim_ureg_in();
//This is not an error as bios shim collections calls unregister blindly
if (!auto_power_on_mock) {
pr_loc_dbg("The %s shim is not registered - ignoring", SHIM_NAME);
return 0;
}
kfree(auto_power_on_mock);
auto_power_on_mock = NULL;
shim_ureg_ok();
return 0;
}
int register_rtc_proxy_shim(void)
{
shim_reg_in();
if (unlikely(auto_power_on_mock)) {
pr_loc_wrn("The %s shim is already registered - unregistering first", SHIM_NAME);
unregister_rtc_proxy_shim();
}
kzalloc_or_exit_int(auto_power_on_mock, sizeof(struct MfgCompatAutoPwrOn));
shim_reg_ok();
return 0;
}

42
shim/bios/rtc_proxy.h Executable file
View File

@ -0,0 +1,42 @@
#ifndef REDPILL_RTC_PROXY_H
#define REDPILL_RTC_PROXY_H
#include "mfgbios_types.h"
/**
* Gets current RTC time (shims VTK_RTC_GET_TIME)
*/
int rtc_proxy_get_time(struct MfgCompatTime *mfgTime);
/**
* Sets current RTC time (shims VTK_RTC_SET_TIME)
*/
int rtc_proxy_set_time(struct MfgCompatTime *mfgTime);
/**
* Enables auto-power on functionality (shims VTK_RTC_INT_APWR).
*
* This is not REALLY implemented and only shimmed. Many motherboards don't handle it well or only support it from
* certain ACPI PSTATEs. It is even more unsupported by hypervisors. If you REALLY need it create a bug report or a PR.
*/
int rtc_proxy_init_auto_power_on(void);
/**
* Gets time for auto-power on (shims VTK_RTC_GET_APWR). **See note for rtc_proxy_init_auto_power_on()**
*/
int rtc_proxy_get_auto_power_on(struct MfgCompatAutoPwrOn *mfgPwrOn);
/**
* Sets time for auto-power on (shims VTK_RTC_SET_APWR). **See note for rtc_proxy_init_auto_power_on()**
*/
int rtc_proxy_set_auto_power_on(struct MfgCompatAutoPwrOn *mfgPwrOn);
/**
* Disables auto-power on functionality (shims VTK_RTC_UINT_APWR). **See note for rtc_proxy_init_auto_power_on()**
*/
int rtc_proxy_uinit_auto_power_on(void);
int unregister_rtc_proxy_shim(void);
int register_rtc_proxy_shim(void);
#endif //REDPILL_RTC_PROXY_H

411
shim/bios_shim.c Executable file
View File

@ -0,0 +1,411 @@
/*
* This shim is responsible for making the hardware<>DSM glue (aka mfg BIOS) happy by providing nullified
* implementations of hardware-specific calls.
*
* The process relies on the fact that the original BIOS module keeps a vtable table in memory. That vtable contains
* pointers to various functions used to communicate with the hardware. The most tricky part here is finding the vtable
* and replacing calls in it with our own. Original ELF contains unscrambled symbols for the table under "synobios_ops"
* name (see: readelf --syms /usr/lib/modules/synobios.ko | grep 'synobios_ops'). However this is NOT a symbol which
* gets exported to the kernel.
*
* When the Linux kernel loads a module it does a couple of things after loading the .ko file. From the important ones
* here it reads the ELF, loads the .symtab (all symbols), processes all relocations, and then does a cleanup of stuff
* which is not needed after module is loaded. The earliest hook normally available for other modules is the access
* through modules notification API. It will provide access to the module as soon as its binary is loaded and init
* function is executing. However:
* - we only get the access to the "struct module"
* - the data available contains kallsyms
* - at this point all non-kernel symbols are discarded from memory (see kernel/module.c:simplify_symbols())
*
* While the symbols exist in the memory the symbol table cannot be accessed (short of loading the ELF again and
* re-parsing the binary... which is way too complex). Most of the ELF parsing routines in the kernel are implemented
* in kernel/module.c in `static` functions. This unfortunately means they aren't really replaceable as they are
* inlined and mangled. However, there's one place where CPU architecture-dependent step happens: relocation of
* symbols. When module.c:apply_relocations() is called on x86_64 it calls the
* arch/x86/kernel/module.c:apply_relocate_add(). Since this function is external it can be "gently" replaced.
*
* During the lifetime of apply_relocate_add(), which is redirected to _apply_relocate_add() here, the full ELF with
* symbol table is available and thus the vtable can be located using process_bios_symbols(). However, it cannot be
* just like that modified at this moment (remember: we're way before module init is called) as 1) functions it points
* to may be relocated still, and 2) it's hardware-dependent (as seen by doing print_debug_symbols() before & after
* init). We need to hook to the module notification API and shim what's needed AFTER module started initializing.
*
* So in summary:
* 1. Redirect apply_relocate_add() => _apply_relocate_add() using internal/override_symbol.h
* 2. Setup module notifier
* 3. Look for "*_synobios" module in _apply_relocate_add() and if found iterate through symbols
* 4. Find "synobios_ops" in full symbols table and save it's start & end addresses; disable override from [1]
* 5. Wait until notified by the kernel about module started loaded (see bios_module_notifier_handler())
* 6. Replace what's needed (see bios/bios_shims_collection.c:shim_bios_module())
* 7. Wait until notified by the kernel about module fully loaded (and replace what was broken since 5.)
* 8. Drink a beer
*
* Additionally, this module also handles replacement of some kernel structures called by the mfgBIOS:
* - see bios_shims_collection.c:shim_disk_leds_ctrl()
*
* References:
* - https://en.wikipedia.org/wiki/Virtual_method_table
*/
#define SHIM_NAME "mfgBIOS"
#include "bios_shim.h"
#include "shim_base.h"
#include "../common.h"
#include "../internal/override/override_symbol.h"
#include "../internal/helper/symbol_helper.h" //kernel_has_symbol()
#include "bios/bios_shims_collection.h" //shim_bios_module(), unshim_bios_module(), shim_bios_disk_leds_ctrl()
#include "bios/bios_hwcap_shim.h" //register_bios_hwcap_shim(), unregister_bios_hwcap_shim(), reset_bios_hwcap_shim()
#include "bios/bios_psu_status_shim.h" //register_bios_psu_status_shim(), unregister_bios_psu_status_shim(), reset_bios_psu_status_shim()
#include <linux/elf.h>
#include <linux/notifier.h> //module notification
#include <linux/module.h> //struct module
static bool bios_shimmed = false;
static bool module_notify_registered = false;
static unsigned long *vtable_start = NULL;
static unsigned long *vtable_end = NULL;
static const struct hw_config *hw_config = NULL;
static inline int enable_symbols_capture(void);
static inline int disable_symbols_capture(void);
/********************************************* Shimming of mfgBIOS module *********************************************/
/**
* Unified way to determine if a given module is a bios module (as this is not a simple == check)
*/
static inline bool is_bios_module(const char *name)
{
char *separator_pos = strrchr(name, '_'); //bios will be named e.g. bromolow_synobios - find's the last _
//Check if it's synobios or sth else really
return (separator_pos && strcmp(separator_pos, "_synobios") == 0);
}
/**
* Handles notifications regarding modules loading. It will only perform actions on modules matching is_bios_module()
*
* This is constantly loaded to provide useful error information in case the bios module goes away (it shouldn't). In
* non-dev builds it can probably just go away.
*
* @return NOTIFY_* const
*/
static int bios_module_notifier_handler(struct notifier_block * self, unsigned long state, void * data)
{
struct module *mod = data;
if (!is_bios_module(mod->name))
return NOTIFY_OK;
if (state == MODULE_STATE_GOING) {
//So this is actually not a problem with RP but rather with the bios module - it cannot be unloaded at will.
//As soon as you try it will cause a circular error with page faults and the kernel will demand a reboot
//We're not unregistering notifier in case one day this is fixed by the bios module ¯\_(ツ)_/¯
pr_loc_err("%s BIOS went away - you may get a kernel panic if YOU unloaded it", mod->name);
bios_shimmed = false;
vtable_start = vtable_end = NULL;
enable_symbols_capture();
reset_bios_shims();
reset_bios_hwcap_shim();
reset_bios_psu_status_shim();
return NOTIFY_OK;
}
if (bios_shimmed)
return NOTIFY_OK;
//So, this is really tricky actually. Some parts of the vtable are populated AND USED during init and some are
// populated in init but used later. This means we need to try to shim twice - as fast as possible after init call
// and just after init call finished.
//We react to every module action by re-shimming its vtable as it might have changed. Other actions are done only
// once below.
if (!shim_bios_module(hw_config, mod, vtable_start, vtable_end)) {
bios_shimmed = false;
return NOTIFY_OK;
}
if (state == MODULE_STATE_LIVE) {
bios_shimmed = true;
pr_loc_inf("%s BIOS *fully* shimmed", mod->name);
} else { //MODULE_STATE_COMING or MODULE_STATE_UNFORMED [but most likely actually MODULE_STATE_COMING]
if (likely(state == MODULE_STATE_COMING)) {
register_bios_hwcap_shim(hw_config);
register_bios_psu_status_shim(hw_config);
}
pr_loc_inf("%s BIOS *early* shimmed", mod->name);
}
return NOTIFY_OK;
}
static struct notifier_block bios_notifier_block = {
.notifier_call = bios_module_notifier_handler
};
/**
* Registers module notifier to modify vtable as soon as module finishes loading
*
* @return 0 on success, -E on failure
*/
static int register_bios_module_notifier(void)
{
if (unlikely(module_notify_registered)) {
pr_loc_bug("%s called while notifier already registered", __FUNCTION__);
return -EALREADY;
}
//Check if the bios module is already present in the system. If it is we have a problem as the vtable must be
// patched as it loads. It's unclear if it can be patched after it's loaded but most certainly we don't have the
// address of the table. That's why this is an error. If by any chance we have an address we can try patching but
// this scenario is unlikely to work (and re-loading of the bios is not possible as it KPs). There's also no EASY
// way of accessing list of modules (and the bios module name depends on platform etc...)
//This symbol is chosen semi-randomly (i.e. it should be stable over time) but it shouldn't be present anywhere else
if (unlikely(kernel_has_symbol("synobios_ioctl"))) {
pr_loc_err("BIOS module is already loaded (did you load this module too late?) - cannot recover!");
return -EDEADLOCK;
}
int out = register_module_notifier(&bios_notifier_block);
if(unlikely(out != 0)) {
pr_loc_err("Failed to register module notifier"); //Currently it's impossible to happen... currently
return out;
}
module_notify_registered = true;
pr_loc_dbg("Registered bios module notifier");
return 0;
}
/**
* Reverses what register_bios_module_notifier did
*
* @return 0 on success, -E on failure
*/
static int unregister_bios_module_notifier(void)
{
if (unlikely(!module_notify_registered)) {
pr_loc_bug("%s called while notifier not yet registered", __FUNCTION__);
return -ENOMEDIUM;
}
int out = unregister_module_notifier(&bios_notifier_block);
if(unlikely(out != 0)) {
pr_loc_err("Failed to unregister module notifier");
return out;
}
module_notify_registered = false;
pr_loc_dbg("Unregistered bios module notifier");
return 0;
}
#define BIOS_CALLTABLE "synobios_ops"
/**
* Scans module ELF headers for BIOS_CALLTABLE and saves its address
*/
static void process_bios_symbols(Elf64_Shdr *sechdrs, const char *strtab, unsigned int symindex, struct module *mod)
{
Elf64_Shdr *symsec = &sechdrs[symindex];
pr_loc_dbg("Symbol section <%p> @ vaddr<%llu> size[%llu]", symsec, symsec->sh_addr, symsec->sh_size);
Elf64_Sym *sym;
Elf64_Sym *vtable = NULL;
sym = (void *)symsec->sh_addr; //First symbol in the table
unsigned int i;
for (i = 0; i < symsec->sh_size / sizeof(Elf64_Sym); i++) {
const char *symname = strtab + sym[i].st_name;
pr_loc_dbg("Symbol #%d in mfgBIOS \"%s\" {%s}<%p>", i, mod->name, symname, (void *)sym[i].st_value);
//There are more than one, we're looking for THE table (not a pointer)
if (strncmp(symname, BIOS_CALLTABLE, sizeof(BIOS_CALLTABLE)) == 0 && sym[i].st_size > sizeof(void *)) {
pr_loc_dbg("Found vtable - size %llu", sym[i].st_size);
vtable = &sym[i];
break;
}
}
//That, to my knowledge, shouldn't happen
if (unlikely(!vtable)) {
pr_loc_wrn("Didn't find \"%s\" in \"%s\" this time - that's weird?", BIOS_CALLTABLE, mod->name);
return;
}
vtable_start = (unsigned long *)vtable->st_value;
vtable_end = vtable_start + vtable->st_size;
pr_loc_dbg("Found \"%s\" in \"%s\" @ <%p =%llu=> %p>", (strtab + vtable->st_name), mod->name, vtable_start,
vtable->st_size, vtable_end);
disable_symbols_capture();
}
/**************************************************** Entrypoints *****************************************************/
int register_bios_shim(const struct hw_config *hw)
{
int out;
hw_config = hw;
shim_reg_in();
if (
(out = shim_disk_leds_ctrl(hw)) != 0 ||
(out = enable_symbols_capture()) != 0 ||
(out = register_bios_module_notifier()) != 0
) {
return out;
}
shim_reg_ok();
return 0;
}
int unregister_bios_shim(void)
{
int out;
shim_ureg_in();
if (likely(bios_shimmed)) {
if (!unshim_bios_module(vtable_start, vtable_end))
return -EINVAL;
}
out = unregister_bios_module_notifier();
if (unlikely(out != 0))
return out;
out = disable_symbols_capture();
if (unlikely(out != 0))
return out;
unshim_disk_leds_ctrl(); //this will be noop if nothing was registered
unregister_bios_hwcap_shim(); //this will be noop if nothing was registered
unregister_bios_psu_status_shim(); //this will be noop if nothing was registered
hw_config = NULL;
shim_ureg_ok();
return 0;
}
/************************************************** Internal Helpers **************************************************/
/**
* A modified arch/x86/kernel/module.c:apply_relocate_add() from Linux v3.10.108 to save synobios_ops address
*
* This is taken straight from Linux v3.10 and modified:
* - added call to process_bios_symbols
* - commented-out DEBUGP
* Original author notice: Copyright (C) 2001 Rusty Russell
*/
static int _apply_relocate_add(Elf64_Shdr *sechdrs, const char *strtab, unsigned int symindex, unsigned int relsec, struct module *me)
{
unsigned int i;
Elf64_Rela *rel = (void *)sechdrs[relsec].sh_addr;
Elf64_Sym *sym;
void *loc;
u64 val;
//Well, this is here because there isn't a good place to plug-in into modules loading to get the full symbols table
//Later on kernel removes "useless" symbols (see module.c:simplify_symbols())... but we need them
//After vtable address is found this override of apply_relocate_add() is removed
if (!vtable_start && is_bios_module(me->name))
process_bios_symbols(sechdrs, strtab, symindex, me);
// DEBUGP("Applying relocate section %u to %u\n",
// relsec, sechdrs[relsec].sh_info);
for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) {
/* This is where to make the change */
loc = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr
+ rel[i].r_offset;
/* This is the symbol it is referring to. Note that all
undefined symbols have been resolved. */
sym = (Elf64_Sym *)sechdrs[symindex].sh_addr
+ ELF64_R_SYM(rel[i].r_info);
// DEBUGP("type %d st_value %Lx r_addend %Lx loc %Lx\n",
// (int)ELF64_R_TYPE(rel[i].r_info),
// sym->st_value, rel[i].r_addend, (u64)loc);
val = sym->st_value + rel[i].r_addend;
switch (ELF64_R_TYPE(rel[i].r_info)) {
case R_X86_64_NONE:
break;
case R_X86_64_64:
*(u64 *)loc = val;
break;
case R_X86_64_32:
*(u32 *)loc = val;
if (val != *(u32 *)loc)
goto overflow;
break;
case R_X86_64_32S:
*(s32 *)loc = val;
if ((s64)val != *(s32 *)loc)
goto overflow;
break;
case R_X86_64_PC32:
case R_X86_64_PLT32:
val -= (u64)loc;
*(u32 *)loc = val;
break;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,20,0)
case R_X86_64_PC64:
val -= (u64)loc;
*(u64 *)loc = val;
break;
#endif
default:
pr_err("%s: Unknown rela relocation: %llu\n",
me->name, ELF64_R_TYPE(rel[i].r_info));
return -ENOEXEC;
}
}
return 0;
overflow:
pr_err("overflow in relocation type %d val %Lx\n",
(int)ELF64_R_TYPE(rel[i].r_info), val);
pr_err("`%s' likely not compiled with -mcmodel=kernel\n",
me->name);
return -ENOEXEC;
}
static override_symbol_inst *ov_apply_relocate_add = NULL;
/**
* Enables override of apply_relocate_add() to redirect it to _apply_relocate_add() in order to plug into a moment where
* process_bios_symbols() can extract the data.
*
* @return 0 on success, -E on failure
*/
static inline int enable_symbols_capture(void)
{
if (unlikely(ov_apply_relocate_add))
return 0; //Technically it's working so it's a non-error scenario (and it may happen with modules notification)
ov_apply_relocate_add = override_symbol("apply_relocate_add", _apply_relocate_add);
if (unlikely(IS_ERR(ov_apply_relocate_add))) {
int out = PTR_ERR(ov_apply_relocate_add);
ov_apply_relocate_add = NULL;
pr_loc_err("Failed to override apply_relocate_add, error=%d", out);
return out;
}
return 0;
}
/**
* Disables override of apply_relocate_add() if enabled.
*
* @return 0 on success/noop, -E on failure
*/
static inline int disable_symbols_capture(void)
{
if (!ov_apply_relocate_add) //may have been restored before
return 0;
int out = restore_symbol(ov_apply_relocate_add);
ov_apply_relocate_add = NULL;
return out;
}

8
shim/bios_shim.h Executable file
View File

@ -0,0 +1,8 @@
#ifndef REDPILLLKM_BIOS_SHIM_H
#define REDPILLLKM_BIOS_SHIM_H
struct hw_config;
int register_bios_shim(const struct hw_config *hw);
int unregister_bios_shim(void);
#endif //REDPILLLKM_BIOS_SHIM_H

92
shim/block_fw_update_shim.c Executable file
View File

@ -0,0 +1,92 @@
/**
* This rather simple shim prevents execution of a firmware update program when done in a one specific way
*
* During the OS installation process one of the steps executes a command "./H2OFFT-Lx64". This is a board firmware
* update program. When executed under KVM it will crash the virtual CPU (and I wasn't brave enough to try it on bare
* metal). All in all the execution must succeed from the perspective of the user-space and the file cannot be modified
* due to checksum check.
*
* This shim hooks a execve() syscall and filter it through shim_sys_execve(). This in turn is self-explanatory for the
* most part - it simply fakes successful execution without invoking anything. While such trickery can be detected (as
* the real process is not really replaced) it is good enough for this case.
*
* Additionally, to make the firmware picking happy we need to pass a sanity check (which is presumably done to ensure
* flasher doesn't accidentally brick an incorrect board) using DMI data. This is handled here by overriding one string
* in the DMI data array (as the kernel API lacks any way of changing that).
*
* References:
* - https://linux.die.net/man/3/execve
* - https://0xax.gitbooks.io/linux-insides/content/SysCall/linux-syscall-4.html
* - https://help.ubuntu.com/community/FimwareUpgrade/Insyde
*/
#define SHIM_NAME "firmware update blocker"
#include "block_fw_update_shim.h"
#include "shim_base.h"
#include "../common.h"
#include "../internal/intercept_execve.h"
#include <linux/dmi.h> //dmi_get_system_info(), DMI_*
#define DMI_MAX_LEN 512
#define FW_BOARD_NAME "\x53\x79\x6e\x6f\x64\x65\x6e" //Synoden
#define FW_UPDATE_PATH "./H2OFFT-Lx64"
static char dmi_product_name_backup[DMI_MAX_LEN] = { '\0' };
static void patch_dmi(void)
{
char *ptr = (char *)dmi_get_system_info(DMI_PRODUCT_NAME);
if (unlikely(ptr == 0)) {
pr_loc_err("Error getting DMI_PRODUCT_NAME, impossible to patch DMI");
return;
}
size_t org_len = strlen(ptr);
if (org_len > DMI_MAX_LEN)
pr_loc_wrn("DMI field longer than %zu - restoring on module unload will be limited to that length", org_len);
if(strlcpy((char *)&dmi_product_name_backup, ptr, DMI_MAX_LEN) < 0)
pr_loc_wrn("Backup DMI truncated to %d", DMI_MAX_LEN);
pr_loc_dbg("Saved backup DMI: %s", dmi_product_name_backup);
//This TECHNICALLY can cause overflow but DMI has buffer for such a short string
if (org_len < strlen_static(FW_BOARD_NAME))
pr_loc_bug("Shimmed DMI field will be longer than original!");
strcpy(ptr, FW_BOARD_NAME);
}
static void unpatch_dmi(void)
{
if (dmi_product_name_backup[0] == '\0') {
pr_loc_dbg("Skipping %s - DMI not patched", __FUNCTION__);
return;
}
strcpy((char *)dmi_get_system_info(DMI_PRODUCT_NAME), dmi_product_name_backup);
pr_loc_dbg("DMI unpatched");
}
int register_fw_update_shim(void)
{
shim_reg_in();
int out = add_blocked_execve_filename(FW_UPDATE_PATH);
if (out != 0)
return out;
patch_dmi();
shim_reg_ok();
return 0;
}
int unregister_fw_update_shim(void)
{
shim_ureg_in();
//Do not remove execve registration here - it will be cleared in one sweep during unregister of interceptor
unpatch_dmi();
shim_ureg_ok();
return 0;
}

7
shim/block_fw_update_shim.h Executable file
View File

@ -0,0 +1,7 @@
#ifndef REDPILL_BLOCK_FW_UPDATE_SHIM_H
#define REDPILL_BLOCK_FW_UPDATE_SHIM_H
int register_fw_update_shim(void);
int unregister_fw_update_shim(void);
#endif //REDPILL_BLOCK_FW_UPDATE_SHIM_H

70
shim/boot_dev/boot_shim_base.c Executable file
View File

@ -0,0 +1,70 @@
#include "boot_shim_base.h"
#include "../../common.h"
#include "../../config/runtime_config.h" //struct boot_media
#include "../../internal/scsi/scsi_toolbox.h" //is_sata_disk(), opportunistic_read_capacity()
#include <scsi/scsi_device.h> //struct scsi_device
#include <linux/usb.h> //struct usb_device
//Definition of known VID/PIDs for USB-based shims
#define SBOOT_RET_VID 0xf400 //Retail boot drive VID
#define SBOOT_RET_PID 0xf400 //Retail boot drive PID
#define SBOOT_MFG_VID 0xf401 //Force-reinstall boot drive VID
#define SBOOT_MFG_PID 0xf401 //Force-reinstall boot drive PID
void *mapped_shim_data = NULL;
void set_shimmed_boot_dev(void *private_data)
{
mapped_shim_data = private_data;
}
void *get_shimmed_boot_dev(void)
{
return mapped_shim_data;
}
bool scsi_is_boot_dev_target(const struct boot_media *boot_dev_config, struct scsi_device *sdp)
{
if (!is_sata_disk(&sdp->sdev_gendev)) {
pr_loc_dbg("%s: it's not a SATA disk, ignoring", __FUNCTION__);
return false;
}
pr_loc_dbg("Checking if SATA disk is a shim target - id=%u channel=%u vendor=\"%s\" model=\"%s\"", sdp->id,
sdp->channel, sdp->vendor, sdp->model);
long long capacity_mib = opportunistic_read_capacity(sdp);
if (unlikely(capacity_mib < 0)) {
pr_loc_dbg("Failed to estimate drive capacity (error=%lld) - it WILL NOT be shimmed", capacity_mib);
return false;
}
if (capacity_mib > boot_dev_config->dom_size_mib) {
pr_loc_dbg("Device has capacity of ~%llu MiB - it WILL NOT be shimmed (>%lu)", capacity_mib,
boot_dev_config->dom_size_mib);
return false;
}
if (unlikely(get_shimmed_boot_dev())) {
pr_loc_wrn("Boot device was already shimmed but a new matching device (~%llu MiB <= %lu) appeared again - "
"this may produce unpredictable outcomes! Ignoring - check your hardware", capacity_mib,
boot_dev_config->dom_size_mib);
return false;
}
pr_loc_dbg("Device has capacity of ~%llu MiB - it is a shimmable target (<=%lu)", capacity_mib,
boot_dev_config->dom_size_mib);
return true;
}
void usb_shim_as_boot_dev(const struct boot_media *boot_dev_config, struct usb_device *usb_device)
{
if (boot_dev_config->mfg_mode) {
usb_device->descriptor.idVendor = cpu_to_le16(SBOOT_MFG_VID);
usb_device->descriptor.idProduct = cpu_to_le16(SBOOT_MFG_PID);
} else {
usb_device->descriptor.idVendor = cpu_to_le16(SBOOT_RET_VID);
usb_device->descriptor.idProduct = cpu_to_le16(SBOOT_RET_PID);
}
}

57
shim/boot_dev/boot_shim_base.h Executable file
View File

@ -0,0 +1,57 @@
#ifndef REDPILL_BOOT_SHIM_BASE_H
#define REDPILL_BOOT_SHIM_BASE_H
#include <linux/types.h> //bool
struct boot_media;
struct usb_device;
struct scsi_device;
/**
* Modify given USB device instance to conform to syno kernel boot device specification
*
* This function takes into consideration the boot_media configuration regarding mfg vs retail mode and will change the
* device descriptors accordingly. It is safe to call this function multiple times on the same object.
*
* @param boot_dev_config Configuration to determine boot mode
* @param usb_device Device to change
*/
void usb_shim_as_boot_dev(const struct boot_media *boot_dev_config, struct usb_device *usb_device);
/**
* Save a free-form pointer into a global storage to mark boot device as shimmed
*
* Other subsystems can determine if the boot device has been shimmed by calling get_shimmed_boot_dev(). However, the
* data passed to this function is opaque by design and only makes sense to the submodule which originally set it.
*
* @param private_data Any non-null pointer or value castable to a pointer type (e.g. unsigned long number)
*/
void set_shimmed_boot_dev(void *private_data);
/**
* Shortcut to remove previously marked as shimmed boot device. It is an equivalent of simply calling set with NULL ptr.
*/
#define reset_shimmed_boot_dev() set_shimmed_boot_dev(NULL);
/**
* Gets shimmed boot device private data (if any)
*
* The caller should not try to interpret the value returned beyond NULL vs. non-NULL, unless the caller is the original
* submodule which set the value using set_shimmed_boot_dev().
*
* @return non-NULL pointer if device has been shimmed or NULL ptr if it wasn't
*/
void *get_shimmed_boot_dev(void);
/**
* Checks if a given SCSI disk can become a boot device
*
* To fully understand the rules and intricacies of how it is used in context you should read the file comment for the
* native SATA DOM shim in shim/boot_dev/sata_boot_shim.c
*
* @param boot_dev_config User-controllable configuration with a threshold for considering an SCSI disk a boot device
* @param sdp SCSI device which ideally should be an SCSI disk (as passing any other ones doesn't make sense)
*/
bool scsi_is_boot_dev_target(const struct boot_media *boot_dev_config, struct scsi_device *sdp);
#endif //REDPILL_BOOT_SHIM_BASE_H

View File

@ -0,0 +1,317 @@
/**
* A crazy attempt to use SATA disks as proper boot devices on systems without SATA DOM support
*
* BACKGROUND
* The syno-modifed SCSI driver (sd.c) contains support for so-called boot disks. It is a logical designation for drives
* separated from normal data disks. Normally that designation is based on vendor & model of the drive. The native SATA
* boot shim uses that fact to modify user-supplied drive to match that vendor-model and be considered bootable.
* Likewise similar mechanism exists for USB boot media. Both are completely separate and work totally differently.
* While both USB storage and SATA are SCSI-based systems they different in the ways devices are identified and pretty
* much in almost everything else except the protocol.
*
*
* HOW DOES IT WORK?
* This shim performs a nearly surgical task of grabbing a SATA disk (similarly to native SATA boot shim) and modifying
* its descriptors to look like a USB drive for a short while. The descriptors cannot be left in such state for too
* long, and have to be reverted as soon as the disk type is determined by the "sd.c" driver. This is because other
* processes actually need to read & probe the drive as a SATA one (as you cannot communicate with a SATA device like
* you do with a USB stick).
* In a birds-eye view the descriptors are modified just before the sd_probe() is called and removed when ida_pre_get()
* is called by the sd_probe(). The ida_pre_get() is nearly guaranteed [even if the sd.c code changes] to be called
* very early in the process as the ID allocation needs to be done for anything else to use structures created within.
*
*
* HERE BE DRAGONS
* This code is highly experimental and may explode at any moment. We previously thought we cannot do anything with
* SATA boot due to lack of kernel support for it (and userland method being broken now). This crazy idea actually
* worked and after many tests on multiple platforms it seems to be stable. However, we advise against using it if USB
* is an option. Code here has many safety cheks and safeguards but we will never consider it bullet-proof.
*
*
* References:
* - https://www.kernel.org/doc/html/latest/core-api/idr.html (IDs assignment in the kernel)
* - drivers/scsi/sd.c in syno kernel GPL sources (look at sd_probe() and syno_disk_type_get())
*/
#include "fake_sata_boot_shim.h"
#include "boot_shim_base.h" //set_shimmed_boot_dev(), get_shimmed_boot_dev(), scsi_is_shim_target(), usb_shim_as_boot_dev()
#include "../shim_base.h" //shim_*
#include "../../common.h"
#include "../../internal/scsi/scsi_toolbox.h" //scsi_force_replug()
#include "../../internal/scsi/scsi_notifier.h" //waiting for the drive to appear
#include "../../internal/call_protected.h" //ida_pre_get()
#include "../../internal/override/override_symbol.h" //overriding ida_pre_get()
#include <scsi/scsi_device.h> //struct scsi_device
#include <scsi/scsi_host.h> //struct Scsi_Host, SYNO_PORT_TYPE_*
#include <linux/usb.h> //struct usb_device
#include <../drivers/usb/storage/usb.h> //struct us_data
#define SHIM_NAME "fake SATA boot device"
static const struct boot_media *boot_dev_config = NULL; //passed to scsi_is_shim_target() & usb_shim_as_boot_dev()
static struct scsi_device *camouflaged_sdp = NULL; //set when ANY device is under camouflage
static struct usb_device *fake_usbd = NULL; //ptr to our fake usb device scaffolding
static int org_port_type = 0; //original port type of the device which registered
static override_symbol_inst *ida_pre_get_ovs = NULL; //trap override
static unsigned long irq_flags = 0; //saved flags when IRQs are disabled (to prevent rescheduling)
//They call each other, see their own docblocks
static int camouflage_device(struct scsi_device *sdp);
static int uncamouflage_device(struct scsi_device *sdp);
struct ida;
/**
* Called by the sd_probe() very early after disk type is determined. We can restore the disk to its original shape
*/
static int ida_pre_get_trap(struct ida *ida, gfp_t gfp_mask)
{
//This can happen if the kernel decides to reschedule and/or some device appears JUST between setting up the trap
// and disabling of rescheduling. We cannot reverse the order as setting up the trap requires flushing CPU caches
// which isn't really feasible in non-preempt & IRQ-disabled state... a catch-22
//It is also possible that it happens during uncamouflage_device - this is why we force-restore here and just call
// it.
if (unlikely(!camouflaged_sdp)) {
pr_loc_bug("Hit ida_pr_get() trap without sdp saved - removing trap and calling original");
restore_symbol(ida_pre_get_ovs);
return _ida_pre_get(ida, gfp_mask);
}
pr_loc_dbg("Hit ida_pre_get() trap! Removing camouflage...");
uncamouflage_device(camouflaged_sdp);
pr_loc_dbg("Calling original ida_pre_get()");
return _ida_pre_get(ida, gfp_mask);
}
/**
* Checks if the device passes is "camouflaged" as a USB device
*/
static bool is_camouflaged(struct scsi_device *sdp)
{
return likely(camouflaged_sdp) && camouflaged_sdp == sdp;
}
/**
* Alters a SATA device to look like a USB boot disk
*
* Order of operations in camouflage/uncamouflage is VERY particular. We make sure we CANNOT fail (at least not without
* a KP resulting from pagefault) once we disable preemption & irqs AND that no changes before preemption is disabled
* are overriding anything external (as we can be rescheduled and we cannot leave stuff half-replaced)
*
* @param sdp A valid SATA disk (it's assumed it passed through scsi_is_boot_dev_target() already) to disguise as USB
*
* @return 0 on success, -E on error
*/
static int camouflage_device(struct scsi_device *sdp)
{
//This is very serious - it means something went TERRIBLY wrong. The camouflage should last only through the
// duration of probing. If we got here again before camouflaging it means there's a device floating around which
// is a SATA device but with broken USB descriptors. This should never ever happen as it may lead to data loss and
// crashes at best.
if (unlikely(camouflaged_sdp)) {
pr_loc_crt("Attempting to camouflage when another device is undergoing camouflage");
return -EEXIST;
}
//Here's the kicker: most of the subsystems save a pointer to some driver-related data into sdp->host->hostdata.
// Unfortunately usb-storage saves a whole us_data structure there. It can do that as it allows them to use some
// neat container_of() tricks later on. However, it means that we must fake that arrangement. This means we have to
// practically go over the boundaries of the struct memory passed as ->pusb_dev is simply +40 bytes over the struct
// (+ 8 bytes to save the ptr). USUALLY it should be safe as there's spare empty space due to memory fragmentation.
// Since we're doing this only for a short moment it shouldn't be a problem but we are making sure here the memory
// is indeed empty where we want to make a change. There's no guarantees that we don't damage anything but with all
// the safeguards here the chance is minimal.
if (unlikely(host_to_us(sdp->host)->pusb_dev)) {
pr_loc_crt("Cannot camouflage - space on pointer not empty");
return -EINVAL;
}
if (unlikely(get_shimmed_boot_dev())) {
pr_loc_wrn("Refusing to camouflage. Boot device was already shimmed but a new matching device appeared again - "
"this may produce unpredictable outcomes! Ignoring - check your hardware");
return -EEXIST;
}
pr_loc_dbg("Camouflaging SATA disk vendor=\"%s\" model=\"%s\" to look like a USB boot device", sdp->vendor,
sdp->model);
pr_loc_dbg("Generating fake USB descriptor");
kzalloc_or_exit_int(fake_usbd, sizeof(struct usb_device));
usb_shim_as_boot_dev(boot_dev_config, fake_usbd);
pr_loc_dbg("Setting-up ida_pre_get() trap");
ida_pre_get_ovs = override_symbol("ida_pre_get", ida_pre_get_trap);
if (unlikely(IS_ERR(ida_pre_get_ovs))) {
pr_loc_err("Failed to override ida_pre_get - error=%ld", PTR_ERR(ida_pre_get_ovs));
ida_pre_get_ovs = NULL;
kfree(fake_usbd);
return PTR_ERR(ida_pre_get_ovs);
}
pr_loc_dbg("Disabling rescheduling");
preempt_disable();
local_irq_save(irq_flags);
pr_loc_dbg("Changing port type %d => %d", sdp->host->hostt->syno_port_type, SYNO_PORT_TYPE_USB);
org_port_type = sdp->host->hostt->syno_port_type;
sdp->host->hostt->syno_port_type = SYNO_PORT_TYPE_USB;
pr_loc_dbg("Faking ptr to usb_device at %p", &host_to_us(sdp->host)->pusb_dev);
host_to_us(sdp->host)->pusb_dev = fake_usbd;
camouflaged_sdp = sdp;
set_shimmed_boot_dev(sdp);
return 0;
}
/**
* Undoes what camouflage_device() does; i.e. restores device to its normal SATA-view
*
* @param sdp Previously camouflaged device
*
* @return 0 on success, -E on error
*/
static int uncamouflage_device(struct scsi_device *sdp)
{
int out = 0;
pr_loc_dbg("Uncamouflaging SATA disk vendor=\"%s\" model=\"%s\"", sdp->vendor, sdp->model);
if (unlikely(host_to_us(sdp->host)->pusb_dev != fake_usbd)) {
pr_loc_bug("Fake USB device in the scsi_device is not the same as our fake one - something changed it");
return -EINVAL;
}
camouflaged_sdp = NULL;
pr_loc_dbg("Removing fake usb_device ptr at %p", &host_to_us(sdp->host)->pusb_dev);
host_to_us(sdp->host)->pusb_dev = NULL;
pr_loc_dbg("Restoring port type %d => %d", sdp->host->hostt->syno_port_type, org_port_type);
sdp->host->hostt->syno_port_type = org_port_type;
org_port_type = 0;
pr_loc_dbg("Re-enabling scheduling");
local_irq_restore(irq_flags);
preempt_enable();
if (likely(ida_pre_get_ovs)) { //scheduling race condition may have removed that already in ida_pre_get_trap()
pr_loc_dbg("Removing ida_pre_get() trap");
if ((out = restore_symbol(ida_pre_get_ovs)) != 0)
pr_loc_err ("Failed to restore original ida_pre_get() - error=%d", out);
ida_pre_get_ovs = NULL;
}
pr_loc_dbg("Cleaning fake USB descriptor");
kfree(fake_usbd);
fake_usbd = NULL;
return out;
}
/**
* Called for every existing SCSI disk to determine if any of them is a candidate to be a boot device.
*
* If a given device is a SATA drive which matches shim criteria it will be unplugged & replugged to be shimmed.
*
* @param sdp This "struct device" should already be guaranteed to be an scsi_device with type=TYPE_DISK (i.e. returning
* "true" from is_scsi_disk()). It will be re-checked anyway but there's no point in passing anything which
* is not a SCSI disk.
*
* @return 0 means "continue calling me" while any other value means "I found what I was looking for, stop calling me".
* This convention is based on how bus_for_each_dev() works
*/
static int on_existing_scsi_disk_device(struct scsi_device *sdp)
{
if (!scsi_is_boot_dev_target(boot_dev_config, sdp))
return 0;
pr_loc_dbg("Found a shimmable SCSI device - reconnecting to trigger shimming");
scsi_force_replug(sdp);
return 1;
}
/**
* Called for every new (or recently forcefully re-plugged) device to camouflage it as a USB boot disk
*/
static int scsi_disk_probe_handler(struct notifier_block *self, unsigned long state, void *data)
{
struct scsi_device *sdp = data;
switch (state) {
case SCSI_EVT_DEV_PROBING:
if (unlikely(camouflaged_sdp)) {
pr_loc_bug("Got device probe when other one is camouflaged - surprise reschedule happened?");
uncamouflage_device(camouflaged_sdp);
return NOTIFY_OK;
}
if (scsi_is_boot_dev_target(boot_dev_config, data))
camouflage_device(sdp);
return NOTIFY_OK;
case SCSI_EVT_DEV_PROBED_OK:
case SCSI_EVT_DEV_PROBED_ERR:
if (is_camouflaged(sdp)) { //camouflage is expected to be removed by the ida_pre_get() trap
pr_loc_bug("Probing finished but device is still camouflages - something went terribly wrong");
uncamouflage_device(sdp);
}
return NOTIFY_OK;
default:
pr_loc_dbg("Not interesting SCSI EVT %lu - ignoring", state);
return NOTIFY_DONE;
}
}
static struct notifier_block scsi_disk_nb = {
.notifier_call = scsi_disk_probe_handler,
.priority = INT_MIN, //we want to be FIRST so that we other things can get the correct drive type
};
int register_fake_sata_boot_shim(const struct boot_media *config)
{
shim_reg_in();
#ifdef NATIVE_SATA_DOM_SUPPORTED
pr_loc_wrn("This platform supports native SATA DoM - usage of %s is highly discouraged", SHIM_NAME);
#else
pr_loc_inf("This %s is a prototype - if stability is desired use USB boot media instead", SHIM_NAME);
#endif
int out;
boot_dev_config = config;
pr_loc_dbg("Registering for new devices notifications");
out = subscribe_scsi_disk_events(&scsi_disk_nb);
if (unlikely(out != 0)) {
pr_loc_err("Failed to register for SCSI disks notifications - error=%d", out);
boot_dev_config = NULL;
return out;
}
pr_loc_dbg("Iterating over existing devices");
out = for_each_scsi_disk(on_existing_scsi_disk_device);
if (unlikely(out != 0 && out != -ENXIO)) {
pr_loc_err("Failed to enumerate current SCSI disks - error=%d", out);
boot_dev_config = NULL;
return out;
}
shim_reg_ok();
return 0;
}
int unregister_fake_sata_boot_shim(void)
{
shim_ureg_in();
unsubscribe_scsi_disk_events(&scsi_disk_nb);
boot_dev_config = NULL;
shim_ureg_ok();
return 0; //noop
}

View File

@ -0,0 +1,8 @@
#ifndef REDPILL_FAKE_SATA_BOOT_SHIM_H
#define REDPILL_FAKE_SATA_BOOT_SHIM_H
struct boot_media;
int register_fake_sata_boot_shim(const struct boot_media *config);
int unregister_fake_sata_boot_shim(void);
#endif //REDPILL_FAKE_SATA_BOOT_SHIM_H

View File

@ -0,0 +1,285 @@
/**
* Implements shimming SATA device to look like a SATA DOM (Disk-on-Module) device supported by the syno kernel
* If you didn't read the docs for shim/boot_device_shim.c go there and read it first!
*
* HOW THE KERNEL ASSIGNS SYNOBOOT TYPE?
* The determination of what is or isn't the correct synoboot device for SATA is made using vendor and model *names*, as
* standard SCSI/SATA don't have any VID/PID designation like USB or PCI.
* Syno kernel uses different vendor/model names depending on the platform. They are taken from the kernel config option
* pairs CONFIG_SYNO_SATA_DOM_VENDOR/CONFIG_SYNO_SATA_DOM_MODEL and CONFIG_SYNO_SATA_DOM_VENDOR_SECOND_SRC/
* CONFIG_SYNO_SATA_DOM_MODEL_SECOND_SRC. This gives the following supported matrix at the time of writing:
* - vendor-name="SATADOM" and model-name="TYPE D 3SE" (purley only)
* - vendor-name="SATADOM-" and model-name="TYPE D 3SE" (all except purley)
* - vendor-name="SATADOM" and model-name="3SE" (purley only)
* - vendor-name="SATADOM" and model-name="D150SH" (all other)
*
* HOW THIS SHIM MATCHES DEVICE TO SHIM?
* The decision is made based on "struct boot_media" (derived from boot config) passed to the register method. The
* only criterion used is the physical size of the disk. The *first* device which is smaller or equal to
* boot_media->dom_size_mib will be shimmed. If a consecutive device matching this rule appears a warning will be
* triggered.
* This sounds quite unusual. We considered multiple options before going that route:
* - Unlike USB we cannot easily match SATA devices using any stable identifier so any VID/PID was out of the window
* - S/N sounds like a good candidate unless you realize hypervisors use the same one for all disks
* - Vendor/Model names cannot be edited by the user and hypervisors ust the same one for all disks
* - Host/Port location can change (and good luck updating it in the boot params every time)
* - The only stable factor seems to be size
*
* HOW IT WORKS FOR HOT PLUGGED DEVICES?
* While the USB boot shim depends on a race condition (albeit a pretty stable one) there's no way to use the same
* method for SATA, despite both of them using SCSI under the hood. This is because true SCSI/SATA devices are directly
* supported by the drivers/scsi/sd.c which generates no events before the type is determined. Because of this we
* decided to exploit the dynamic nature of Linux drivers subsystem. All drivers register their buses with the kernel
* and are automatically informed by the kernel if something appears on these buses (either during boot or via hot plug)
*
* This module simply asks the kernel drivers subsystem for the driver registered for "sd" (SCSI) devices. Then it
* replaces its trigger function pointer. Normally it points to drivers/scsi/sd.c:sd_probe() which "probes" and configs
* the device. Our sd_probe_shim() first reads the capacity and if criteria are met (see section above) it replaces
* the vendor & model names and passes the control to the real sd_probe(). If nothing matches it transparently calls
* the real sd_probe().
*
* If you're debugging you can test it without restarting the whole SD by removing and re-adding device. For example for
* "sd 6:0:0:0: [sdg] 630784 512-byte logical blocks: (322 MB/308 MiB)" you should do:
* echo 1 > /sys/block/sdg/device/delete # change SDG to the correct device
* echo "0 0 0" > /sys/class/scsi_host/host6/scan # host 6 is the same as "sd 6:..." notation in dmesg
* Warning: rescans and delete hard-yanks the device from controller so DO NOT do this on a disk with important data!
*
* HOW IT WORKS FOR EXISTING DEVICES?
* Unfortunately, our sd_probe() replacement is still a bit of a race condition. However, this time we're racing with
* SCSI driver loading which usually isn't a module. Because of this we need to expect some (probably all) devices to be
* already probed. We need to do essentially what's described above (with /sys) but from kernel space.
* To avoid any crashes and possible data loss we are never touching disks which aren't SATA and matching the size
* match criterion. In other words this shim will NOT yank a data drive from the system.
*
* WHAT IF THIS CODE LOADS BEFORE THE DRIVER?!
* Despite the SCSI driver being one of the first things loaded by the kernel and something which almost everywhere is
* baked into to kernel there's a way to load our module earlier (via ioscheduler). In such case we cannot even use
* driver_find("sd", ...) as it will return NULL (since there's no driver for "sd" *yet*). In such case we can hook
* "scsi_register_driver()" which is an exported symbol (==it will last) and keep it hooked until we find the
* registration of "sd" driver in particular (as SCSI also handles CDROMs, USBs, iSCSI and others)
*
* THE FINAL PICTURE
* Ok, it is pretty complex indeed. Here's the decision tree this submodule goes through:
* register_native_sata_boot_shim()
* => driver_find("sd", ...)
* ===FOUND===
* + shim sd_probe() to sd_probe_shim()
* <will shim vendor/model if appropriate for every newly plugged/re-plugged device>
* + probe_existing_devices()
* <iterate through all, find if any matches, if so disconnect it (which will prompt it to trigger sd_probe)>
* ===NOT FOUND===
* + override scsi_register_driver() [using start_scsi_register_driver_watcher()]
* <it will "wait" until the driver attempts to register>
* ===scsi_register_driver() called & drv->name is "sd"=== [see scsi_register_driver_shim()]
* + modify drv->probe to &sd_probe_shim
* + stop_scsi_register_driver_watcher()
* <we don't want to provide our own implementation of scsi_register_driver()>
* + call [now original] scsi_register_driver()
* <this will trigger sd_probe_shim() when disk is detected and shimming will happen; no need to call
* probe_existing_devices() here as the driver JUST registered>
*
* KNOWN LIMITATIONS
* If you hot-unplug that SATA drive which is used for synoboot it will NOT be shimmed the next time you plug it without
* rebooting. This is because we were lazy and didn't implement the removal shimming (as this behavior isn't defined
* anyway with synoboot devices as they're not user-removable).
*
* This shim is only supported on kernels compiled with CONFIG_SYNO_BOOT_SATA_DOM enabled. Kernels built without that
* option will never check for the vendor/model names and will never be considered SYNOBOOT.
*
* SOURCES
* - Synology's kernel GPL source -> drivers/scsi/sd.c, search for "gSynoBootSATADOM"
* - https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf
*/
#include "native_sata_boot_shim.h"
#include "../../common.h"
#include "../../config/runtime_config.h" //consts, NATIVE_SATA_DOM_SUPPORTED
#ifdef NATIVE_SATA_DOM_SUPPORTED
#include "boot_shim_base.h" //set_shimmed_boot_dev(), get_shimmed_boot_dev(), scsi_is_boot_dev_target()
#include "../shim_base.h" //shim_reg_*(), scsi_ureg_*()
#include "../../internal/call_protected.h" //scsi_scan_host_selected()
#include "../../internal/scsi/scsi_toolbox.h" //scsi_force_replug(), for_each_scsi_disk()
#include "../../internal/scsi/scsi_notifier.h" //watching for new devices to shim them as they appear
#include <scsi/scsi_device.h> //struct scsi_device
#define SHIM_NAME "native SATA DOM boot device"
static const struct boot_media *boot_dev_config = NULL; //passed to scsi_is_shim_target()
//Structure for watching for new devices (via SCSI notifier / scsi_notifier.c event system)
static int on_new_scsi_disk(struct notifier_block *self, unsigned long state, void *data);
static struct notifier_block scsi_disk_nb = {
.notifier_call = on_new_scsi_disk,
.priority = INT_MAX //We want to be LAST, after all other possible fixes has been already applied
};
/********************************************* Actual shimming routines ***********************************************/
/**
* Attempts to shim the device passed
*
* @return 0 if device was successfully shimmed, -E on error
*/
static int shim_device(struct scsi_device *sdp)
{
pr_loc_dbg("Trying to shim SCSI device vendor=\"%s\" model=\"%s\"", sdp->vendor, sdp->model);
if (get_shimmed_boot_dev()) {
pr_loc_wrn("The device should be shimmed but another device has been already shimmed as boot dev."
"Device has been ignored.");
return -EEXIST;
}
pr_loc_dbg("Shimming device to vendor=\"%s\" model=\"%s\"", CONFIG_SYNO_SATA_DOM_VENDOR,
CONFIG_SYNO_SATA_DOM_MODEL);
strcpy((char *)sdp->vendor, CONFIG_SYNO_SATA_DOM_VENDOR);
strcpy((char *)sdp->model, CONFIG_SYNO_SATA_DOM_MODEL);
set_shimmed_boot_dev(sdp);
return 0;
}
/**
* Handles registration of newly plugged SCSI/SATA devices. It's called by the SCSI notifier automatically.
*
* @return NOTIFY_*
*/
static __used int on_new_scsi_disk(struct notifier_block *self, unsigned long state, void *data)
{
if (state != SCSI_EVT_DEV_PROBING)
return NOTIFY_DONE;
struct scsi_device *sdp = data;
pr_loc_dbg("Found new SCSI disk vendor=\"%s\" model=\"%s\": checking boot shim viability", sdp->vendor, sdp->model);
if (!scsi_is_boot_dev_target(boot_dev_config, sdp))
return NOTIFY_OK;
int err = shim_device(data);
if (unlikely(err != 0)) {
//If we let the device register it may be misinterpreted as a normal disk and possibly formatted
pr_loc_err("Shimming process failed with error=%d - "
"preventing the device from appearing in the OS to avoid possible damage", err);
return NOTIFY_BAD;
}
return NOTIFY_OK;
}
/**
* Processes existing device and if it's a SATA drive which matches shim criteria it will be unplugged & replugged to be
* shimmed
*
* @param sdp This "struct device" should already be guaranteed to be an scsi_device with type=TYPE_DISK (i.e. returning
* "true" from is_scsi_disk())
*
* @return 0 means "continue calling me" while any other value means "I found what I was looking for, stop calling me".
* This convention is based on how bus_for_each_dev() works
*/
static int on_existing_scsi_disk(struct scsi_device *sdp)
{
pr_loc_dbg("Found existing SCSI disk vendor=\"%s\" model=\"%s\": checking boot shim viability", sdp->vendor,
sdp->model);
if (!scsi_is_boot_dev_target(boot_dev_config, sdp))
return 0;
//So, now we know it's a shimmable target but we cannot just call shim_device() as this will change vendor+model on
// already connected device, which will change these information but will not trigger syno type change. When we
// disconnect & reconnect the device it will reappear and go through the on_new_scsi_disk() route.
pr_loc_inf("SCSI disk vendor=\"%s\" model=\"%s\" is already connected but it's a boot dev. "
"It will be forcefully reconnected to shim it as boot dev.", sdp->vendor, sdp->model);
int out = scsi_force_replug(sdp);
if (out < 0)
pr_loc_err("Failed to replug the SCSI device (error=%d) - it may not shim as expected", out);
else
pr_loc_dbg("SCSI device replug triggered successfully");
return 1;
}
/****************************************** Standard public API of the shim *******************************************/
static bool shim_registered = false;
int register_native_sata_boot_shim(const struct boot_media *config)
{
shim_reg_in();
//Regardless of the method we must set the expected size (in config) as shim may be called any moment from now on
boot_dev_config = config;
int out = 0;
if (unlikely(boot_dev_config->type != BOOT_MEDIA_SATA_DOM)) {
pr_loc_bug("%s doesn't support device type %d", __FUNCTION__, boot_dev_config->type);
out = -EINVAL;
goto fail;
}
if (unlikely(shim_registered)) {
pr_loc_bug("Native SATA boot shim is already registered");
out = -EEXIST;
goto fail;
}
/* We always set-up watching for new devices, as the SCSI notifier is smart enough to accept new subscribers
* regardless of the driver state, but if the driver is already loaded we also need to take care of existing devs.
* Additionally, subscribing for notifications will, in the future, give us info if a device went away.
*/
out = subscribe_scsi_disk_events(&scsi_disk_nb);
if (unlikely(out != 0)) {
pr_loc_err("Failed to register for SCSI disks notifications - error=%d", out);
goto fail;
}
//This will already check if driver is loaded and only iterate if it is
out = for_each_scsi_disk(on_existing_scsi_disk);
//0 means "call me again" or "success", 1 means "found what I wanted, stop iterating", -ENXIO is "driver not ready"
if (unlikely(out < 0 && out != -ENXIO)) {
pr_loc_dbg("SCSI driver is already loaded but iteration over existing devices failed - error=%d", out);
goto fail_unwatch;
}
shim_registered = true;
shim_reg_ok();
return 0;
fail_unwatch:
unsubscribe_scsi_disk_events(&scsi_disk_nb); //we keep the original code, so this function return code is ignored
fail:
boot_dev_config = NULL;
return out;
}
int unregister_native_sata_boot_shim(void)
{
shim_ureg_in();
if (unlikely(!shim_registered)) {
pr_loc_bug("Native SATA boot shim is not registered");
return -ENOENT;
}
int out = unsubscribe_scsi_disk_events(&scsi_disk_nb);
if (out != 0)
pr_loc_err("Failed to unsubscribe from SCSI events");
//@todo we are consciously NOT doing reset_shimmed_boot_dev(). It may be registered and we're not doing anything to
// unregister it
shim_registered = false;
shim_ureg_ok();
return 0;
}
#else //ifdef NATIVE_SATA_DOM_SUPPORTED
int register_native_sata_boot_shim(const struct boot_media *boot_dev_config)
{
pr_loc_err("Native SATA boot shim cannot be registered in a kernel built without SATA DoM support");
return -ENODEV;
}
int unregister_native_sata_boot_shim(void)
{
pr_loc_err("Native SATA boot shim cannot be unregistered in a kernel built without SATA DoM support");
return -ENODEV;
}
#endif //ifdef else NATIVE_SATA_DOM_SUPPORTED

View File

@ -0,0 +1,8 @@
#ifndef REDPILL_NATIVE_SATA_BOOT_SHIM_H
#define REDPILL_NATIVE_SATA_BOOT_SHIM_H
struct boot_media;
int register_native_sata_boot_shim(const struct boot_media *config);
int unregister_native_sata_boot_shim(void);
#endif //REDPILL_NATIVE_SATA_BOOT_SHIM_H

271
shim/boot_dev/usb_boot_shim.c Executable file
View File

@ -0,0 +1,271 @@
/**
* Implements shimming USB device to look like an embedded USB device supported by the syno kernel
* If you didn't read the docs for shim/boot_device_shim.c go there and read it first!
*
* HOW THE KERNEL ASSIGNS SYNOBOOT TYPE?
* The determination of what is or isn't the correct synoboot device for USBs is made using VID & PID of the device.
* During normal operation both of them need to equal 0xf400 to be considered a boot device. In a special "mfg" mode the
* installation is forced with 0xf401 ids instead.
*
* HOW THIS SHIM MATCHES DEVICE TO SHIM?
* The decision is made based on "struct boot_media" (derived from boot config) passed to the register method:
* - if vid/pid combo is set (i.e. not VID_PID_EMPTY) it must match the newly detected device
* - if vid/pid is not set (i.e. VID_PID_EMPTY) the first device is used (NOT recommended unless you don't use USB)
* - if a second device matching any of the criteria above appears a warning is emitted and device is ignored
*
* HOW IT WORKS?
* In order to dynamically change VID & PID of a USB device we need to modify device descriptor just after the device is
* detected by the USB subsystem. However it has to be done before the device is picked up by the SCSI subsystem, which
* is responsible for creating /dev/xxx entries.
* Since the assumption is that the USB stick is present from boot the sequence of events needs to look like that:
* 0. Kernel starts
* 1. This LKM is loaded
* 2. USB subsystem loads
* 3. Drive is detected
* 4. This LKM changes VID+PID
* 5. SCSI subsystem detects the device and creates a /dev/... node for it
*
* This poses several problems. First this module must load before USB subsystem. Then to get the device quicker than
* SCSI subsystem can a notification receiver is set up. However we need to wait to do this after the usbcore actually
* loads. To make sure it's the case a kernel module watcher is used. That's why symbols from usbcore are loaded
* dynamically, as at the moment of this LKM insertion they aren't available. In case usbcore is loaded before this LKM
* VID+PID change may not be effective (this scenario is supported pretty much for debugging only).
* This sequence is rather time sensitive. It shouldn't fail on any modern multicore system.
*
* References
* - Synology's kernel GPL source -> drivers/scsi/sd.c, search for "IS_SYNO_USBBOOT_ID_"
* - https://0xax.gitbooks.io/linux-insides/content/Concepts/linux-cpu-4.html
* - https://lwn.net/Articles/160501/
*/
#include "usb_boot_shim.h"
#include "boot_shim_base.h" //set_shimmed_boot_dev(), get_shimmed_boot_dev(), usb_shim_as_boot_dev()
#include "../shim_base.h" //shim_*
#include "../../common.h"
#include "../../config/runtime_config.h" //struct boot_device & consts
#include "../../internal/helper/symbol_helper.h" //kernel_has_symbol()
#include "../../internal/call_protected.h" //dynamically calling usb_* functions
#include <linux/notifier.h>
#include <linux/usb.h>
#include <linux/module.h> //struct module
#define SHIM_NAME "USB boot device"
static bool module_notify_registered = false;
static bool device_notify_registered = false;
static const struct boot_media *boot_media = NULL; //passed to usb_shim_as_boot_dev()
/**
* Responds to USB devices being added/removed
*/
static int device_notifier_handler(struct notifier_block *b, unsigned long event, void *data)
{
struct usb_device *device = (struct usb_device*)data;
struct usb_device *prev_device = get_shimmed_boot_dev();
if (event == USB_DEVICE_ADD) {
//TODO: Can we even check if it matched mass storage here... (bInterfaceClass == USB_CLASS_MASS_STORAGE)
if (boot_media->vid == VID_PID_EMPTY || boot_media->pid == VID_PID_EMPTY) {
pr_loc_wrn("Your boot device VID and/or PID is not set - "
"using device found <vid=%04x, pid=%04x> (prev_shimmed=%d)",
device->descriptor.idVendor, device->descriptor.idProduct, prev_device ? 1:0);
} else if (device->descriptor.idVendor != boot_media->vid || device->descriptor.idProduct != boot_media->pid) {
pr_loc_dbg("Found new device <vid=%04x, pid=%04x> - "
"didn't match expected <vid=%04x, pid=%04x> (prev_shimmed=%d)",
device->descriptor.idVendor, device->descriptor.idProduct, boot_media->vid, boot_media->pid,
prev_device ? 1:0);
return NOTIFY_OK;
}
//This will happen especially when VID+PID weren't set and two USB devices were detected
if (prev_device) {
pr_loc_wrn("Boot device was already shimmed but a new matching device appeared again - "
"this may produce unpredictable outcomes! Ignoring - check your hardware");
return NOTIFY_OK;
}
usb_shim_as_boot_dev(boot_media, device);
set_shimmed_boot_dev(device);
pr_loc_inf("Device <vid=%04x, pid=%04x> shimmed to <vid=%04x, pid=%04x>", boot_media->vid, boot_media->pid,
device->descriptor.idVendor, device->descriptor.idProduct);
return NOTIFY_OK;
}
if (prev_device && event == USB_DEVICE_REMOVE && device == prev_device) {
pr_loc_wrn("Previously shimmed boot device gone away");
reset_shimmed_boot_dev();
return NOTIFY_OK;
}
return NOTIFY_OK;
}
static struct notifier_block device_notifier_block = {
.notifier_call = device_notifier_handler,
.priority = INT_MIN, //We need to be first
};
/**
* Watches for USB events
*/
static void register_device_notifier(void)
{
//This should never happen but there's never enough error checking.
//Even if the module was already loaded register_device_notifier() should not be called twice before module is
// unloaded and reloaded
if (unlikely(device_notify_registered)) {
pr_loc_bug("Device notify re-registration via %s w/o module unload (?!)", __FUNCTION__);
return;
}
//This has to use dynamic calling to avoid being dependent on usbcore (since we need to load before usbcore)
_usb_register_notify(&device_notifier_block); //has no return value
device_notify_registered = true;
pr_loc_dbg("Registered USB device notifier");
}
static int unregister_device_notifier(void)
{
if (unlikely(!device_notify_registered)) {
pr_loc_bug("%s called while notifier not registered", __FUNCTION__);
return -ENOENT;
}
//This has to use dynamic calling to avoid being dependent on usbcore (since we need to load before usbcore)
_usb_unregister_notify(&device_notifier_block); //has no return value
device_notify_registered = false;
pr_loc_dbg("Unregistered USB device notifier");
return 0;
}
/**
* Responds to "usbcore" [and others] load
*/
static int ubscore_notifier_handler(struct notifier_block * self, unsigned long state, void * data)
{
struct module *mod = data;
if (strcmp(mod->name, "usbcore") != 0)
return NOTIFY_OK;
if (state == MODULE_STATE_GOING) {
//TODO: call unregister with some force flag?
device_notify_registered = false;
reset_shimmed_boot_dev();
pr_loc_wrn("usbcore module unloaded - this should not happen normally");
return NOTIFY_OK;
}
//This may need to be changed to MODULE_STATE_LIVE if MODULE_STATE_COMING is too early for device notification
if (state != MODULE_STATE_LIVE)
return NOTIFY_OK;
pr_loc_dbg("usbcore registered, adding device watcher");
register_device_notifier();
return NOTIFY_OK;
}
static struct notifier_block usbcore_notifier_block = {
.notifier_call = ubscore_notifier_handler
};
/**
* Watches for "usbcore" module load
*/
static int register_usbcore_notifier(void)
{
int error = 0;
if (unlikely(module_notify_registered)) {
pr_loc_bug("%s called while notifier already registered", __FUNCTION__);
return 0; //technically it's not an error
}
error = register_module_notifier(&usbcore_notifier_block);
if(unlikely(error != 0)) {
pr_loc_err("Failed to register module notifier"); //Currently it's impossible to happen... currently
return error;
}
module_notify_registered = true;
pr_loc_dbg("Registered usbcore module notifier");
//check if usbcore is MAYBE already loaded and give a warning + call register_device_notifier() manually
// this state is FINE for debugging but IS NOT FINE for production use
//We're using kernel_has_symbol() to not acquire module mutex needed for module checks
if (kernel_has_symbol("usb_register_notify")) {
pr_loc_wrn("usbcore module is already loaded (did you load this module too late?) "
"-> registering device notifier right away");
register_device_notifier();
}
return error;
}
static int unregister_usbcore_notifier(void)
{
if (unlikely(!module_notify_registered)) { //unregister should be called first if so
pr_loc_bug("%s called while notifier not registered", __FUNCTION__);
return -ENOENT;
}
int out = unregister_module_notifier(&usbcore_notifier_block);
if(unlikely(out != 0)) {
pr_loc_err("Failed to unregister module notifier"); //Currently it's impossible to happen... currently
return out;
}
module_notify_registered = false;
pr_loc_dbg("Unregistered usbcore module notifier");
return 0;
}
int register_usb_boot_shim(const struct boot_media *boot_dev_config)
{
shim_reg_in();
if (unlikely(boot_dev_config->type != BOOT_MEDIA_USB)) {
pr_loc_bug("%s doesn't support device type %d", __FUNCTION__, boot_dev_config->type);
return -EINVAL;
}
if (unlikely(boot_media)) {
pr_loc_bug("USB boot shim is already registered");
return -EEXIST;
}
boot_media = boot_dev_config;
int out = register_usbcore_notifier(); //it will register device notifier when module loads
if (out != 0)
return out;
shim_reg_ok();
return out;
}
int unregister_usb_boot_shim(void)
{
shim_ureg_in();
if (unlikely(!boot_media)) {
pr_loc_bug("USB boot shim is not registered");
return -ENOENT;
}
int out = 0;
if (
(out = unregister_usbcore_notifier()) != 0
|| (out = unregister_device_notifier()) != 0
)
return out;
boot_media = NULL;
shim_ureg_ok();
return out;
}

8
shim/boot_dev/usb_boot_shim.h Executable file
View File

@ -0,0 +1,8 @@
#ifndef REDPILL_USB_BOOT_SHIM_H
#define REDPILL_USB_BOOT_SHIM_H
struct boot_media;
int register_usb_boot_shim(const struct boot_media *boot_dev_config);
int unregister_usb_boot_shim(void);
#endif //REDPILL_USB_BOOT_SHIM_H

110
shim/boot_device_shim.c Executable file
View File

@ -0,0 +1,110 @@
/**
* Boot device shim ensures that DSM assigns a proper /dev/ device to our USB stick or SATA DOM
*
* WHY IS THIS NEEDED?
* In short the DSM has multiple types of SCSI devices (boot device, USB drive, eSATA drive etc). The boot device is
* always mounted to /dev/synoboot (with respective partitions at /dev/synobootN). The determination what to place there
* is made based on different factors depending on the type of device used to boot (see drivers/scsi/sd.c):
* 1) Standard USB stick
* - it has to be connected via a real USB port (i.e. not a fake-usb-over-sata like ESXi tries to do)
* - it must have VID/PID combo of 0xf400/0xf400
* - allows for normal boot when OS is installed, or triggers OS install/repair screen
* 2) Force-install USB stick
* - it has to be connected via a real USB port
* - it must have VID/PID combo of 0xf401/0xf401
* - always triggers OS install/repair screen
* 3) SATA DOM (Disk-on-Module)
* - kernel is compiled with SATA DOM support (NATIVE_SATA_DOM_SUPPORTED => CONFIG_SYNO_BOOT_SATA_DOM)
* - is a real SATA (i.e. not SCSI/iSCSI/VirtIO) device
* - has platform dependent vendor/model strings of CONFIG_SYNO_SATA_DOM_VENDOR/CONFIG_SYNO_SATA_DOM_MODEL
* - has platform dependent vendor/model strings of CONFIG_SYNO_SATA_DOM_VENDOR_SECOND_SRC/CONFIG_SYNO_SATA_DOM_MODEL_SECOND_SRC
* - SATA DOM *cannot* be used to force-reinstall (as there isn't an equivalent of USB's VID/PID of 0xf401/0xf401)
* - restrictions of native SATA-DOM are lifted by sata_port_shim.c and fake_sata_boot_shim.c
*
* There are other special ones (e.g. iSCSI) which aren't supported here. These only apply to small subset of platforms.
*
* HOW IT WORKS?
* Depending on the runtime configuration this shim will either engage USB-based shim or SATA-based one. See respective
* implementations in shim/boot_dev/.
*
* References:
* - See drivers/scsi/sd.c in Linux sources (especially sd_probe() method)
*/
#define SHIM_NAME "boot device router"
#include "boot_device_shim.h"
#include "shim_base.h"
#include "../common.h"
#include "../config/runtime_config.h"
#include "boot_dev/usb_boot_shim.h"
#include "boot_dev/fake_sata_boot_shim.h"
#include "boot_dev/native_sata_boot_shim.h"
#define BOOT_MEDIA_SHIM_NULL (-1)
static int registered_type = BOOT_MEDIA_SHIM_NULL;
int register_boot_shim(const struct boot_media *boot_dev_config)
{
shim_reg_in();
if (unlikely(registered_type != BOOT_MEDIA_SHIM_NULL)) {
pr_loc_bug("Boot shim is already registered with type=%d", registered_type);
return -EEXIST;
}
int out;
switch (boot_dev_config->type) {
case BOOT_MEDIA_USB:
out = register_usb_boot_shim(boot_dev_config);
break;
case BOOT_MEDIA_SATA_DOM:
out = register_native_sata_boot_shim(boot_dev_config);
break;
case BOOT_MEDIA_SATA_DISK:
out = register_fake_sata_boot_shim(boot_dev_config);
break;
default:
pr_loc_bug("Failed to %s - unknown type=%d", __FUNCTION__, boot_dev_config->type);
return -EINVAL;
}
if (out != 0)
return out; //individual shims should print what went wrong
registered_type = boot_dev_config->type;
shim_reg_ok();
return 0;
}
int unregister_boot_shim(void)
{
shim_ureg_in();
int out;
switch (registered_type) {
case BOOT_MEDIA_USB:
out = unregister_usb_boot_shim();
break;
case BOOT_MEDIA_SATA_DOM:
out = unregister_native_sata_boot_shim();
break;
case BOOT_MEDIA_SATA_DISK:
out = unregister_fake_sata_boot_shim();
break;
case BOOT_MEDIA_SHIM_NULL:
pr_loc_bug("Boot shim is no registered");
return -ENOENT;
default: //that cannot happen unless register_boot_shim() is broken
pr_loc_bug("Failed to %s - unknown type=%d", __FUNCTION__, registered_type);
return -EINVAL;
}
if (out != 0)
return out; //individual shims should print what went wrong
registered_type = BOOT_MEDIA_SHIM_NULL;
shim_ureg_ok();
return 0;
}

8
shim/boot_device_shim.h Executable file
View File

@ -0,0 +1,8 @@
#ifndef REDPILLLKM_BOOT_DEVICE_SHIM_H
#define REDPILLLKM_BOOT_DEVICE_SHIM_H
struct boot_media;
int register_boot_shim(const struct boot_media *boot_dev_config);
int unregister_boot_shim(void);
#endif //REDPILLLKM_BOOT_DEVICE_SHIM_H

39
shim/disable_exectutables.c Executable file
View File

@ -0,0 +1,39 @@
#define SHIM_NAME "common executables disabler"
#include "disable_exectutables.h"
#include "shim_base.h"
#include "../common.h"
#include "../internal/intercept_execve.h"
#define PSTORE_PATH "/usr/syno/bin/syno_pstore_collect"
#define BOOTLOADER_UPDATE1_PATH "uboot_do_upd.sh"
#define BOOTLOADER_UPDATE2_PATH "./uboot_do_upd.sh"
#define SAS_FW_UPDATE_PATH "/tmpData/upd@te/sas_fw_upgrade_tool"
#define OOB_FW_UPDATE_PATH "/usr/syno/sbin/syno_oob_fw_upgrade"
int register_disable_executables_shim(void)
{
shim_reg_in();
int out;
if (
(out = add_blocked_execve_filename(BOOTLOADER_UPDATE1_PATH)) != 0
|| (out = add_blocked_execve_filename(BOOTLOADER_UPDATE2_PATH)) != 0
|| (out = add_blocked_execve_filename(PSTORE_PATH)) != 0
|| (out = add_blocked_execve_filename(SAS_FW_UPDATE_PATH)) != 0
|| (out = add_blocked_execve_filename(OOB_FW_UPDATE_PATH)) != 0
) {
pr_loc_bug("Failed to disable some executables");
return out;
}
shim_reg_ok();
return 0;
}
int unregister_disable_executables_shim(void)
{
//noop - execve entries will be cleared in one sweep during unregister of interceptor (it's much faster this way)
//this function is kept for consistency
return 0;
}

7
shim/disable_exectutables.h Executable file
View File

@ -0,0 +1,7 @@
#ifndef REDPILL_DISABLE_EXECTUTABLES_H
#define REDPILL_DISABLE_EXECTUTABLES_H
int register_disable_executables_shim(void);
int unregister_disable_executables_shim(void);
#endif //REDPILL_DISABLE_EXECTUTABLES_H

243
shim/pci_shim.c Executable file
View File

@ -0,0 +1,243 @@
#define SHIM_NAME "PCI devices"
#include "pci_shim.h"
#include "shim_base.h"
#include "../common.h"
#include "../config/vpci_types.h" //MAX_VPCI_DEVS, pci_shim_device_type
#include "../config/platform_types.h" //hw_config
#include "../internal/virtual_pci.h"
#include <linux/pci_ids.h>
unsigned int free_dev_idx = 0;
static void *devices[MAX_VPCI_DEVS] = { NULL };
static struct pci_dev_descriptor *allocate_vpci_dev_dsc(void) {
if (free_dev_idx >= MAX_VPCI_DEVS) {
/*index has to be at max MAX_VPCI_DEVS-1*/
pr_loc_bug("No more device indexes are available (max devs: %d)", MAX_VPCI_DEVS);
return ERR_PTR(-ENOMEM);
}
struct pci_dev_descriptor *dev_dsc;
kmalloc_or_exit_ptr(dev_dsc, sizeof(struct pci_dev_descriptor));
memcpy(dev_dsc, &pci_dev_conf_default_normal_dev, sizeof(struct pci_dev_descriptor));
devices[free_dev_idx++] = dev_dsc;
return dev_dsc;
}
#define allocate_vpci_dev_dsc_var() \
struct pci_dev_descriptor *dev_dsc = allocate_vpci_dev_dsc(); \
if (IS_ERR(dev_dsc)) return PTR_ERR(dev_dsc);
static int
add_vdev(struct pci_dev_descriptor *dev_dsc, unsigned char bus_no, unsigned char dev_no, unsigned char fn_no,
bool is_mf)
{
const struct virtual_device *vpci_vdev;
if (is_mf) {
vpci_vdev = vpci_add_multifunction_device(bus_no, dev_no, fn_no, dev_dsc);
} else if(unlikely(fn_no != 0x00)) {
//Making such config will either cause the device to not show up at all or only fn_no=0 one will show u
pr_loc_bug("%s called with non-MF device but non-zero fn_no", __FUNCTION__);
return -EINVAL;
} else {
vpci_vdev = vpci_add_single_device(bus_no, dev_no, dev_dsc);
}
return IS_ERR(vpci_vdev) ? PTR_ERR(vpci_vdev) : 0;
}
/**
* Adds a fake Marvell controller
*
* These errors in kernlog are normal (as we don't emulate the behavior of the controller as it's not needed):
* pci 0001:0a:00.0: Can't map mv9235 registers
* ahci: probe of 0001:0a:00.0 failed with error -22
*
* @return 0 on success or -E
*/
static inline int
vdev_add_generic_marvell_ahci(u16 dev, unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf)
{
allocate_vpci_dev_dsc_var();
dev_dsc->vid = PCI_VENDOR_ID_MARVELL_EXT;
dev_dsc->dev = dev;
dev_dsc->rev_id = 0x11; //All Marvells so far use revision 11
dev_dsc->class = U24_CLASS_TO_U8_CLASS(PCI_CLASS_STORAGE_SATA_AHCI);
dev_dsc->subclass = U24_CLASS_TO_U8_SUBCLASS(PCI_CLASS_STORAGE_SATA_AHCI);
dev_dsc->prog_if = U24_CLASS_TO_U8_PROGIF(PCI_CLASS_STORAGE_SATA_AHCI);
return add_vdev(dev_dsc, bus_no, dev_no, fn_no, is_mf);
}
static int vdev_add_MARVELL_88SE9235(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf)
{
return vdev_add_generic_marvell_ahci(0x9235, bus_no, dev_no, fn_no, is_mf);
}
static int vdev_add_MARVELL_88SE9215(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf)
{
return vdev_add_generic_marvell_ahci(0x9215, bus_no, dev_no, fn_no, is_mf);
}
static int vdev_add_INTEL_I211(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf)
{
allocate_vpci_dev_dsc_var();
dev_dsc->vid = PCI_VENDOR_ID_INTEL;
dev_dsc->dev = 0x1539;
dev_dsc->rev_id = 0x03; //Not confirmed
dev_dsc->class = U16_CLASS_TO_U8_CLASS(PCI_CLASS_NETWORK_ETHERNET);
dev_dsc->subclass = U16_CLASS_TO_U8_SUBCLASS(PCI_CLASS_NETWORK_ETHERNET);
return add_vdev(dev_dsc, bus_no, dev_no, fn_no, is_mf);
}
static int vdev_add_INTEL_X552(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf)
{
allocate_vpci_dev_dsc_var();
dev_dsc->vid = PCI_VENDOR_ID_INTEL;
dev_dsc->dev = 0x15ad;
dev_dsc->rev_id = 0x03; //Not confirmed
dev_dsc->class = U16_CLASS_TO_U8_CLASS(PCI_CLASS_NETWORK_ETHERNET);
dev_dsc->subclass = U16_CLASS_TO_U8_SUBCLASS(PCI_CLASS_NETWORK_ETHERNET);
return add_vdev(dev_dsc, bus_no, dev_no, fn_no, is_mf);
}
static int vdev_add_INTEL_CPU_AHCI_CTRL(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf)
{
allocate_vpci_dev_dsc_var();
dev_dsc->vid = PCI_VENDOR_ID_INTEL;
dev_dsc->dev = 0x5ae3;
dev_dsc->class = U24_CLASS_TO_U8_CLASS(PCI_CLASS_STORAGE_SATA_AHCI);
dev_dsc->subclass = U24_CLASS_TO_U8_SUBCLASS(PCI_CLASS_STORAGE_SATA_AHCI);
dev_dsc->prog_if = U24_CLASS_TO_U8_PROGIF(PCI_CLASS_STORAGE_SATA_AHCI);
return add_vdev(dev_dsc, bus_no, dev_no, fn_no, is_mf);
}
//This technically should be a bridge but we don't have the info to recreate full tree
static inline int
vdev_add_generic_intel_pcie(u16 dev, unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf) {
allocate_vpci_dev_dsc_var();
dev_dsc->vid = PCI_VENDOR_ID_INTEL;
dev_dsc->dev = dev;
dev_dsc->class = U16_CLASS_TO_U8_CLASS(PCI_CLASS_BRIDGE_PCI);
dev_dsc->subclass = U16_CLASS_TO_U8_SUBCLASS(PCI_CLASS_BRIDGE_PCI);
return add_vdev(dev_dsc, bus_no, dev_no, fn_no, is_mf);
}
static int vdev_add_INTEL_CPU_PCIE_PA(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf)
{
return vdev_add_generic_intel_pcie(0x5ad8, bus_no, dev_no, fn_no, is_mf);
}
static int vdev_add_INTEL_CPU_PCIE_PB(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf)
{
return vdev_add_generic_intel_pcie(0x5ad6, bus_no, dev_no, fn_no, is_mf);
}
static int vdev_add_INTEL_CPU_USB_XHCI(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf)
{
allocate_vpci_dev_dsc_var();
dev_dsc->vid = PCI_VENDOR_ID_INTEL;
dev_dsc->dev = 0x5aa8;
dev_dsc->class = U24_CLASS_TO_U8_CLASS(PCI_CLASS_SERIAL_USB_XHCI);
dev_dsc->subclass = U24_CLASS_TO_U8_SUBCLASS(PCI_CLASS_SERIAL_USB_XHCI);
dev_dsc->prog_if = U24_CLASS_TO_U8_PROGIF(PCI_CLASS_SERIAL_USB_XHCI);
return add_vdev(dev_dsc, bus_no, dev_no, fn_no, is_mf);
}
static inline int
vdev_add_generic_intel_io(u16 dev, unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf)
{
allocate_vpci_dev_dsc_var();
dev_dsc->vid = PCI_VENDOR_ID_INTEL;
dev_dsc->dev = dev;
dev_dsc->class = U16_CLASS_TO_U8_CLASS(PCI_CLASS_SP_OTHER);
dev_dsc->subclass = U16_CLASS_TO_U8_SUBCLASS(PCI_CLASS_SP_OTHER);
return add_vdev(dev_dsc, bus_no, dev_no, fn_no, is_mf);
}
static int vdev_add_INTEL_CPU_I2C(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf)
{
return vdev_add_generic_intel_io(0x5aac, bus_no, dev_no, fn_no, is_mf);
}
static int vdev_add_INTEL_CPU_HSUART(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf)
{
return vdev_add_generic_intel_io(0x5abc, bus_no, dev_no, fn_no, is_mf);
}
static int vdev_add_INTEL_CPU_SPI(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf)
{
return vdev_add_generic_intel_io(0x5ac6, bus_no, dev_no, fn_no, is_mf);
}
static int vdev_add_INTEL_CPU_SMBUS(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf)
{
allocate_vpci_dev_dsc_var();
dev_dsc->vid = PCI_VENDOR_ID_INTEL;
dev_dsc->dev = 0x5ad4;
dev_dsc->class = U16_CLASS_TO_U8_CLASS(PCI_CLASS_SERIAL_SMBUS);
dev_dsc->subclass = U16_CLASS_TO_U8_SUBCLASS(PCI_CLASS_SERIAL_SMBUS);
return add_vdev(dev_dsc, bus_no, dev_no, fn_no, is_mf);
}
static int (*dev_type_handler_map[])(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf) = {
[VPD_MARVELL_88SE9235] = vdev_add_MARVELL_88SE9235,
[VPD_MARVELL_88SE9215] = vdev_add_MARVELL_88SE9215,
[VPD_INTEL_I211] = vdev_add_INTEL_I211,
[VPD_INTEL_X552] = vdev_add_INTEL_X552,
[VPD_INTEL_CPU_AHCI_CTRL] = vdev_add_INTEL_CPU_AHCI_CTRL,
[VPD_INTEL_CPU_PCIE_PA] = vdev_add_INTEL_CPU_PCIE_PA,
[VPD_INTEL_CPU_PCIE_PB] = vdev_add_INTEL_CPU_PCIE_PB,
[VPD_INTEL_CPU_USB_XHCI] = vdev_add_INTEL_CPU_USB_XHCI,
[VPD_INTEL_CPU_I2C] = vdev_add_INTEL_CPU_I2C,
[VPD_INTEL_CPU_HSUART] = vdev_add_INTEL_CPU_HSUART,
[VPD_INTEL_CPU_SPI] = vdev_add_INTEL_CPU_SPI,
[VPD_INTEL_CPU_SMBUS] = vdev_add_INTEL_CPU_SMBUS,
};
int register_pci_shim(const struct hw_config *hw)
{
shim_reg_in();
pr_loc_dbg("Creating vPCI devices for %s", hw->name);
int out;
for (int i = 0; i < MAX_VPCI_DEVS; i++) {
if (hw->pci_stubs[i].type == __VPD_TERMINATOR__)
break;
pr_loc_dbg("Calling %ps with B:D:F=%02x:%02x:%02x mf=%d", dev_type_handler_map[hw->pci_stubs[i].type],
hw->pci_stubs[i].bus, hw->pci_stubs[i].dev, hw->pci_stubs[i].fn,
hw->pci_stubs[i].multifunction ? 1 : 0);
out = dev_type_handler_map[hw->pci_stubs[i].type](hw->pci_stubs[i].bus, hw->pci_stubs[i].dev,
hw->pci_stubs[i].fn, hw->pci_stubs[i].multifunction);
if (out != 0) {
pr_loc_err("Failed to create vPCI device B:D:F=%02x:%02x:%02x - error=%d", hw->pci_stubs[i].bus,
hw->pci_stubs[i].dev, hw->pci_stubs[i].fn, out);
return out;
}
pr_loc_dbg("vPCI device %d created successfully", i+1);
}
shim_reg_ok();
return 0;
}
int unregister_pci_shim(void)
{
shim_ureg_in();
vpci_remove_all_devices_and_buses();
for (int i = 0; i < free_dev_idx; i++) {
pr_loc_dbg("Free PCI dev %d @ %p", i, devices[i]);
kfree(devices[i]);
}
shim_ureg_ok();
return -EIO; //vpci_remove_all_devices_and_buses has a bug - this is a canary to not forget
}

24
shim/pci_shim.h Executable file
View File

@ -0,0 +1,24 @@
#ifndef REDPILL_PCI_SHIM_H
#define REDPILL_PCI_SHIM_H
enum pci_shim_device_type {
__VPD_TERMINATOR__,
VPD_MARVELL_88SE9235, //1b4b:9235
VPD_MARVELL_88SE9215, //1b4b:9215
VPD_INTEL_I211, //8086:1539
VPD_INTEL_X552, //8086:15ad
VPD_INTEL_CPU_AHCI_CTRL, //8086:5ae3
VPD_INTEL_CPU_PCIE_PA, //8086:5ad8
VPD_INTEL_CPU_PCIE_PB, //8086:5ad6
VPD_INTEL_CPU_USB_XHCI, //8086:5aa8
VPD_INTEL_CPU_I2C, //8086:5aac
VPD_INTEL_CPU_HSUART, //8086:5abc
VPD_INTEL_CPU_SPI, //8086:5ac6
VPD_INTEL_CPU_SMBUS, //8086:5ad4
};
typedef struct hw_config hw_config_;
int register_pci_shim(const struct hw_config *hw);
int unregister_pci_shim(void);
#endif //REDPILL_PCI_SHIM_H

387
shim/pmu_shim.c Executable file
View File

@ -0,0 +1,387 @@
#define SHIM_NAME "PMU emulator"
#include "pmu_shim.h"
#include "shim_base.h"
#include "../common.h"
#include "../internal/uart/virtual_uart.h"
#include <linux/kfifo.h> //kfifo_*
#define PMU_TTYS_LINE 1 //so far this is hardcoded by syno, so we doubt it will ever change
#define WORK_BUFFER_LEN VUART_FIFO_LEN
#define to_hex_buf_len(len) ((len)*3+1) //2 chars for each hex + space + NULL terminator
#define HEX_BUFFER_LEN to_hex_buf_len(VUART_FIFO_LEN)
//PMU packets are at minimum 2 bytes long (PMU_CMD_HEAD + 1-3 bytes command + optional data). If this is set to a high
// value (e.g. VUART_FIFO_LEN) in practice commands will only be delivered when the client indicates end-of-transmission)
// which may not be bad...
#define PMU_MIN_PACKET 2
#define PMU_CMD_HEAD 0x2d //every PMU packet is delimited by containing 0x2d (ASCII "-"/dash) as its first character
typedef struct command_definition command_definition;
/**
* A single PMU command and its routing
*/
struct command_definition {
void (*fn) (const command_definition *t, const char *data, u8 data_len);
const u8 length; //commands are realistically 1-3 chars only
const char *name;
} __packed;
/**
* Result for matching of command signature against known list
*/
typedef enum {
PMU_CMD_AMBIGUOUS = -1,
PMU_CMD_NOT_FOUND = 0,
PMU_CMD_FOUND = 1,
} pmu_match_status;
/**
* Default/noop shim for a PMU command. It simply prints the command received.
*/
static void cmd_shim_noop(const command_definition *t, const char *data, u8 data_len)
{
pr_loc_dbg("vPMU received %s using %d bytes - NOOP", t->name, data_len);
}
//@todo when we get the physical PMU emulator we can move this to a separate library so that shim contacts an internal
// routing routine for commands which aren't shimmed here. Then we will add all PMU=>kernel commands as well. Currently
// we only define kernel=>PMU ones as these are the ones we need to listen for.
#define PMU_CMD__MIN_CODE 0x30
#define PMU_CMD__MAX_CODE 0x75
#define single_byte_idx(id) ((id)-PMU_CMD__MIN_CODE)
#define get_single_byte_cmd(id) single_byte_cmds[single_byte_idx(id)] //call it ONLY after has_single_byte_cmd!!!
#define has_single_byte_cmd(id) \
(likely((id) >= PMU_CMD__MIN_CODE) && likely((id) <= PMU_CMD__MAX_CODE) && get_single_byte_cmd(id).length != 0)
#define DEFINE_SINGLE_BYTE_CMD(cnm, fp) [single_byte_idx(PMU_CMD_ ## cnm)] = { .name = #cnm, .length = 1, .fn = fp }
#define PMU_CMD_OUT_HW_POWER_OFF 0x31 //"1"
#define PMU_CMD_OUT_BUZ_SHORT 0x32 //"2"
#define PMU_CMD_OUT_BUZ_LONG 0x33 //"3"
#define PMU_CMD_OUT_PWR_LED_ON 0x34 //"4"
#define PMU_CMD_OUT_PWR_LED_BLINK 0x35 //"5"
#define PMU_CMD_OUT_PWR_LED_OFF 0x36 //"6"
#define PMU_CMD_OUT_STATUS_LED_OFF 0x37 //"7"
#define PMU_CMD_OUT_STATUS_LED_ON_GREEN 0x38 //"8"
#define PMU_CMD_OUT_STATUS_LED_PULSE_GREEN 0x39 //"9"
#define PMU_CMD_OUT_STATUS_LED_ON_ORANGE 0x3A //":"
#define PMU_CMD_OUT_STATUS_LED_PULSE_ORANGE 0x3B //";"
//0x3C unknown (possibly not used)
#define PMU_CMD_OUT_STATUS_LED_PULSE 0x3d //"="
//0x3E-3F unknown (possibly not used)
#define PMU_CMD_OUT_USB_LED_ON 0x40 //"@"
#define PMU_CMD_OUT_USB_LED_PULSE 0x41 //"A"
#define PMU_CMD_OUT_USB_LED_OFF 0x42 //"B"
#define PMU_CMD_OUT_HW_RESET 0x43 //"C"
//0x43-4A unknown
#define PMU_CMD_OUT_10G_LED_ON 0x4a //"J"
#define PMU_CMD_OUT_10G_LED_OFF 0x4b //"K"
//0x4C unknown
#define PMU_CMD_OUT_LED_TOG_PWR_STAT 0x4d //"M", allows for using one led for status and power and toggle between them
//0x4E unknown
#define PMU_CMD_OUT_SWITCH_UP_VER 0x4f //"O"
#define PMU_CMD_OUT_MIR_LED_OFF 0x50 //"P"
//0x51-55 unknown (except 52)
#define PMU_CMD_OUT_GET_UNIQ 0x52 //"P"
#define PMU_CMD_OUT_PWM_CYCLE 0x56 //"V"
#define PMU_CMD_OUT_PWM_HZ 0x57 //"W"
//0x58-59 unknown
//0x60-71 inputs (except 6C)
#define PMU_CMD_OUT_WOL_ON 0x6c //"l"
#define PMU_CMD_OUT_SCHED_UP_OFF 0x72 //"r"
#define PMU_CMD_OUT_SCHED_UP_ON 0x73 //"s"
#define PMU_CMD_OUT_FAN_HEALTH_OFF 0x74 //"t"
#define PMU_CMD_OUT_FAN_HEALTH_ON 0x75 //"u"
static const command_definition single_byte_cmds[single_byte_idx(PMU_CMD__MAX_CODE)+1] = {
DEFINE_SINGLE_BYTE_CMD(OUT_HW_POWER_OFF, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_BUZ_SHORT, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_BUZ_LONG, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_PWR_LED_ON, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_PWR_LED_BLINK, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_PWR_LED_OFF, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_STATUS_LED_OFF, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_STATUS_LED_ON_GREEN, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_STATUS_LED_PULSE_GREEN, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_STATUS_LED_ON_ORANGE, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_STATUS_LED_PULSE_ORANGE, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_STATUS_LED_PULSE, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_USB_LED_ON, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_USB_LED_PULSE, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_USB_LED_OFF, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_HW_RESET, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_10G_LED_ON, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_10G_LED_OFF, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_LED_TOG_PWR_STAT, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_SWITCH_UP_VER, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_MIR_LED_OFF, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_GET_UNIQ, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_PWM_CYCLE, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_PWM_HZ, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_WOL_ON, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_SCHED_UP_OFF, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_SCHED_UP_ON, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_FAN_HEALTH_OFF, cmd_shim_noop),
DEFINE_SINGLE_BYTE_CMD(OUT_FAN_HEALTH_ON, cmd_shim_noop),
};
static char *uart_buffer = NULL; //keeps data streamed directly by the vUART... todo: vUART should manage this buffer
static char *work_buffer = NULL; //collecting & operatint on the data received from vUART
static char *work_buffer_curr = NULL; //pointer to the current free space in work_buffer
static char *hex_print_buffer = NULL; //helper buffer to print char arrays in hex
#define work_buffer_fill() ((unsigned int)(work_buffer_curr - work_buffer))
/**
* Free all buffers used by this submodule
*
* It is safe to call this method without checking buffers state (it has a deliberate protection against double-free)
*/
static void free_buffers(void)
{
if (likely(uart_buffer))
kfree(uart_buffer);
if (likely(work_buffer))
kfree(work_buffer);
if (likely(hex_print_buffer))
kfree(hex_print_buffer);
uart_buffer = NULL;
work_buffer = NULL;
work_buffer_curr = NULL;
hex_print_buffer = NULL;
}
/**
* Allocates space for all buffers used by this submodule
*/
static int alloc_buffers(void)
{
kmalloc_or_exit_int(uart_buffer, VUART_FIFO_LEN);
kmalloc_or_exit_int(work_buffer, WORK_BUFFER_LEN);
kmalloc_or_exit_int(hex_print_buffer, HEX_BUFFER_LEN);
work_buffer_curr = work_buffer;
return 0;
}
/**
* Converts passed char buffer into user-readable hex print of it
*
* @todo this should probably be extracted
*/
static __used const char *get_hex_print(const char *buffer, int len)
{
if (unlikely(len == 0)) {
hex_print_buffer[0] = '\0';
return hex_print_buffer;
} else if (unlikely(to_hex_buf_len(len) > HEX_BUFFER_LEN)) {
pr_loc_bug("Printing %d bytes as hex requires %d bytes in buffer - buffer is %d bytes", len,
to_hex_buf_len(len), HEX_BUFFER_LEN);
hex_print_buffer[0] = '\0';
return hex_print_buffer;
}
int hex_len = 0;
for (int i = 0; i < len; ++i) {
sprintf(&hex_print_buffer[i * 3], "%02x ", buffer[i]);
hex_len += 3;
}
hex_print_buffer[hex_len-1] = '\0';
return hex_print_buffer;
}
/**
* Matches command against a list of known ones based on the signature specified
*
* @param cmd pointer to a pointer where address of command structure can be saved if found
*/
static pmu_match_status noinline
match_command(const command_definition **cmd, const char *signature, const unsigned int sig_len)
{
if (unlikely(sig_len == 0)) {
pr_loc_dbg("Invalid zero-length command (stray head without command signature) - discarding");
return PMU_CMD_NOT_FOUND;
}
if (likely(sig_len == 1) //regular 1 byte
|| (sig_len == 3 && signature[1] == 0x0d && signature[2] == 0x0a) //1 byte with CRLF (sic!)
) {
if (!has_single_byte_cmd(signature[0]))
return PMU_CMD_NOT_FOUND;
*cmd = &get_single_byte_cmd(signature[0]);
return PMU_CMD_FOUND;
}
return PMU_CMD_NOT_FOUND; //@todo Currently we don't handle multibyte commands; it has to be a full iteration
}
/**
* Finds command based on its signature and execute its callback if found
*/
static void route_command(const char *buffer, const unsigned int len)
{
const command_definition *cmd = NULL;
if (match_command(&cmd, buffer, len) != PMU_CMD_FOUND) {
pr_loc_wrn("Unknown %d byte PMU command with signature hex=\"%s\" ascii=\"%.*s\"", len,
get_hex_print(buffer, len), len, buffer);
return;
}
pr_loc_dbg("Executing cmd %s handler %pF", cmd->name, cmd->fn);
cmd->fn(cmd, buffer, len);
}
/**
* Scans work buffer (copied from vUART buffer) to find commands
*
* @param end_of_packet Indicates whether this command was called because the vUART transmitter assumed
* end-of-transmission/IDLE, or flushed due to its buffer being full. If this parameter is true the
* data in the work buffer is assumed to be a complete representation of the state. This becomes
* important if we cannot with 100% confidence say, after reaching the end of the buffer, if it
* will be a multibyte command (but we possibly didn't get all the bytes YET) or this is single or
* multibyte command which we don't know.
*
* This function makes an assumption that when it's called that the buffer represents the whole state of the command
* If this becomes to take too long we can move it to a separate thread, but this will require a lock.
*/
static noinline void process_work_buffer(bool end_of_packet)
{
if (unlikely(work_buffer == work_buffer_curr)) {
//this can happen if kernel sends no data but we get IDLE... shouldn't logically happen
pr_loc_wrn("%s called on empty buffer?!", __FUNCTION__);
return;
}
int cmd_len = -1; //number of bytes in the command (excluding header)
for(char *curr = work_buffer; curr < work_buffer_curr; ++curr) {
if (*curr == PMU_CMD_HEAD) { //got the beginning of a new command
//we've found a new command in the buffer - lets check if the previously collected data matches anything
if (cmd_len != -1) { //we only want to call it if this isn't the first byte after last cmd (or 1st in buf)
route_command(curr-cmd_len, cmd_len);
cmd_len = 0; //we've got the head so 0 and not -1
} else {
++cmd_len; //We've read the buffer containing head, we then expect to get something which is non-head
}
} else {
if (cmd_len == -1) { //we don't expect data before head
pr_loc_wrn("Found garbage data in PMU buffer before cmd head (\"%c\" / 0x%02x) - ignoring", *curr,
*curr);
continue;
}
++cmd_len; //collecting another byte for the currently processed command
}
}
//We've finished processing the buffer. Now we need to decide what to do with that last piece of data
unsigned int processed = work_buffer_fill();
if (cmd_len != -1) { //if it's -1 it means we didn't find any heading so we're just discarding all data
if (end_of_packet) {
route_command(work_buffer_curr-cmd_len, cmd_len);
} else { //if the packed didn't end we need to keep that piece of buffer for the next run
processed -= cmd_len + 1; //we also keep head
}
}
unsigned int left = work_buffer_fill() - processed;
if (likely(left != 0)) {
memmove(work_buffer, work_buffer+processed, left);
}
work_buffer_curr = work_buffer + left;
// pr_loc_dbg("Left buffer %p curr=%p with %d bytes in it (ascii=\"%.*s\" hex={%s})", work_buffer, work_buffer_curr, left, left, work_buffer,
// get_hex_print(work_buffer, left));
}
/**
* Callback passed to vUART. It will be called any time some data is available.
*/
static noinline void pmu_rx_callback(int line, const char *buffer, unsigned int len, vuart_flush_reason reason)
{
pr_loc_dbg("Got %d bytes from PMU: reason=%d hex={%s} ascii=\"%.*s\"", len, reason, get_hex_print(buffer, len), len, buffer);
int buffer_space = WORK_BUFFER_LEN - work_buffer_fill();
if (unlikely(work_buffer_curr + len > work_buffer + WORK_BUFFER_LEN)) { //todo just remove as much as needed from the buffer to fit more data
pr_loc_err("Work buffer is full! Only %d of %d bytes will be copied from receiver", len, buffer_space);
len = buffer_space;
}
memcpy(work_buffer_curr, buffer, len);
work_buffer_curr += len;
// pr_loc_dbg("Copied data to work buffer, now with %d bytes in it (cur=%p)",
// (unsigned int)(work_buffer_curr - work_buffer), work_buffer_curr);
//We only want to analyze the buffer when we are sure we have the full command to process. This is because commands
// are variable length and have no end delimiter not length specified with prefixes of short commands conflicting
// with longer commands (sic!)
//For example, you have "SW1" command which when sent will look like "-SW1" (0x2d 0x53 0x57 0x31). We can capture
// this when VUART_FLUSH_IDLE happens. We can also easily capture this when multiple commands are sent at once
// (unlikely but possible) since it will be something like "-SW1-3". However, we CANNOT distinguish "-S" from
// incomplete "-SW1". So we need to rely on IDLE - if we got "-S" with IDLE it means it was "-S" and not the
// beginning of "-SW1".
//We also forcefully flush on full buffer even if no IDLE was specified, as it's technically possible for the
// software to send a long sequence of commands at once totaling more than our buffer (extremely unlikely).
//Additionally, we only process IDLE-signalled buffers when they have at least a single byte of data as some
// versions of the mfgBIOS attach head AND THEN in a separate packet send the actual commands (sic!)
if (reason == VUART_FLUSH_IDLE && work_buffer_fill() > 1)
process_work_buffer(true);
//our buffer is full [we must process] or vUART buffer was full [we should process]
else if (buffer_space <= len || reason == VUART_FLUSH_FULL)
process_work_buffer(false);
}
int register_pmu_shim(const struct hw_config *hw)
{
shim_reg_in();
int out;
if ((out = vuart_add_device(PMU_TTYS_LINE) != 0)) {
pr_loc_err("Failed to initialize vUART for PMU at ttyS%d", PMU_TTYS_LINE);
return out;
}
if ((out = alloc_buffers()) != 0)
goto error_out;
//We don't set the threshold as some commands are variable length but the "packets" are properly split
if ((out = vuart_set_tx_callback(PMU_TTYS_LINE, pmu_rx_callback, uart_buffer, VUART_THRESHOLD_MAX))) {
pr_loc_err("Failed to register RX callback");
goto error_out;
}
shim_reg_ok();
return 0;
error_out:
free_buffers();
vuart_remove_device(PMU_TTYS_LINE); //this also removes callback (if set)
return out;
}
int unregister_pmu_shim(void)
{
shim_ureg_in();
int out = 0;
if (unlikely(!uart_buffer)) {
pr_loc_bug("Attempted to %s while it's not registered", __FUNCTION__);
return 0; //Technically it succeeded
}
if ((out = vuart_remove_device(PMU_TTYS_LINE)) != 0)
pr_loc_err("Failed to remove vUART for line=%d", PMU_TTYS_LINE);
free_buffers();
shim_ureg_ok();
return out;
}

8
shim/pmu_shim.h Executable file
View File

@ -0,0 +1,8 @@
#ifndef REDPILL_PMU_SHIM_H
#define REDPILL_PMU_SHIM_H
typedef struct hw_config hw_config_;
int register_pmu_shim(const struct hw_config *hw);
int unregister_pmu_shim(void);
#endif //REDPILL_PMU_SHIM_H

Some files were not shown because too many files have changed in this diff Show More