mirror of
https://github.com/AuxXxilium/arc-lkm.git
synced 2024-11-23 14:50:57 +07:00
init: lkm
Signed-off-by: AuxXxilium <info@auxxxilium.tech>
This commit is contained in:
commit
d288da5003
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: AuxXxilium
|
294
.github/workflows/build.yml
vendored
Normal file
294
.github/workflows/build.yml
vendored
Normal 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
63
.gitignore
vendored
Executable 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
783
CMakeLists.txt
Executable 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
674
LICENSE
Executable 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
97
Makefile
Executable 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
10
README.md
Executable 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
128
common.h
Executable 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
95
compat/string_compat.c
Executable 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
11
compat/string_compat.h
Executable 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
|
30
compat/toolkit/drivers/pci/pci.h
Normal file
30
compat/toolkit/drivers/pci/pci.h
Normal 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 */
|
135
compat/toolkit/drivers/usb/storage/usb.h
Executable file
135
compat/toolkit/drivers/usb/storage/usb.h
Executable 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
115
compat/toolkit/fs/proc/internal.h
Executable 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;
|
||||
}
|
0
compat/toolkit/include/.gitkeep
Executable file
0
compat/toolkit/include/.gitkeep
Executable file
455
config/cmdline_delegate.c
Executable file
455
config/cmdline_delegate.c
Executable 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
31
config/cmdline_delegate.h
Executable 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
48
config/cmdline_opts.h
Executable 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
126
config/platform_types.h
Executable 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
1047
config/platforms.h
Executable file
File diff suppressed because it is too large
Load Diff
246
config/runtime_config.c
Executable file
246
config/runtime_config.c
Executable 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
71
config/runtime_config.h
Executable 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
50
config/uart_defs.h
Executable 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
18
config/vpci_types.h
Executable 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
135
debug/debug_execve.c
Executable 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
6
debug/debug_execve.h
Executable 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
77
debug/debug_vuart.h
Executable 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
113
internal/call_protected.c
Executable 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
77
internal/call_protected.h
Executable 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
|
200
internal/helper/ftrace_helper.h
Normal file
200
internal/helper/ftrace_helper.h
Normal 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
16
internal/helper/math_helper.c
Executable 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
29
internal/helper/math_helper.h
Executable 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
44
internal/helper/memory_helper.c
Executable 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
37
internal/helper/memory_helper.h
Executable 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
112
internal/helper/symbol_helper.c
Executable 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
22
internal/helper/symbol_helper.h
Executable 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
|
269
internal/intercept_driver_register.c
Executable file
269
internal/intercept_driver_register.c
Executable 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;
|
||||
}
|
59
internal/intercept_driver_register.h
Executable file
59
internal/intercept_driver_register.h
Executable 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
145
internal/intercept_execve.c
Executable 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
9
internal/intercept_execve.h
Executable 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
26
internal/ioscheduler_fixer.c
Executable 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
6
internal/ioscheduler_fixer.h
Executable 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
13
internal/notifier_base.h
Executable 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
|
307
internal/override/override_symbol.c
Executable file
307
internal/override/override_symbol.c
Executable 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;
|
||||
}
|
133
internal/override/override_symbol.h
Executable file
133
internal/override/override_symbol.h
Executable 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
|
168
internal/override/override_syscall.c
Executable file
168
internal/override/override_syscall.c
Executable 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;
|
||||
}
|
59
internal/override/override_syscall.h
Executable file
59
internal/override/override_syscall.h
Executable 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
350
internal/scsi/hdparam.h
Executable 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
248
internal/scsi/scsi_notifier.c
Executable 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
29
internal/scsi/scsi_notifier.h
Executable 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
|
4
internal/scsi/scsi_notifier_list.c
Executable file
4
internal/scsi/scsi_notifier_list.c
Executable file
@ -0,0 +1,4 @@
|
||||
#include "scsi_notifier_list.h"
|
||||
#include <linux/notifier.h>
|
||||
|
||||
BLOCKING_NOTIFIER_HEAD(rp_scsi_notify_list);
|
27
internal/scsi/scsi_notifier_list.h
Executable file
27
internal/scsi/scsi_notifier_list.h
Executable 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
246
internal/scsi/scsi_toolbox.c
Executable 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
100
internal/scsi/scsi_toolbox.h
Executable 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
24
internal/scsi/scsiparam.h
Executable 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
60
internal/stealth.c
Executable 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
29
internal/stealth.h
Executable 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
|
162
internal/stealth/sanitize_cmdline.c
Executable file
162
internal/stealth/sanitize_cmdline.c
Executable 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;
|
||||
}
|
23
internal/stealth/sanitize_cmdline.h
Executable file
23
internal/stealth/sanitize_cmdline.h
Executable 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
472
internal/uart/uart_swapper.c
Executable 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
18
internal/uart/uart_swapper.h
Executable 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
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
126
internal/uart/virtual_uart.h
Executable 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
73
internal/uart/vuart_internal.h
Executable 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
153
internal/uart/vuart_virtual_irq.c
Executable 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
|
20
internal/uart/vuart_virtual_irq.h
Executable file
20
internal/uart/vuart_virtual_irq.h
Executable 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
575
internal/virtual_pci.c
Executable 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
202
internal/virtual_pci.h
Executable 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
126
redpill_main.c
Executable 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(¤t_config)) != 0 //This MUST be the second entry
|
||||
|| (out = populate_runtime_config(¤t_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(¤t_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(¤t_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(¤t_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
4
redpill_main.h
Executable 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
117
shim/bios/bios_hwcap_shim.c
Executable 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
23
shim/bios/bios_hwcap_shim.h
Executable 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
379
shim/bios/bios_hwmon_shim.c
Executable 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
12
shim/bios/bios_hwmon_shim.h
Executable 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
|
208
shim/bios/bios_psu_status_shim.c
Normal file
208
shim/bios/bios_psu_status_shim.c
Normal 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;
|
||||
}
|
23
shim/bios/bios_psu_status_shim.h
Normal file
23
shim/bios/bios_psu_status_shim.h
Normal 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
347
shim/bios/bios_shims_collection.c
Executable 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;
|
||||
}
|
56
shim/bios/bios_shims_collection.h
Executable file
56
shim/bios/bios_shims_collection.h
Executable 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
283
shim/bios/mfgbios_types.h
Executable 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
270
shim/bios/rtc_proxy.c
Executable 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
42
shim/bios/rtc_proxy.h
Executable 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
411
shim/bios_shim.c
Executable 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
8
shim/bios_shim.h
Executable 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
92
shim/block_fw_update_shim.c
Executable 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
7
shim/block_fw_update_shim.h
Executable 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
70
shim/boot_dev/boot_shim_base.c
Executable 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
57
shim/boot_dev/boot_shim_base.h
Executable 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
|
317
shim/boot_dev/fake_sata_boot_shim.c
Executable file
317
shim/boot_dev/fake_sata_boot_shim.c
Executable 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
|
||||
}
|
8
shim/boot_dev/fake_sata_boot_shim.h
Executable file
8
shim/boot_dev/fake_sata_boot_shim.h
Executable 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
|
285
shim/boot_dev/native_sata_boot_shim.c
Executable file
285
shim/boot_dev/native_sata_boot_shim.c
Executable 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
|
8
shim/boot_dev/native_sata_boot_shim.h
Executable file
8
shim/boot_dev/native_sata_boot_shim.h
Executable 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
271
shim/boot_dev/usb_boot_shim.c
Executable 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
8
shim/boot_dev/usb_boot_shim.h
Executable 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
110
shim/boot_device_shim.c
Executable 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
8
shim/boot_device_shim.h
Executable 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
39
shim/disable_exectutables.c
Executable 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
7
shim/disable_exectutables.h
Executable 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
243
shim/pci_shim.c
Executable 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
24
shim/pci_shim.h
Executable 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
387
shim/pmu_shim.c
Executable 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
8
shim/pmu_shim.h
Executable 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
Loading…
Reference in New Issue
Block a user