├── .ci ├── apply_general_ssh_options.sh ├── prepare_test_stage.sh └── test-on-device.sh ├── .gitignore ├── .gitlab-ci.yml ├── .test-stage └── tests │ └── bbapi-module │ ├── run │ └── tags ├── CHANGES ├── CMakeLists.txt ├── COPYING ├── Lindent ├── Makefile ├── Makefile.FreeBSD ├── README.md ├── TcBaDevDef.h ├── api.c ├── api.h ├── button ├── Makefile └── button.c ├── config_CX01-CX5020.h ├── config_CX03-CX5140.h ├── config_CX05-CX2030.h ├── config_c6015.h ├── config_cx2030_cx2100-0004.h ├── config_cx2030_cx2100-0904.h ├── config_cx5000.h ├── config_cx5130.h ├── display ├── Makefile └── display.c ├── display_example.cpp ├── linuxkpi └── linux │ ├── platform_device.h │ └── version.h ├── power ├── Makefile └── power.c ├── scripts ├── cx2100_demo.sh └── poll_pwrfail.sh ├── sensors_example.cpp ├── simple_cdev.c ├── simple_cdev.h ├── sups ├── Makefile └── sups.c ├── test_config.h ├── tools ├── 10_get_fructose.sh ├── fructose-1.4.0.sha512 └── fructose │ ├── double_compare.h │ ├── fructose.h │ ├── test_base.h │ └── test_root.h ├── unittest.cpp ├── unittest ├── load-module.sh └── test-all.sh └── wdt ├── Makefile └── watchdog.c /.ci/apply_general_ssh_options.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # SPDX-License-Identifier: MIT 3 | # Copyright (C) 2020 Beckhoff Automation GmbH & Co. KG 4 | 5 | set -e 6 | set -u 7 | 8 | mkdir -p "/root/.ssh" 9 | 10 | cat > /root/.ssh/config << EOF 11 | Host * 12 | StrictHostKeyChecking no 13 | UserKnownHostsFile /dev/null 14 | EOF 15 | -------------------------------------------------------------------------------- /.ci/prepare_test_stage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # SPDX-License-Identifier: MIT 3 | # Copyright (C) Beckhoff Automation GmbH & Co. KG 4 | 5 | set -e 6 | set -u 7 | 8 | . "$(shlib.sh get-path)/log.sh" 9 | 10 | script_path="$(cd "$(dirname "${0}")" && pwd)" 11 | readonly script_path 12 | 13 | jitlab shallow-clone test_stage "${BHF_CI_TEST_STAGE_GIT_REF:-master}" 14 | -------------------------------------------------------------------------------- /.ci/test-on-device.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This was copied from https://git.beckhoff.dev/beckhoff/debian-install/-/blob/05d6e9e08d344b3580e3c71bf3e62952fd8b115b/debian-mkrootfs 4 | # We dropped the linux-image-bhf kernel and packages we don't need. Important 5 | # is we need a kernel without bbapi driver compiled in, because here we want 6 | # to test our out of tree module version. 7 | mkrootfs() { 8 | mmdebstrap \ 9 | ${DEBIAN_MKROOTFS_ARCH:+--architectures="${DEBIAN_MKROOTFS_ARCH}"} \ 10 | --variant=minbase \ 11 | --hook-directory=/usr/share/misc/debian-install/mmdebstrap-hooks \ 12 | --include="${default_packages}" \ 13 | --verbose \ 14 | "${DEBIAN_MKROOTFS_SUITE}" \ 15 | "${rootfs}" \ 16 | https://deb-mirror.beckhoff.com/debian 17 | } 18 | 19 | set -e 20 | set -u 21 | set -x 22 | 23 | readonly rackctl_device=test-device 24 | 25 | device_ip="$(rackctl-config get "${rackctl_device}" ip)" 26 | 27 | readonly device_ip 28 | readonly ssh_target="Administrator@${device_ip}" 29 | 30 | eval "$(cleanup init)" 31 | 32 | # We need our mmdebstrap hooks 33 | sudo apt update && sudo apt install debian-install 34 | 35 | # Generate a rootfs with an upstream Debian kernel 36 | export DEBIAN_MKROOTFS_ADMIN_PASSWORD=1 37 | export DEBIAN_MKROOTFS_ADMIN_USER=Administrator 38 | export DEBIAN_MKROOTFS_BHF_DISTRO=bookworm-experimental 39 | export DEBIAN_MKROOTFS_PIPELINE_REPOS="" 40 | export DEBIAN_MKROOTFS_SUITE=bookworm 41 | default_packages="\ 42 | bhfinfo,\ 43 | build-essential,\ 44 | ca-certificates,\ 45 | cmake,\ 46 | curl,\ 47 | linux-headers-rt-amd64,\ 48 | linux-image-rt-amd64,\ 49 | ssh,\ 50 | sudo,\ 51 | systemd,\ 52 | systemd-sysv,\ 53 | zstd" 54 | 55 | # ATTENTION: We can't use /tmp here. NFS root mount would complain about 56 | # missing permissions, so we put our tmpdir in the working directory. 57 | tmpdir="$(pwd)/tmpdir" 58 | "${CLEANUP}/add" "rm -rf '${tmpdir}'" 59 | 60 | # Fake directory structure for rackctl-netboot 61 | readonly rootfs="${tmpdir}/nfs/rootfs" 62 | mkdir -p "${rootfs}" 63 | mkrootfs 64 | 65 | # TODO replae this with a proper tool or proper test-runner container 66 | . 03_init_ssh.sh 67 | 68 | # Netboot an upstream Debian kernel on a device with Beckhoff BIOS 69 | # ln -sf /proc/net/pnp "${rootfs}/etc/resolv.conf" 70 | cat > "${rootfs}/etc/resolv.conf" <<- EOF 71 | domain beckhoff.com 72 | search beckhoff.com 73 | nameserver $(rackctl-config get "${rackctl_device}" rackcontroller/ip) 74 | EOF 75 | 76 | # We need noninteractive sudo for modules install and to access /dev/bbapi 77 | printf 'ALL\tALL = (ALL) NOPASSWD: ALL\n' >> "${rootfs}/etc/sudoers" 78 | 79 | rackctl-netboot \ 80 | --workdir="${tmpdir}" \ 81 | "${rackctl_device}" local & 82 | "${CLEANUP}/add" "neokill $!" 83 | wait_ssh "${ssh_target}" 84 | 85 | ./test_stage/test_stage.sh "$@" --config-dir=./.test-stage --target-os=tclur rackctl "${rackctl_device}" 86 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bin 2 | *.o 3 | *.ko 4 | *.o.cmd 5 | *.ko.cmd 6 | *.mod.c 7 | *.order 8 | *.symvers 9 | *.c~ 10 | *.cpp~ 11 | *.h~ 12 | *_if.h 13 | vnode_if_newproto.h 14 | vnode_if_typedef.h 15 | .tmp_versions/ 16 | tools/fructose/ 17 | tools/fructose-*.tar.gz 18 | build 19 | export_syms 20 | machine 21 | x86 22 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | variables: 2 | TEST_RUNNER_CONTAINER: ${REGISTRY_HOST}/beckhoff/test_stage/test_runner:v4.104 3 | 4 | test-on-device: 5 | image: ${TEST_RUNNER_CONTAINER} 6 | stage: build 7 | script: 8 | - .ci/prepare_test_stage.sh 9 | - .ci/test-on-device.sh 10 | tags: 11 | - bbapi 12 | 13 | .build-example: &build-example 14 | image: ${REGISTRY_HOST}/beckhoff/docker-ubuntu-cached:latest 15 | stage: build 16 | before_script: 17 | - apk add --update alpine-sdk cmake linux-headers 18 | script: 19 | - sed -i "s/define CONFIG_INTERACTIVE 1/define CONFIG_INTERACTIVE 0/" test_config.h 20 | - sed -i.bak "s/\/\*\* select test device \*\//#include \"config_CX01-CX5020.h\"/" test_config.h 21 | - make binaries 22 | - mv ./build/unittest.bin unittest.bin.CX01-CX5020 23 | - mv test_config.h.bak test_config.h 24 | - sed -i.bak "s/\/\*\* select test device \*\//#include \"config_CX03-CX5140.h\"/" test_config.h 25 | - make binaries 26 | - mv ./build/unittest.bin unittest.bin.CX03-CX5140 27 | - mv test_config.h.bak test_config.h 28 | - sed -i.bak "s/\/\*\* select test device \*\//#include \"config_CX05-CX2030.h\"/" test_config.h 29 | - make binaries 30 | - mv ./build/unittest.bin unittest.bin.CX05-CX2030 31 | - mv test_config.h.bak test_config.h 32 | artifacts: 33 | paths: 34 | - ./build/*.bin 35 | - unittest.bin.* 36 | 37 | build-i386-example: 38 | <<: *build-example 39 | image: i386/alpine 40 | 41 | build-x86_64-example: 42 | <<: *build-example 43 | image: alpine 44 | -------------------------------------------------------------------------------- /.test-stage/tests/bbapi-module/run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # SPDX-License-Identifier: MIT 3 | # Copyright (C) Beckhoff Automation GmbH & Co. KG 4 | 5 | set -e 6 | set -u 7 | 8 | readonly test_device="${1?Missing }" 9 | readonly ssh_cmd="ssh Administrator@${test_device}" 10 | 11 | ${ssh_cmd} /bin/sh -$- <<- EOF 12 | # Basic trace message for debugging. 13 | uname -a 14 | 15 | # Suppress sudo warning messages about our hostname. 16 | printf '127.0.0.1\t%s\n' "\$(hostname)" | sudo tee -a /etc/hosts 17 | 18 | # We could copy this from the test runner, but this looks even easier. 19 | curl --url "https://git.beckhoff.dev/api/v4/projects/${CI_PROJECT_ID}/repository/archive?sha=${CI_COMMIT_SHA}" | tar --extract --gunzip 20 | 21 | # Now, build and install our bbapi module on the test device. 22 | cd "${CI_PROJECT_NAME}-${CI_COMMIT_SHA}-${CI_COMMIT_SHA}" 23 | make 24 | make install 25 | 26 | # Basic test if the driver was loaded. 27 | sudo dmesg | grep --ignore-case bbapi 28 | ls -lh /dev/bbapi 29 | 30 | # With BBAPI available, we should now be able to run our example. At 31 | # least a minimal subset of the test cases. 32 | sed -i "s/define CONFIG_INTERACTIVE 1/define CONFIG_INTERACTIVE 0/" test_config.h 33 | sed -i "s/\/\*\* select test device \*\//#include \"config_c6015.h\"/" test_config.h 34 | sed -i "s/#define UNITTEST_DO_COMPARE true/#define UNITTEST_DO_COMPARE false/" unittest.cpp 35 | make binaries 36 | sudo ./build/unittest.bin 37 | EOF 38 | -------------------------------------------------------------------------------- /.test-stage/tests/bbapi-module/tags: -------------------------------------------------------------------------------- 1 | ssh 2 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | 2 | 1.7: 3 | ==== 4 | - split bbapi_power driver into: 5 | bbapi_power: 6 | - CX2100-0904 7 | bbapi_sups: 8 | - CX5000 S-UPS 9 | - CX5100 S-UPS 10 | - detect sups/power capabilities in bbapi_init() and register platform_devices for detected features 11 | 12 | Test log: 13 | ----------| | 14 | Kernel | arch | device | os |result (bbapi, display, power, sups, watchdog) 15 | ==========|======|======================|=======|============================================== 16 | 4.1.7-rt8 |x86_64| CX2030 + CX2100-0904 | 14.04 | no error 17 | 4.1.7-rt8 |x86_64| CX5130 | 14.04 | no error 18 | 4.1.7-rt8 | i386 | CX5020 | 14.04 | no error 19 | 20 | * os/14.04 means Ubuntu 14.04 LTS 21 | 22 | 23 | 24 | 1.6: 25 | ==== 26 | - add bbapi_power driver with support for: 27 | - CX2100-0904 28 | - CX5000 S-UPS 29 | - CX5100 S-UPS 30 | - make bbapi_wdt less verbose 31 | 32 | Test log: 33 | ----------| | 34 | Kernel | arch | device | os |result (bbapi, display, power, watchdog) 35 | ==========|======|======================|=======|======================================== 36 | 4.1.7-rt8 |x86_64| CX2030 + CX2100-0904 | 14.04 | no error 37 | 4.1.7-rt8 |x86_64| CX5130 | 14.04 | no error 38 | 4.1.7-rt8 | i386 | CX5020 | 14.04 | no error 39 | 40 | * os/14.04 means Ubuntu 14.04 LTS 41 | 42 | 43 | 44 | 1.5: 45 | ==== 46 | - replace ENABLE_KEEPALIVEPING with runtime code 47 | - fix watchdog api to always support WDIOF_KEEPALIVEPING 48 | - refactor test_config and config_*.h 49 | 50 | Test log: 51 | ----------| | 52 | Kernel | arch | device | os |result (bbapi, display, watchdog) 53 | ==========|======|======================|=======|===================================== 54 | 4.1.7-rt8 |x86_64| CX2030 + CX2100-0904 | 14.04 | no error 55 | 4.1.7-rt8 |x86_64| CX5130 | 14.04 | no error 56 | 4.1.7-rt8 | i386 | CX5020 | 14.04 | no error 57 | 58 | * os/14.04 means Ubuntu 14.04 LTS 59 | 60 | 61 | 62 | 1.4: 63 | ==== 64 | - add support for platforms without legacy watchdog 65 | - remove support for kernels < 2.6.35 66 | - remove constness from in buffer in bbapi_write() 67 | 68 | Test log: 69 | ----------| | 70 | Kernel | arch | device | os |result (bbapi, display, watchdog) 71 | ==========|======|======================|=======|===================================== 72 | 4.1.7-rt8 |x86_64| CX2030 + CX2100-0904 | 14.04 | no error 73 | 4.1.7-rt8 |x86_64| CX5130 | 14.04 | no error 74 | 4.1.7-rt8 | i386 | CX5020 | 14.04 | no error 75 | 76 | * os/14.04 means Ubuntu 14.04 LTS 77 | 78 | 79 | 80 | 1.3: 81 | ==== 82 | - fix bbapi_find_bios() to work on platforms without 16 byte BBAPI alignment 83 | 84 | Test log: 85 | ----------| | 86 | Kernel | arch | device | os |result (bbapi, display, watchdog) 87 | ==========|======|======================|=======|===================================== 88 | 4.1.7-rt8 |x86_64| CX2030 + CX2100-0904 | 14.04 | no error 89 | 4.1.7-rt8 | i386 | CX5020 | 14.04 | no error 90 | 91 | * os/14.04 means Ubuntu 14.04 LTS 92 | 93 | 94 | 95 | 1.2: 96 | ==== 97 | - change license of TcBaDevDef_gpl.h to allow usage in proprietary userspace software 98 | - fix BIOS search area to pass the resource map sanity check 99 | 100 | Test log: 101 | ----------| | 102 | Kernel | arch | device | os |result (bbapi, watchdog) 103 | ==========|======|======================|=======|===================================== 104 | 3.4.69 |x86_64| CX2030 + CX2100-0904 | 14.04 | no error 105 | 3.16.0 | i386 | CX2030 + CX2100-0004 | 14.04 | no error 106 | 3.12.18 | i386 | CX5020 | 14.04 | no error 107 | 108 | * os/14.04 means Ubuntu 14.04 LTS 109 | 110 | 111 | 112 | 1.1: 113 | ==== 114 | - Avoided unused return value warning. (Patch from Florian Pose ) 115 | - replaced DEV_NAME with KBUILD_MODNAME 116 | 117 | Test log: 118 | ----------| | 119 | Kernel | arch | device | os |result (bbapi, watchdog) 120 | ==========|======|======================|=======|===================================== 121 | 3.4.69 | i386 | CX2030 + CX2100-0004 | 12.04 | no error 122 | 3.4.69 |x86_64| CX2030 + CX2100-0004 | 12.04 | no error 123 | 3.4.69 | i386 | CX2030 + CX2100-0904 | 12.04 | no error 124 | 3.4.69 |x86_64| CX2030 + CX2100-0904 | 12.04 | no error 125 | 3.12.18 | i386 | CX5020 | 14.04 | no error 126 | 127 | * os/12.04 means Ubuntu 12.04 LTS 128 | * os/14.04 means Ubuntu 14.04 LTS 129 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10.2) 2 | project(bbapi CXX) 3 | find_package(Threads) 4 | add_executable(display_example display_example.cpp) 5 | add_executable(sensors_example sensors_example.cpp) 6 | add_executable(unittest unittest.cpp) 7 | target_link_libraries(display_example -static ${CMAKE_THREAD_LIBS_INIT}) 8 | target_link_libraries(sensors_example -static) 9 | target_link_libraries(unittest -static ${CMAKE_THREAD_LIBS_INIT}) 10 | set_target_properties(display_example sensors_example unittest PROPERTIES SUFFIX ".bin") 11 | 12 | 13 | add_compile_options( 14 | -lpthread 15 | -Wall 16 | -pedantic 17 | -std=c++11 18 | ) 19 | 20 | include_directories( 21 | ${CMAKE_SOURCE_DIR}/../ 22 | ${CMAKE_SOURCE_DIR}/tools/ 23 | ) 24 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Valid-License-Identifier: MIT 2 | SPDX-URL: https://spdx.org/licenses/MIT.html 3 | Usage-Guide: 4 | To use the MIT License put the following SPDX tag/value pair into a 5 | comment according to the placement guidelines in the licensing rules 6 | documentation: 7 | SPDX-License-Identifier: MIT 8 | License-Text: 9 | 10 | MIT License 11 | 12 | Copyright (c) 2019 Beckhoff Automation GmbH & Co. KG 13 | 14 | Permission is hereby granted, free of charge, to any person obtaining a 15 | copy of this software and associated documentation files (the "Software"), 16 | to deal in the Software without restriction, including without limitation 17 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 18 | and/or sell copies of the Software, and to permit persons to whom the 19 | Software is furnished to do so, subject to the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be included in 22 | all copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 29 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 30 | DEALINGS IN THE SOFTWARE. 31 | -------------------------------------------------------------------------------- /Lindent: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | PARAM="-npro -kr -i8 -ts8 -sob -l80 -ss -ncs -cp1" 3 | RES=`indent --version` 4 | V1=`echo $RES | cut -d' ' -f3 | cut -d'.' -f1` 5 | V2=`echo $RES | cut -d' ' -f3 | cut -d'.' -f2` 6 | V3=`echo $RES | cut -d' ' -f3 | cut -d'.' -f3` 7 | if [ $V1 -gt 2 ]; then 8 | PARAM="$PARAM -il0" 9 | elif [ $V1 -eq 2 ]; then 10 | if [ $V2 -gt 2 ]; then 11 | PARAM="$PARAM -il0"; 12 | elif [ $V2 -eq 2 ]; then 13 | if [ $V3 -ge 10 ]; then 14 | PARAM="$PARAM -il0" 15 | fi 16 | fi 17 | fi 18 | indent $PARAM "$@" 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET = bbapi 2 | EXTRA_DIR = /lib/modules/$(shell uname -r)/extra/ 3 | obj-m += $(TARGET).o 4 | $(TARGET)-objs := api.o simple_cdev.o 5 | SUBDIRS := $(filter-out scripts/., $(wildcard */.)) 6 | KDIR ?= /lib/modules/$(shell uname -r)/build 7 | 8 | OS!=uname -s 9 | SUDO_Linux=sudo 10 | SUDO_FreeBSD=doas 11 | SUDO := ${SUDO_${OS}} 12 | ccflags-y := -DBIOSAPIERR_OFFSET=0 13 | ccflags-y := -DUNAME_S=\"${OS}\" 14 | 15 | all: 16 | make -C $(KDIR) M=$(PWD) modules 17 | make -C $(KDIR) M=$(PWD)/button modules 18 | make -C $(KDIR) M=$(PWD)/display modules 19 | make -C $(KDIR) M=$(PWD)/power modules 20 | make -C $(KDIR) M=$(PWD)/sups modules 21 | make -C $(KDIR) M=$(PWD)/wdt modules 22 | 23 | install_all: all install 24 | cd button && make install 25 | cd display && make install 26 | cd power && make install 27 | cd sups && make install 28 | cd wdt && make install 29 | 30 | .PHONY: unload_all 31 | unload_all: 32 | - ${SUDO} rmmod $(TARGET)_button 33 | - ${SUDO} rmmod $(TARGET)_disp 34 | - ${SUDO} rmmod $(TARGET)_power 35 | - ${SUDO} rmmod $(TARGET)_sups 36 | - ${SUDO} rmmod $(TARGET)_wdt 37 | - ${SUDO} rmmod $(TARGET) 38 | 39 | install: unload_all bbapi.ko 40 | ${SUDO} mkdir -p $(EXTRA_DIR) 41 | ${SUDO} cp ./$(TARGET).ko $(EXTRA_DIR) 42 | ${SUDO} depmod -a 43 | ${SUDO} modprobe $(TARGET) 44 | 45 | clean: 46 | rm -rf build/ 47 | make -C $(KDIR) M=$(PWD) clean 48 | 49 | # indent the source files with the kernels Lindent script 50 | indent: indent_files indent_subdirs 51 | 52 | indent_files: api.c api.h display_example.cpp sensors_example.cpp simple_cdev.c simple_cdev.h 53 | ./Lindent $? 54 | 55 | indent_subdirs: $(SUBDIRS) 56 | 57 | $(SUBDIRS): 58 | make indent -C $@ 59 | 60 | binaries: 61 | cmake -H. -Bbuild 62 | cmake --build build 63 | 64 | display_example: binaries 65 | ${SUDO} ./build/$@.bin 66 | 67 | sensors_example: binaries 68 | ${SUDO} ./build/$@.bin 69 | 70 | sensors_example_with_c: sensors_example.cpp 71 | $(CC) -x c --std=c17 -Wall -pedantic sensors_example.cpp -o $@.bin 72 | ${SUDO} ./$@.bin 73 | 74 | 75 | unittest: binaries 76 | ${SUDO} ./build/$@.bin 77 | 78 | 79 | ${TARGET}.ko: api.c 80 | make -f Makefile.$(OS) 81 | 82 | load: ${TARGET}.ko 83 | ${SUDO} make -f Makefile.$(OS) $@ 84 | 85 | unload: 86 | ${SUDO} make -f Makefile.$(OS) $@ 87 | 88 | .PHONY: clean display_example sensors_example unittest $(SUBDIRS) 89 | -------------------------------------------------------------------------------- /Makefile.FreeBSD: -------------------------------------------------------------------------------- 1 | KMOD=bbapi 2 | SRCS+= api.c 3 | SRCS+= simple_cdev.c 4 | SRCS+= bus_if.h 5 | SRCS+= device_if.h 6 | SRCS+= vnode_if.h 7 | CFLAGS+= -DKBUILD_MODNAME='"bbapi"' 8 | CFLAGS+= -DLINUXKPI_PARAM_PREFIX="bbapi_" 9 | CFLAGS+= -DUNAME_S='"TC/BSD"' 10 | CFLAGS+= -DPAGE_KERNEL_EXEC=M_EXEC 11 | CFLAGS+= -D__KERNEL__ 12 | CFLAGS+= -I${SRCTOP}/sys/compat/linuxkpi/common/include 13 | CFLAGS+= -I${PWD}/linuxkpi 14 | UTS_RELEASE != uname -r 15 | 16 | all: prepare_linux 17 | 18 | .PHONY: prepare_linux 19 | prepare_linux: 20 | mkdir -p ${PWD}/linuxkpi/generated/ 21 | echo "#define UTS_RELEASE \"${UTS_RELEASE}\"" > ${PWD}/linuxkpi/generated/utsrelease.h 22 | 23 | .include 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Beckhoff BIOS API 2 | 3 | ### Linux Kernel 5.8 and BBAPI 4 | Since Linux Kernel 5.8 it is not possible to dynamically load a Kernel module which allocates executable memory. 5 | Therefore the BBAPI doesn't work as a loadable Kernel module anymore. 6 | If you want to use it with Kernel Version>=5.8 you need to compile your own kernel and statically compile the BBAPI into your kernel. 7 | 8 | ### General information about BBAPI 9 | The “BIOS-API” is a piece of software which is part of the BIOS in Beckhoff industrial motherboards. 10 | It offers a one-stop solution for communicating with several components on the board, 11 | such as temperature and voltage sensors, the S-USV microcontroller, the PWRCTRL microcontroller, 12 | the Watchdog and other components (if installed). 13 | It also offers access to a small memory area in the EEPROM reserved for user data. 14 | the API is integrated into the BIOS. 15 | The OS which is running on the board needs to have a special Device Driver installed to access the API functions. 16 | Through this driver the user software can take advantage of the API functionality. 17 | 18 | ### BBAPI Driver 19 | The Beckhoff BIOS API linux driver is implemented in two layers. 20 | The kernel module 'bbapi' represents the bottom layer, which communicates 21 | directly to the BIOS. The upper interface of 'bbapi' is based on ioctl's 22 | and IndexGroups and IndexOffsets defined in the "Beckhoff BIOS-API manual". 23 | 24 | On top of 'bbapi' a second layer is implemented to provide a linux interface 25 | based on device files. 'bbapi_display' and 'bbapi_wdt' implement this second 26 | layer. Both depend on an installed 'bbapi' module. 27 | 'bbapi_display' implements virtual terminal like interface to the CX2100 text display 28 | 'bbapi_wdt' implements a common watchdog interface to the CX hw watchdog 29 | 30 | 31 | ### How to build and install the kernel modules 32 | #### Install 'bbapi' 33 | 34 | 1. cd into bbapi 35 | 2. make && make install 36 | 37 | #### Install 'bbapi_display' 38 | 39 | 1. make sure 'bbapi' is already installed 40 | 2. cd into /display 41 | 3. make && make install 42 | 43 | #### Install 'bbapi_power' 44 | 45 | 1. make sure 'bbapi' is already installed 46 | 2. cd into /power 47 | 3. make && make install 48 | 49 | #### Install 'bbapi_sups' 50 | 51 | 1. make sure 'bbapi' is already installed 52 | 2. cd into /sups 53 | 3. make && make install 54 | 55 | #### Install 'bbapi_wdt' 56 | 57 | 1. make sure 'bbapi' is already installed 58 | 2. cd into /wdt 59 | 3. make && make install 60 | 61 | 62 | ### How to access the bbapi 63 | `/dev/bbapi` is the device file to access the low level BBAPI
64 | see "Beckhoff BIOS-API manual" and unittest.cpp for more details. 65 | 66 | `/dev/cx_display` is the device file to access the CX2100 text display.
67 | see display_example.cpp for detailed information 68 | 69 | `/dev/watchdog` is the device file to access the CX hardware watchdog.
70 | See https://www.kernel.org/doc/Documentation/watchdog/watchdog-api.txt 71 | 72 | `/sys/class/gpio/sups_pwrfail/value` shows the power fail state on devices with S-UPS.
73 | See scripts/poll_pwrfail.sh for detailed information 74 | 75 | ### History 76 | See [CHANGES](CHANGES) 77 | -------------------------------------------------------------------------------- /TcBaDevDef.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | Beckhoff BIOS API driver to access hwmon sensors for Beckhoff IPCs 4 | Copyright (C) 2014 - 2018 Beckhoff Automation GmbH & Co. KG 5 | Author: Patrick Bruenn 6 | */ 7 | #ifndef _TCBADEVDEF_H_ 8 | #define _TCBADEVDEF_H_ 9 | 10 | #ifndef WINDOWS 11 | typedef char TCHAR; 12 | #define _T(x) x 13 | #pragma GCC diagnostic ignored "-Wwrite-strings" 14 | 15 | /** legacy mode can be enabled by predefining BIOSAPIERR_OFFSET to 0 */ 16 | #ifdef BIOSAPIERR_OFFSET 17 | #pragma message "legacy mode is deprecated" 18 | #define BBAPI_CMD 0x5000 // BIOS API Command number for IOCTL call 19 | #else 20 | #define BIOSAPIERR_OFFSET 0x20000000 21 | 22 | #ifdef __FreeBSD__ 23 | #include 24 | #define BBAPI_CMD _IOWR('B', 0x5001, struct bbapi_struct) 25 | #else 26 | #define BBAPI_CMD_LEGACY 0x5000 // BIOS API Command number for IOCTL call 27 | #define BBAPI_CMD 0x5001 // BIOS API Command number for IOCTL call 28 | #endif 29 | #endif 30 | #define BBAPI_WATCHDOG_MAX_TIMEOUT_SEC (255 * 60) // BBAPI maximum timeout is 255 minutes 31 | 32 | #ifndef __KERNEL__ 33 | #define __user 34 | #define __STDC_FORMAT_MACROS 35 | #include 36 | #include 37 | #include 38 | #include 39 | #endif /* #ifndef __KERNEL__ */ 40 | 41 | struct bbapi_struct { 42 | uint32_t nIndexGroup; 43 | uint32_t nIndexOffset; 44 | const void __user *pInBuffer; 45 | uint32_t nInBufferSize; 46 | void __user *pOutBuffer; 47 | uint32_t nOutBufferSize; 48 | #if BIOSAPIERR_OFFSET > 0 49 | uint32_t __user *pBytesReturned; 50 | void __user *pMode; 51 | #endif 52 | #ifdef __cplusplus 53 | bbapi_struct(uint32_t group, uint32_t offset, const void __user *pIn, uint32_t inSize, void __user *pOut, uint32_t outSize, uint32_t *bytesReturned = nullptr, void *mode = nullptr) 54 | : nIndexGroup(group), 55 | nIndexOffset(offset), 56 | pInBuffer(pIn), 57 | nInBufferSize(inSize), 58 | pOutBuffer(pOut), 59 | nOutBufferSize(outSize) 60 | #if BIOSAPIERR_OFFSET > 0 61 | ,pBytesReturned(bytesReturned), 62 | pMode(mode) 63 | #endif 64 | {}; 65 | #endif /* #ifdef __cplusplus */ 66 | }; 67 | #endif /* #ifndef WINDOWS */ 68 | 69 | #define BADEVICE_MBINFO_snprintf(p, buffer, len) \ 70 | snprintf(buffer, len, "%.8s hw: %d v%d.%d", (p)->MBName, (p)->MBRevision, (p)->biosMajVersion, (p)->biosMinVersion); 71 | 72 | #define BADEVICE_VERSION_snprintf(p, buffer, len) \ 73 | snprintf(buffer, len, "ver.: %d rev.: %d build: %d", (p)->version, (p)->revision, (p)->build); 74 | 75 | #define Bapi_GpioInfoEx_snprintf(p, buffer, len) \ 76 | snprintf(buffer, len, "0x%x, 0x%x, 0x%x, 0x%" PRIu64 "x, 0x%" PRIu64 "x [type, length, flags, address, bitmask]", \ 77 | (p)->type, (p)->length, (p)->flags, (p)->address, (p)->bitmask); 78 | 79 | #define SENSORINFO_snprintf(p, buffer, len) \ 80 | snprintf(buffer, len, "%23s %14s val:%5u(0x%04x) min:%5u(0x%04x) max:%5u(0x%04x) nom:%5u(0x%04x) %12s", \ 81 | LOCATIONCAPS[(int)((p)->eLocation)].name, PROBECAPS[(int)((p)->eType)].name, \ 82 | (p)->readVal.value,(p)->readVal.status, (p)->minVal.value, (p)->minVal.status, \ 83 | (p)->maxVal.value, (p)->maxVal.status, (p)->nomVal.value, (p)->nomVal.status, (p)->desc); 84 | 85 | #define TSUps_GpioInfo_snprintf(p, buffer, len) \ 86 | snprintf(buffer, len, "0x%04x, 0x%02x, 0x%02x [ioAddr, offset, params]", (p)->ioAddr, (p)->offset, (p)->params); 87 | 88 | 89 | //////////////////////////////////////////////////////////////// 90 | // Max. mainboard type (plattform) string length (inclusive null delimiter) 91 | #define BAGEN_MAX_MAINBOARD_TYPE 16 92 | //////////////////////////////////////////////////////////////// 93 | // Max. power controller serial number string length (inclusive null delimiter) 94 | #define PWRCTRL_MAX_SERIAL_NUMBER 17 95 | //////////////////////////////////////////////////////////////// 96 | // Max. power controller test number string length (inclusive null delimiter) 97 | #define PWRCTRL_MAX_TEST_NUMBER 7 98 | /////////////////////////////////////////////////////////// 99 | // Max. CX power supply display line length 100 | #define CXPWRSUPP_MAX_DISPLAY_CHARS 16 // without null delimiter 101 | #define CXPWRSUPP_MAX_DISPLAY_LINE (CXPWRSUPP_MAX_DISPLAY_CHARS + 1) // 17 => inclusive null delimiter 102 | 103 | //////////////////////////////////////////////////////////////// 104 | // max. length of device string (inclusive null delimiter) 105 | #define BADEVICE_MAX_STRING 24 106 | 107 | //////////////////////////////////////////////////////////////// 108 | // String description 109 | typedef struct TStringResourceCap 110 | { 111 | const TCHAR* name; 112 | }PROBECAP, LOCATIONCAP, BOOTLOADER_FWUPDATE_ERRORSTRING, *PBOOTLOADER_FWUPDATE_ERRORSTRING; 113 | 114 | //////////////////////////////////////////////////////////////// 115 | // Version information structure 116 | typedef struct TBaDevice_Version 117 | { 118 | uint8_t version; 119 | uint8_t revision; 120 | uint16_t build; 121 | #ifdef __cplusplus 122 | TBaDevice_Version(uint8_t v = 0, uint8_t r = 0, uint16_t b = 0) 123 | : version(v), revision(r), build(b) 124 | { 125 | } 126 | 127 | bool operator==(const TBaDevice_Version& ref) const { 128 | return 0 == memcmp(this, &ref, sizeof(*this)); 129 | } 130 | 131 | int snprintf(char* buffer, size_t len) const { 132 | return ::BADEVICE_VERSION_snprintf(this, buffer, len); 133 | }; 134 | #endif /* #ifdef __cplusplus */ 135 | }BADEVICE_VERSION, *PBADEVICE_VERSION; 136 | 137 | // Collective version information 138 | typedef struct TBaVersion 139 | { 140 | BADEVICE_VERSION api;// BIOS (api implementation) version information 141 | BADEVICE_VERSION drv;// Driver version information 142 | BADEVICE_VERSION dll;// Api DLL version information 143 | BADEVICE_VERSION fun;// Function DLL version information 144 | }BAVERSION,*PBAVERSION; 145 | 146 | //////////////////////////////////////////////////////////////// 147 | // String parameter 148 | typedef struct TBaDevice_String 149 | { 150 | char s[BADEVICE_MAX_STRING]; 151 | }BADEVICE_STRING, *PBADEVICE_STRING; 152 | 153 | //////////////////////////////////////////////////////////////// 154 | // Mainboard Information structure 155 | typedef struct TBaDevice_MBInfo 156 | { 157 | char MBName[8];//ascii string 158 | uint8_t MBRevision; 159 | uint8_t biosMajVersion; 160 | uint8_t biosMinVersion; 161 | uint8_t reserved; 162 | #ifdef __cplusplus 163 | TBaDevice_MBInfo(const char* name = NULL, uint8_t revision = 0, uint8_t major = 0, uint8_t minor = 0) 164 | : MBRevision(revision), biosMajVersion(major), biosMinVersion(minor), reserved(0) 165 | { 166 | if(name) { 167 | strncpy(MBName, name, sizeof(MBName)); 168 | } else { 169 | memset(MBName, 0, sizeof(MBName)); 170 | } 171 | } 172 | 173 | bool operator==(const TBaDevice_MBInfo& ref) const { 174 | return 0 == memcmp(this, &ref, sizeof(*this)); 175 | } 176 | 177 | int snprintf(char* buffer, size_t len) const { 178 | return ::BADEVICE_MBINFO_snprintf(this, buffer, len); 179 | }; 180 | #endif /* #ifdef __cplusplus */ 181 | }BADEVICE_MBINFO, *PBADEVICE_MBINFO; 182 | 183 | //////////////////////////////////////////////////////////////// 184 | // Date structure 185 | typedef struct TBDevice_Date 186 | { 187 | uint16_t day; // 0 == not used 188 | uint16_t month; // 0 == not used 189 | uint16_t year; // 0 == not used 190 | uint16_t calWeek; // calendar week 191 | }BADEVICE_DATE, *PBADEVICE_DATE; 192 | 193 | //////////////////////////////////////////////////////////////// 194 | // Sensor types 195 | typedef enum TProbeType 196 | { 197 | PROBE_UNKNOWN = 0, // Unknown 198 | PROBE_TEMPERATURE = 1, // Temperature probe [°C]; 199 | PROBE_VOLTAGE = 2, // Voltage probe [0.01V]; 200 | PROBE_FAN = 3, // Fan tachometer input [RPM]; 201 | PROBE_CASE_INTRUSION = 4, // Case intrusion input [0 = Closed, 1 = Open]; 202 | PROBE_CURRENT = 5, // Current probe [0.001A]; 203 | PROBE_COUNTER = 6, // Operating time counter (Betriebsstundenzähler) 204 | PROBE_POWER = 7, // Power consumption probe [0.001W] 205 | 206 | //add new types below 207 | PROBE_MAX 208 | }PROBETYPE,*PPROBETYPE; 209 | 210 | static const PROBECAP PROBECAPS[PROBE_MAX+1] = 211 | { 212 | {_T("UNKNOWN\0")}, 213 | {_T("TEMPERATURE\0")}, 214 | {_T("VOLTAGE\0")}, 215 | {_T("FAN\0")}, 216 | {_T("CASE INTRUSION\0")}, 217 | {_T("CURRENT\0")}, 218 | {_T("COUNTER\0")}, 219 | {_T("POWER\0")}, 220 | {_T("\0")} 221 | }; 222 | #define PROBENAME(x) (PROBECAPS[min(x,PROBE_MAX)].name) 223 | 224 | //////////////////////////////////////////////////////////////// 225 | // Sensor locations 226 | typedef enum TLocationType 227 | { 228 | LOCATION_UNKNOWN = 0, 229 | LOCATION_OTHER = 1, 230 | LOCATION_PROCESSOR = 2, 231 | LOCATION_DISK = 3, 232 | LOCATION_SYSTEM_MANAGEMENT_MODULE = 4, 233 | LOCATION_MOTHERBOARD = 5, 234 | LOCATION_MEMORY_MODULE = 6, 235 | LOCATION_POWER_SUPPLY = 7, 236 | LOCATION_ADDIN_CARD = 8, 237 | LOCATION_FRONT_PANEL_BOARD = 9, 238 | LOCATION_BACK_PANEL_BOARD = 10, 239 | LOCATION_PERIPHERIE = 11, 240 | LOCATION_CHASSIS = 12, 241 | LOCATION_BATTERY = 13, 242 | LOCATION_UPS = 14, 243 | LOCATION_GRAFFIC_BOARD = 15, 244 | LOCATION_SUPERIO = 16, 245 | LOCATION_CHIPSET = 17, 246 | LOCATION_PWRCTRL = 18, 247 | 248 | // add new types below 249 | LOCATION_MAX 250 | }LOCATIONTYPE, *PLOCATIONTYPE; 251 | 252 | static const LOCATIONCAP LOCATIONCAPS[LOCATION_MAX+1] = 253 | { 254 | {_T("UNKNOWN\0")}, 255 | {_T("OTHER\0")}, 256 | {_T("PROCESSOR\0")}, 257 | {_T("DISK\0")}, 258 | {_T("SYSTEM MANAGEMENT MODULE\0")}, 259 | {_T("MOTHERBOARD\0")}, 260 | {_T("MEMORY MODULE\0")}, 261 | {_T("POWER SUPPLY\0")}, 262 | {_T("ADDIN CARD\0")}, 263 | {_T("FRONT PANEL BOARD\0")}, 264 | {_T("BACK PANEL BOARD\0")}, 265 | {_T("PERIPHERIE\0")}, 266 | {_T("CHASSIS\0")}, 267 | {_T("BATTERY\0")}, 268 | {_T("UPS\0")}, 269 | {_T("GRAFFIC BOARD\0")}, 270 | {_T("SUPERIO\0")}, 271 | {_T("CHIPSET\0")}, 272 | {_T("PWRCTRL\0")}, 273 | {_T("\0")} 274 | }; 275 | #define LOCATIONNAME(x) (LOCATIONCAPS[min(x,LOCATION_MAX)].name) 276 | 277 | //////////////////////////////////////////////////////////////// 278 | // Infovalue structure 279 | typedef struct TInfoValue 280 | { 281 | int16_t value; // -32000 .. +32000 282 | uint16_t status;// sensor/probe measure/value status 283 | uint32_t reserved; 284 | }INFOVALUE, *PINFOVALUE; 285 | 286 | // Info value status bits 287 | #define INFOVALUE_STATUS_OK 0x0000 // value is valid and can be used 288 | #define INFOVALUE_STATUS_UNUSED 0x8000 // invalid/unused/unknown value 289 | #define INFOVALUE_STATUS_RELVALUE 0x4000 // used by temperature == relative temperature if set 290 | 291 | //////////////////////////////////////////////////////////////// 292 | // Sensor information structure (new size == 56 byte (0x38), old size == 48 byte (0x30) 293 | typedef struct TSensorInfo 294 | { 295 | PROBETYPE eType; // sensor type 296 | LOCATIONTYPE eLocation; // Sensor location 297 | INFOVALUE readVal; // Current value 298 | INFOVALUE nomVal; // Nominal value 299 | INFOVALUE minVal; // Min. value 300 | INFOVALUE maxVal; // Max. value 301 | uint32_t reserved; 302 | char desc[12]; // description of sensor as ASCII string (inkludes null termination) 303 | #ifdef __cplusplus 304 | TSensorInfo(int status = 0) 305 | { 306 | memset(this, 0, sizeof(*this)); 307 | readVal.status = INFOVALUE_STATUS_OK; 308 | } 309 | 310 | bool operator==(const TSensorInfo& ref) const { 311 | return (INFOVALUE_STATUS_UNUSED != readVal.status) && (INFOVALUE_STATUS_UNUSED != ref.readVal.status); 312 | } 313 | 314 | int snprintf(char* buffer, size_t len) const { 315 | return ::SENSORINFO_snprintf(this, buffer, len); 316 | }; 317 | #endif /* #ifdef __cplusplus */ 318 | }SENSORINFO, *PSENSORINFO; 319 | 320 | //********************************************************* 321 | // SUPS data types 322 | //********************************************************* 323 | //////////////////////////////////////////////////////////////// 324 | // SUPS state 325 | typedef enum TSUps_State 326 | { 327 | SUPS_STATE_OTHER = 0x00, 328 | SUPS_STATE_OFF = 0xAF,// UPS off 329 | SUPS_STATE_DCIN_FAIL = 0xBB,// DCin fail 330 | SUPS_STATE_CHARGING = 0xC0,// Charging 331 | SUPS_STATE_DISCHARGING = 0xD0,// Discharging 332 | SUPS_STATE_BAL_ERR = 0xEE,// Balance error 333 | SUPS_STATE_NOT_SUPP = 0xF0,// Not supported 334 | SUPS_STATE_NO_COMM = 0xF1,// Not supported 335 | SUPS_STATE_NACK = 0xF2,// NACK 336 | SUPS_STATE_CHKSUM_ERR = 0xF3,// Checksum error 337 | SUPS_STATE_ID_ERR = 0xF4,// ID error, communication failed 338 | SUPS_STATE_CTRL_PWRF = 0xFF// µC power fail 339 | }SUPS_STATE,*PSUPS_STATE; 340 | 341 | //////////////////////////////////////////////////////////////// 342 | // SUPS or watchdog GPIO pin info 343 | typedef struct TSUps_GpioInfo 344 | { 345 | uint16_t ioAddr; 346 | uint8_t offset; 347 | uint8_t params; 348 | const uint32_t reserved; 349 | #ifdef __cplusplus 350 | TSUps_GpioInfo(uint16_t addr = 0, uint8_t off = 0, uint8_t param = 0) 351 | : ioAddr(addr), offset(off), params(param), reserved(0) 352 | { 353 | } 354 | 355 | bool operator==(const TSUps_GpioInfo& ref) const { 356 | return (ioAddr == ref.ioAddr) && (offset == ref.offset) && (params == ref.params); 357 | } 358 | 359 | int snprintf(char* buffer, size_t len) const { 360 | return ::TSUps_GpioInfo_snprintf(this, buffer, len); 361 | }; 362 | #endif /* #ifdef __cplusplus */ 363 | }SUPS_GPIO_INFO, *PSUPS_GPIO_INFO, WATCHDOG_GPIO_INFO, *PWATCHDOG_GPIO_INFO; 364 | 365 | typedef struct Bapi_GpioInfoEx 366 | { 367 | uint16_t type; //0=Port access, 1=MemoryMappedIo,... 368 | uint16_t length; 369 | uint16_t flags; //Bit0-1: 00 Not valid, 01=RO, 10=WO, 11=RW;Bit2-3: 00=lowActive, 01=highActive,11=Toggle;Bit4-15:TBD 370 | uint16_t reserved; 371 | uint64_t address; 372 | uint64_t bitmask; 373 | #ifdef __cplusplus 374 | Bapi_GpioInfoEx(uint16_t t = 0, uint16_t len = 0, uint16_t f = 0, uint64_t addr = 0, uint64_t mask = 0) 375 | : type(t), 376 | length(len), 377 | flags(f), 378 | reserved(0), 379 | address(addr), 380 | bitmask(mask) 381 | { 382 | } 383 | 384 | bool operator==(const Bapi_GpioInfoEx& ref) const { 385 | return !memcmp(this, &ref, sizeof(*this)); 386 | } 387 | 388 | int snprintf(char* buffer, size_t len) const { 389 | return ::Bapi_GpioInfoEx_snprintf(this, buffer, len); 390 | }; 391 | #endif /* #ifdef __cplusplus */ 392 | }BAPI_GPIO_INFO_EX; 393 | 394 | /////////////////////////////////////////////////////////// 395 | // Index-Group/Index-Offset specification 396 | /////////////////////////////////////////////////////////// 397 | #define BIOSIGRP_GENERAL 0x00000000 // 0x00000000..0x00001FFF General BIOS functions 398 | #define BIOSIOFFS_GENERAL_VERSION 0x00000000 // Return BIOS API version, W:0, R:4 399 | #define BIOSIOFFS_GENERAL_GETBOARDNAME 0x00000001 // Return mainboard plattform, W:0, R:16 400 | #define BIOSIOFFS_GENERAL_GETBOARDINFO 0x00000002 // Return mainboard info (BiosVersion, Mainboard, Revision), W:0, R:SIZEOF(BADEVICE_MBINFO) 401 | #define BIOSIOFFS_GENERAL_GETPLATFORMINFO 0x00000003 // Returns the BIOS API platform information (32, 64 bit?). W:0, R:1 ( <0x00> := 32 bit API, <0x01> := 64 bit API ) 402 | 403 | #define BIOSIGRP_SYSTEM 0x00002000 // 0x00002000..0x00002FFF System information 404 | #define BIOSIOFFS_SYSTEM_COUNT_SENSORS 0x00000000 // Return max. number of available sensors, W:0, R:4 (count) 405 | #define BIOSIOFFS_SYSTEM_SENSOR_MIN 0x00000001 // Return info of first sensor IO:Sensor index, W:0, R:SIZEOF(SENSORINFO) 406 | // #define BIOSIOFFS_SYSTEM_SENSOR_MAX 0x000000FF // Return info of max. sensor, W:0, R:56 407 | 408 | #define BIOSIGRP_SERVICES 0x00003000 // 0x00003000..0x00003FFF := Device services 409 | #define BIOSIOFFS_SERVICES_GPIOREADIN0 0x00000000 // Reads GPIO0 input, W:0, R:1 410 | #define BIOSIOFFS_SERVICES_GPIOREADIN1 0x00000001 // Reads GPIO1 input, W:0, R:1 411 | #define BIOSIOFFS_SERVICES_GPIOREADOUT0 0x00000002 // Reads GPIO0 output, W:0, R:1 412 | #define BIOSIOFFS_SERVICES_GPIOREADOUT1 0x00000003 // Reads GPIO1 output, W:0, R:1 413 | #define BIOSIOFFS_SERVICES_GPIOREADMASK0 0x00000004 // Reads GPIO0 mask, W:0, R:1 414 | #define BIOSIOFFS_SERVICES_GPIOREADMASK1 0x00000005 // Reads GPIO1 mask, W:0, R:1 415 | #define BIOSIOFFS_SERVICES_GPIOWRITEOUT0 0x00000006 // Writes GPIO0 output, W:1, R:0 416 | #define BIOSIOFFS_SERVICES_GPIOWRITEOUT1 0x00000007 // Writes GPIO1 output, W:1, R:0 417 | #define BIOSIOFFS_SERVICES_GPIOWRITEMASK0 0x00000008 // Writes GPIO0 mask, W:1, R:0 418 | #define BIOSIOFFS_SERVICES_GPIOWRITEMASK1 0x00000009 // Writes GPIO1 mask, W:1, R:0 419 | 420 | #define BIOSIGRP_PWRCTRL 0x00004000 // Power controller functions 421 | #define BIOSIOFFS_PWRCTRL_BOOTLDR_REV 0x00000000 // Bootloader revision, W:0, R:3 ([xx].[yy]-[zz]) 422 | #define BIOSIOFFS_PWRCTRL_FIRMWARE_REV 0x00000001 // Firmware revision, W:0, R:3 ([xx].[yy]-[zz]) 423 | #define BIOSIOFFS_PWRCTRL_DEVICE_ID 0x00000002 // Device ID, W:0, R:1 424 | #define BIOSIOFFS_PWRCTRL_OPERATING_TIME 0x00000003 // Operating time counter (Betriebsstundenzähler), W:0, R:4 (minutes) 425 | #define BIOSIOFFS_PWRCTRL_BOARD_TEMP 0x00000004 // Board temperature: min, max, W:0, R:2 426 | #define BIOSIOFFS_PWRCTRL_INPUT_VOLTAGE 0x00000005 // Input voltage: min, max, W:0, R:2 427 | #define BIOSIOFFS_PWRCTRL_SERIAL_NUMBER 0x00000006 // Serial number, W:0, R:16 428 | #define BIOSIOFFS_PWRCTRL_BOOT_COUNTER 0x00000007 // Boot counter, W:0, R:2 429 | #define BIOSIOFFS_PWRCTRL_PRODUCTION_DATE 0x00000008 // Manufacturing date, W:0, R:2, ([KW].[JJ]) 430 | #define BIOSIOFFS_PWRCTRL_BOARD_POSITION 0x00000009 // Board position, W:0, R:1 431 | #define BIOSIOFFS_PWRCTRL_SHUTDOWN_REASON 0x0000000A // Last Shutdown reason, W:0, R:1 432 | #define BIOSIOFFS_PWRCTRL_TEST_COUNTER 0x0000000B // Test counter, W:0, R:1 433 | #define BIOSIOFFS_PWRCTRL_TEST_NUMBER 0x0000000C // Test number, W:0, R:6 434 | 435 | #define BIOSIGRP_SUPS 0x00005000 // SUPS functions 436 | #define BIOSIOFFS_SUPS_ENABLE 0x00000000 // Enable/disable SUPS, W:1, R:0 437 | #define BIOSIOFFS_SUPS_STATUS 0x00000001 // SUPS status, W:0, R:1 438 | #define BIOSIOFFS_SUPS_REVISION 0x00000002 // SUPS revision, W:0, R:2 439 | #define BIOSIOFFS_SUPS_PWRFAIL_COUNTER 0x00000003 // Power fail counter, W:0, R:2 440 | #define BIOSIOFFS_SUPS_PWRFAIL_TIMES 0x00000004 // Get latest power fail time stamps, W:0, R:12 441 | #define BIOSIOFFS_SUPS_SET_SHUTDOWN_TYPE 0x00000005 // Set the Shutdown behavior, W:1, R:0 442 | #define BIOSIOFFS_SUPS_GET_SHUTDOWN_TYPE 0x00000006 // Get the Shutdown behavior and reset, W:0, R:1 443 | #define BIOSIOFFS_SUPS_ACTIVE_COUNT 0x00000007 // Get the SUSV Active Count and reset, W:0, R:1 444 | #define BIOSIOFFS_SUPS_INTERNAL_PWRF_STATUS 0x00000008 // Get the number of Pwr-Fail in the SUSV, W:0, R:1 445 | #define BIOSIOFFS_SUPS_CAPACITY_TEST 0x00000009 // Capacitator test, W:0, R:0 446 | #define BIOSIOFFS_SUPS_TEST_RESULT 0x0000000a // Get SUPS test result, W:0, R:1 447 | #define BIOSIOFFS_SUPS_GPIO_PIN 0x000000a0 // Get the Address and the GPIO-Pin from PWR-Fail PIN, W:0, R:4 448 | #define BIOSIOFFS_SUPS_GPIO_PIN_EX 0x000000a1 // Get the Address and the GPIO-Pin from PWR-Fail PIN, W:0, R:24 449 | 450 | #define BIOSIGRP_WATCHDOG 0x00006000 // Watchdog functions 451 | #define BIOSIOFFS_WATCHDOG_ENABLE_TRIGGER 0x00000000 // Enable/trigger watchdog, W:1 (0 == Disable, 1..255 == Enable + set internal, R:0 452 | #define BIOSIOFFS_WATCHDOG_CONFIG 0x00000001 // Configure watchdog, W:1 (sets interval timebase, 0 == seconds, 1 == minutes), R:0 453 | #define BIOSIOFFS_WATCHDOG_GETCONFIG 0x00000002 // Get WD config, W:0, R:1 454 | #define BIOSIOFFS_WATCHDOG_SETCONFIG 0x00000003 // Set WD config, W:1, R:0 455 | #define BIOSIOFFS_WATCHDOG_ACTIVATE_PWRCTRL 0x00000004 // Activate PwrCtrl IO-Watchdog, W:1, R:0 456 | #define BIOSIOFFS_WATCHDOG_TRIGGER_TIMESPAN 0x00000005 // Enables/trigger the watchdog with TimeSpan, W:2, R:0 457 | #define BIOSIOFFS_WATCHDOG_IORETRIGGER 0x00000006 // IO-Retrigger, W:0, R:0 458 | #define BIOSIOFFS_WATCHDOG_GPIO_PIN 0x00000007 // Get IO Addr for direct retrigger, W:0, R:4 459 | #define BIOSIOFFS_WATCHDOG_GPIO_PIN_EX 0x00000008 // Get IO Addr for direct retrigger, W:0, R:24 460 | 461 | #define BIOSIGRP_UEEPROM 0x00007000 // 0x00007000..0x00007FFF := User EEPROM functions 462 | #define BIOSIOFFS_UEEPROM_READ 0x00000000 // Reads EEPROM data, W:0, R:128 (MAX_SIZE_OF_UEEPROM_DATA) 463 | #define BIOSIOFFS_UEEPROM_WRITE 0x00000001 // Writes EEPROM data, W:128 (MAX_SIZE_OF_UEEPROM_DATA), R:0 464 | #define BIOSIOFFS_UEEPROM_READ_BYTE 0x00000002 // Reads EEPROM byte, W:1 (byte offset), R:1 (read data) 465 | #define BIOSIOFFS_UEEPROM_WRITE_BYTE 0x00000003 // Writes EEPROM byte, W:2 ( Byte[0] := byte offset, Byte[1] := value), R:0 466 | 467 | #define BIOSIGRP_LED 0x00008000 // TwinCAT and user LED functions 468 | #define BIOSIOFFS_LED_SET_TC 0x00000000 // Sets TwinCAT LED, W:1, R:0 (write value 0=off, 1=red, 2=blue, 3=green) 469 | #define BIOSIOFFS_LED_SET_USER 0x00000001 // Sets user LED, W:1, R:0 (write value 0=off, 1=red, 2=blue, 3=green) 470 | #define BIOSIOFFS_LED_SET_PWR 0x00000002 //Sets Pwr LED, W:1, R:0 (write value 0=off, 1=red, 2=blue, 3=green, 4=yellow, 5=magenta, 6=cyan, 7=white) 471 | 472 | #define BIOSIGRP_CXPWRSUPP 0x00009000 // CX Power Supply functions 473 | #define BIOSIOFFS_CXPWRSUPP_GETTYPE 0x00000010 // Get type, W:0, R:4 (DWORD) 474 | #define BIOSIOFFS_CXPWRSUPP_GETSERIALNO 0x00000011 // Get serial number, W:0, R:4 (DWORD := decimal1) 475 | #define BIOSIOFFS_CXPWRSUPP_GETFWVERSION 0x00000012 // Get FW version, W:0, R:2 (BYTE[2] := xx.yy) 476 | #define BIOSIOFFS_CXPWRSUPP_GETBOOTCOUNTER 0x00000013 // Get boot counter, W:0, R:4 (DWORD) 477 | #define BIOSIOFFS_CXPWRSUPP_GETOPERATIONTIME 0x00000014 // Get operation time, [minutes] since production time, W:0, R:4 (DWORD) 478 | #define BIOSIOFFS_CXPWRSUPP_GET5VOLT 0x00000030 // Get 5V sensor [mV], W:0, R:2 (WORD) 479 | #define BIOSIOFFS_CXPWRSUPP_GETMAX5VOLT 0x00000031 // Get max. 5V sensor [mV], W:0, R:2 (WORD) 480 | #define BIOSIOFFS_CXPWRSUPP_GET12VOLT 0x00000032 // Get 12V sensor [mV], W:0, R:2 (WORD) 481 | #define BIOSIOFFS_CXPWRSUPP_GETMAX12VOLT 0x00000033 // Get max. 12V sensor [mV], W:0, R:2 (WORD) 482 | #define BIOSIOFFS_CXPWRSUPP_GET24VOLT 0x00000034 // Get 24V sensor [mV], W:0, R:2 (WORD) 483 | #define BIOSIOFFS_CXPWRSUPP_GETMAX24VOLT 0x00000035 // Get max. 24V sensor [mV], W:0, R:2 (WORD) 484 | #define BIOSIOFFS_CXPWRSUPP_GETTEMP 0x00000036 // Get temperature [°C], W:0, R:1 (Signed BYTE) 485 | #define BIOSIOFFS_CXPWRSUPP_GETMINTEMP 0x00000037 // Get min. temperature [°C], W:0, R:1 (Signed BYTE) 486 | #define BIOSIOFFS_CXPWRSUPP_GETMAXTEMP 0x00000038 // Get max. temperature [°C], W:0, R:1 (Signed BYTE) 487 | #define BIOSIOFFS_CXPWRSUPP_GETCURRENT 0x00000039 // Get current [mA], W:0, R:2 (WORD) 488 | #define BIOSIOFFS_CXPWRSUPP_GETMAXCURRENT 0x0000003A // Get max. current [mA], W:0, R:2 (WORD) 489 | #define BIOSIOFFS_CXPWRSUPP_GETPOWER 0x0000003B // Get power [mW], W:0, R:4 (DWORD) 490 | #define BIOSIOFFS_CXPWRSUPP_GETMAXPOWER 0x0000003C // Get max. power [mW], W:0, R:4 (DWORD) 491 | #define BIOSIOFFS_CXPWRSUPP_ENABLEBACKLIGHT 0x00000060 // Set display backlight, W:1 (0x00 := OFF, 0xFF := ON), R:0 492 | #define BIOSIOFFS_CXPWRSUPP_DISPLAYLINE1 0x00000061 // Set display line 1, W:17(BYTE[17]), R:0 493 | #define BIOSIOFFS_CXPWRSUPP_DISPLAYLINE2 0x00000062 // Set display line 2, W:17(BYTE[17]), R:0 494 | #define BIOSIOFFS_CXPWRSUPP_GETBUTTONSTATE 0x00000063 // Get button state, W:0, R:1 (0x01:=right, 0x02:=left, 0x04:=down, 0x08:=up, 0x10:=state select) 495 | 496 | #define BIOSIGRP_CXUPS 0x00009001 // CX (condensator/battery) UPS functions 497 | #define BIOSIOFFS_CXUPS_GETENABLED 0x00000000 // Get UPS enabled state, W:0, R:1 (BYTE) (0 := Disabled, 1 := Enabled) 498 | #define BIOSIOFFS_CXUPS_SETENABLED 0x00000001 // Set UPS enabled state, W:1 (BYTE) (0 := Disable, 1 := Enable), R:0 499 | #define BIOSIOFFS_CXUPS_GETFIRMWAREVER 0x00000002 // Get firmware version, W:0, R:2 (BYTE[0..1]) (BYTE[0] := Version, BYTE[1] := Revision) 500 | #define BIOSIOFFS_CXUPS_GETPOWERSTATUS 0x00000003 // Get power status, W:0, R:1 (BYTE) (0 := Unknown, 1 := Online, 2 := OnBatteries) 501 | #define BIOSIOFFS_CXUPS_GETBATTERYSTATUS 0x00000004 // Get battery status, W:0, R:1 (BYTE) (0 := Unknown, 1 := Ok, 2 := Change batteries) 502 | #define BIOSIOFFS_CXUPS_GETBATTERYCAPACITY 0x00000005 // Get battery capacity, W:0, R:1 (BYTE) [%] 503 | #define BIOSIOFFS_CXUPS_GETBATTERYRUNTIME 0x00000006 // Get battery runtime, W:0, R:4 (DWORD) [s] 504 | #define BIOSIOFFS_CXUPS_GETBOOTCOUNTER 0x00000007 // Get boot counter, W:0, R:4 (DWORD) [n] 505 | #define BIOSIOFFS_CXUPS_GETOPERATIONTIME 0x00000008 // Get operation time (minutes since production time), W:0, R:4 (DWORD) [min] 506 | #define BIOSIOFFS_CXUPS_GETPOWERFAILCOUNT 0x00000009 // Get powerfail count, W:0, R:4 (DWORD) [n] 507 | #define BIOSIOFFS_CXUPS_GETBATTERYCRITICAL 0x0000000A // Get battery critical, W:0, R:1 (BYTE) (0 := Cap. ok, 1 := Cap. critical) 508 | #define BIOSIOFFS_CXUPS_GETBATTERYPRESENT 0x0000000B // Get battery present, W:0, R:1 (BYTE) (0 := Not present, 1 := Present) 509 | // #define BIOSIOFFS_CXUPS_GETRESTARTMODE 0x0000000C // OBSOLETE: Don't use it! Get restart mode, W:0, R:1 (BYTE) (0 := Disabled, 1 := Enabled) 510 | // #define BIOSIOFFS_CXUPS_SETRESTARTMODE 0x0000000D // OBSOLETE: Don't use it! Set restart mode, W:1 (BYTE) (0 := Disable, 1 := Enable), R:0 511 | #define BIOSIOFFS_CXUPS_SETSHUTDOWNMODE 0x0000000E // Set shutdown mode, W:1 (BYTE) (0 := Disable, 1:= Enable), R:0 512 | #define BIOSIOFFS_CXUPS_GETBATTSERIALNUMBER 0x00000011 // Get serial number W:0, R:4 513 | #define BIOSIOFFS_CXUPS_GETBATTHARDWAREVERSION 0x00000015 // Get hardware version W:0, R:2 514 | #define BIOSIOFFS_CXUPS_GETBATTPRODUCTIONDATE 0x00000019 // Get production date W:0, R:4 515 | #define BIOSIOFFS_CXUPS_GETOUTPUTVOLT 0x00000032 // Get 9~12V output voltage, W:0, R:2 (WORD) [mV] 516 | #define BIOSIOFFS_CXUPS_GETMAXOUTPUTVOLT 0x00000033 // Get max. 9~12V output voltage, W:0, R:2 (WORD) [mV] 517 | #define BIOSIOFFS_CXUPS_GETINPUTVOLT 0x00000034 // Get 24V input voltage, W:0, R:2 (WORD) [mV] 518 | #define BIOSIOFFS_CXUPS_GETMAXINPUTVOLT 0x00000035 // Get max. 24V input voltage, W:0, R:2 (WORD) [mV] 519 | #define BIOSIOFFS_CXUPS_GETTEMP 0x00000036 // Get temperature, W:0, R:1 (Signed BYTE)[°C] 520 | #define BIOSIOFFS_CXUPS_GETMINTEMP 0x00000037 // Get min. temperature, W:0, R:1 (Signed BYTE)[°C] 521 | #define BIOSIOFFS_CXUPS_GETMAXTEMP 0x00000038 // Get max. temperature, W:0, R:1 (Signed BYTE)[°C] 522 | #define BIOSIOFFS_CXUPS_GETCHARGINGCURRENT 0x00000039 // Get charging current, W:0, R:2 (WORD) [mA] 523 | #define BIOSIOFFS_CXUPS_GETMAXCHARGINGCURRENT 0x0000003A // Get max. charging current, W:0, R:2 (WORD) [mA] 524 | #define BIOSIOFFS_CXUPS_GETCHARGINGPOWER 0x0000003B // Get charging power consumption, W:0, R:4 (DWORD) [mW] 525 | #define BIOSIOFFS_CXUPS_GETMAXCHARGINGPOWER 0x0000003C // Get max. charging power consumption, W:0, R:4 (DWORD) [mW] 526 | #define BIOSIOFFS_CXUPS_GETDISCHARGINGCURRENT 0x0000003D // Get discharging current, W:0, R:2 (WORD) [mA] 527 | #define BIOSIOFFS_CXUPS_GETMAXDISCHARGINGCURRENT 0x0000003E // Get max. discharging current, W:0, R:2 (WORD) [mA] 528 | #define BIOSIOFFS_CXUPS_GETDISCHARGINGPOWER 0x0000003F // Get discharging power consumption, W:0, R:4 (DWORD) [mW] 529 | #define BIOSIOFFS_CXUPS_GETMAXDISCHARGINGPOWER 0x00000040 // Get max. discharging power consumption, W:0, R:4 (DWORD) [mW] 530 | #define BIOSIOFFS_CXUPS_GETLASTBATTCHANGEDATE 0x00000097 // Gets last battery change date , W:0, R:4 531 | #define BIOSIOFFS_CXUPS_SETLASTBATTCHANGEDATE 0x00000098 // Sets last battery change date W:4, R:0 532 | #define BIOSIOFFS_CXUPS_GETBATTRATEDCAPACITY 0x00000099 // Get rated capacity W:0, R:4 [mAh] 533 | #define BIOSIOFFS_CXUPS_GETSMBUSADDRESS 0x000000F0 // Get SMBus address W:0, R:2 (hHost, address) 534 | 535 | #define BIOSAPIERR_NOERR 0x0 536 | #define BIOSAPI_SRVNOTSUPP (BIOSAPIERR_OFFSET + 0x701) 537 | #define BIOSAPI_INVALIDSIZE (BIOSAPIERR_OFFSET + 0x705) 538 | #define BIOSAPI_BUSY (BIOSAPIERR_OFFSET + 0x708) 539 | #define BIOSAPI_INVALIDPARM (BIOSAPIERR_OFFSET + 0x70B) 540 | 541 | #define TCBADEV_ERROR_NONE 0x00000000 //No Error 542 | #define TCBADEV_ERROR_SRVNOTSUPP (BIOSAPIERR_OFFSET + 0x701) //Service/Function not supported 543 | #define TCBADEV_ERROR_INVALIDGRP (BIOSAPIERR_OFFSET + 0x702) //Invalid Index Group 544 | #define TCBADEV_ERROR_INVALIDOFFSET (BIOSAPIERR_OFFSET + 0x703) //Invalid Index Offset 545 | #define TCBADEV_ERROR_INVALIDACCESS (BIOSAPIERR_OFFSET + 0x704) //Reading/writing not permitted 546 | #define TCBADEV_ERROR_INVALIDSIZE (BIOSAPIERR_OFFSET + 0x705) //Invalid data size parameter 547 | #define TCBADEV_ERROR_INVALIDDATA (BIOSAPIERR_OFFSET + 0x706) //Invalid data value 548 | #define TCBADEV_ERROR_NOTREADY (BIOSAPIERR_OFFSET + 0x707) //Function/service not ready 549 | #define TCBADEV_ERROR_BUSY (BIOSAPIERR_OFFSET + 0x708) //Function/Service not ready 550 | #define TCBADEV_ERROR_INVALIDCONTEXT (BIOSAPIERR_OFFSET + 0x709) //Invalid context 551 | #define TCBADEV_ERROR_NOMEMORY (BIOSAPIERR_OFFSET + 0x70A) //No memory 552 | #define TCBADEV_ERROR_INVALIDPARM (BIOSAPIERR_OFFSET + 0x70B) //Invalid Parameter 553 | #define TCBADEV_ERROR_NOTFOUND (BIOSAPIERR_OFFSET + 0x70C) //Not found 554 | #define TCBADEV_ERROR_SYNTAX (BIOSAPIERR_OFFSET + 0x70D) //Syntax error 555 | #define TCBADEV_ERROR_INCOMPATIBLE (BIOSAPIERR_OFFSET + 0x70E) //Object do not match 556 | #define TCBADEV_ERROR_EXISTS (BIOSAPIERR_OFFSET + 0x70F) //Object already exists 557 | #define TCBADEV_ERROR_INVALIDSTATE (BIOSAPIERR_OFFSET + 0x712) //Invalid state 558 | #define TCBADEV_ERROR_TIMEOUT (BIOSAPIERR_OFFSET + 0x719) //Device has timeout 559 | #define TCBADEV_ERROR_REQUESTABORT (BIOSAPIERR_OFFSET + 0x71F) //Request is aborted (exception handler) 560 | #define TCBADEV_ERROR_INVALIDRESP (BIOSAPIERR_OFFSET + 0x754) //Invalid response received 561 | 562 | #endif /* #ifndef _TCBADEVDEF_H_ */ 563 | -------------------------------------------------------------------------------- /api.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | Character Driver for Beckhoff BIOS API 4 | Author: Heiko Wilke 5 | Author: Patrick Bruenn 6 | Copyright (C) 2013 - 2018 Beckhoff Automation GmbH & Co. KG 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #if (LINUX_VERSION_CODE < KERNEL_VERSION(4,12,0)) 24 | #include 25 | #else 26 | #include 27 | #endif 28 | 29 | #include "api.h" 30 | #include "TcBaDevDef.h" 31 | 32 | #define DRV_VERSION "0.2.11" 33 | #if BIOSAPIERR_OFFSET > 0 34 | #define DRV_DESCRIPTION "Beckhoff BIOS API Driver" 35 | #else 36 | #define DRV_DESCRIPTION "Beckhoff BIOS API Driver (legacy mode)" 37 | #endif 38 | 39 | /* Global Variables */ 40 | static struct bbapi_object g_bbapi; 41 | 42 | static unsigned long g_bbapi_busy_retry = 10; 43 | module_param_named(busy_retry, g_bbapi_busy_retry, ulong, 0); 44 | MODULE_PARM_DESC(busy_retry, "Number of attemps to retry BBAPI calls, failed with BIOSAPI_BUSY."); 45 | 46 | static unsigned long g_bbapi_search_area = BBIOSAPI_SIGNATURE_SEARCH_AREA; 47 | module_param_named(search_area, g_bbapi_search_area, ulong, 0); 48 | MODULE_PARM_DESC(search_area, "Size in bytes of the area to search for the BBAPI signature."); 49 | 50 | #if defined(__i386__) 51 | static const uint64_t BBIOSAPI_SIGNATURE = 0x495041534F494242LL; // API-String "BBIOSAPI" 52 | 53 | /** 54 | * This function is a wrapper to the Beckhoff BIOS API entry function, 55 | * which uses MS Windows calling convention. 56 | * I found no elegant and stable way to implement this for i386. 57 | * "__attribute__ ((stdcall))" + va_args was promissing but worked only 58 | * with optimization level "-O1". As long as ms_abi is not supported 59 | * on 32 bit x86 we stick with inline assembly... 60 | */ 61 | static unsigned int noinline bbapi_call(const void __kernel * const in, 62 | void __kernel * const out, void *const entry, 63 | const struct bbapi_struct *const cmd, 64 | unsigned int *bytes_written) 65 | { 66 | unsigned int ret; 67 | __asm__("push %0": :"r"(bytes_written)); 68 | __asm__("push %0": :"r"(cmd->nOutBufferSize)); 69 | __asm__("push %0": :"r"(out)); 70 | __asm__("push %0": :"r"(cmd->nInBufferSize)); 71 | __asm__("push %0": :"r"(in)); 72 | __asm__("push %0": :"r"(cmd->nIndexOffset)); 73 | __asm__("push %0": :"r"(cmd->nIndexGroup)); 74 | __asm__("call *%0": :"r"(entry)); 75 | __asm__("mov %%eax, %0": "=m"(ret):); 76 | return ret; 77 | } 78 | #elif defined(__x86_64__) 79 | static const uint64_t BBIOSAPI_SIGNATURE = 0x3436584950414242LL; // API-String "BBAPIX64" 80 | 81 | /** 82 | * This function is a wrapper to the Beckhoff BIOS API entry function, 83 | * which uses MS Windows calling convention. 84 | * On x86_64 all we need to do is to use gcc "__attribute__((ms_abi))" 85 | */ 86 | typedef 87 | __attribute__ ((ms_abi)) uint32_t(*PFN_BBIOSAPI_CALL) (uint32_t group, 88 | uint32_t offset, 89 | void *in, 90 | uint32_t inSize, 91 | void *out, 92 | uint32_t outSize, 93 | uint32_t * bytes); 94 | 95 | static unsigned int bbapi_call(void __kernel * const in, 96 | void __kernel * const out, 97 | PFN_BBIOSAPI_CALL entry, 98 | const struct bbapi_struct *const cmd, 99 | unsigned int *bytes_written) 100 | { 101 | return entry(cmd->nIndexGroup, cmd->nIndexOffset, in, 102 | cmd->nInBufferSize, out, cmd->nOutBufferSize, 103 | bytes_written); 104 | } 105 | #endif 106 | 107 | static unsigned int bbapi_call_retry(void __kernel * const in, 108 | void __kernel * const out, 109 | PFN_BBIOSAPI_CALL entry, 110 | const struct bbapi_struct *const cmd, 111 | unsigned int *bytes_written) 112 | { 113 | ulong retries = g_bbapi_busy_retry; 114 | for (;;) { 115 | const unsigned int status = bbapi_call(in, out, entry, cmd, bytes_written); 116 | if (BIOSAPI_BUSY == (status | BIOSAPIERR_OFFSET)) { 117 | if (retries--) { 118 | pr_warn("BBAPI busy, waiting and retrying...\n"); 119 | msleep(100); 120 | continue; 121 | } else { 122 | pr_err("BBAPI was busy for too long, giving up.\n"); 123 | } 124 | } 125 | return status; 126 | } 127 | } 128 | 129 | unsigned int bbapi_rw(uint32_t group, uint32_t offset, 130 | void __kernel * const in, uint32_t size_in, 131 | void __kernel * const out, const uint32_t size_out, uint32_t *bytes_written) 132 | { 133 | const struct bbapi_struct cmd = { 134 | .nIndexGroup = group, 135 | .nIndexOffset = offset, 136 | .pInBuffer = NULL, 137 | .nInBufferSize = size_in, 138 | .pOutBuffer = NULL, 139 | .nOutBufferSize = size_out 140 | }; 141 | volatile unsigned int result = 0; 142 | 143 | if (!g_bbapi.entry) 144 | return BIOSAPI_SRVNOTSUPP; 145 | 146 | mutex_lock(&g_bbapi.mutex); 147 | result = bbapi_call_retry(in, out, g_bbapi.entry, &cmd, bytes_written); 148 | mutex_unlock(&g_bbapi.mutex); 149 | if (result) { 150 | pr_debug("%s(0x%x:0x%x) failed with: 0x%x\n", __func__, 151 | cmd.nIndexGroup, cmd.nIndexOffset, result); 152 | return -(result | BIOSAPIERR_OFFSET); 153 | } 154 | return result; 155 | } 156 | 157 | unsigned int bbapi_read(uint32_t group, uint32_t offset, 158 | void __kernel * const out, const uint32_t size) 159 | { 160 | uint32_t bytes_written = 0; 161 | return bbapi_rw(group, offset, NULL, 0, out, size, &bytes_written); 162 | } 163 | 164 | EXPORT_SYMBOL(bbapi_read); 165 | 166 | unsigned int bbapi_write(uint32_t group, uint32_t offset, 167 | void __kernel * const in, uint32_t size) 168 | { 169 | uint32_t bytes_written = 0; 170 | return bbapi_rw(group, offset, in, size, NULL, 0, &bytes_written); 171 | } 172 | 173 | EXPORT_SYMBOL(bbapi_write); 174 | 175 | int bbapi_board_is(const char *const boardname) 176 | { 177 | char board[CXPWRSUPP_MAX_DISPLAY_LINE] = { 0 }; 178 | 179 | bbapi_read(BIOSIGRP_GENERAL, BIOSIOFFS_GENERAL_GETBOARDNAME, &board, 180 | sizeof(board) - 1); 181 | return 0 == strncmp(board, boardname, sizeof(board)); 182 | } 183 | 184 | EXPORT_SYMBOL(bbapi_board_is); 185 | 186 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) 187 | #include 188 | static struct kprobe kp = { 189 | .symbol_name = "kallsyms_lookup_name" 190 | }; 191 | 192 | typedef unsigned long (*kallsyms_lookup_name_t)(const char *name); 193 | kallsyms_lookup_name_t fcn_kallsyms_lookup_name; 194 | 195 | typedef void *(*fcn_vmalloc_node_range_t)(unsigned long size, unsigned long align, 196 | unsigned long start, unsigned long end, gfp_t gfp_mask, 197 | pgprot_t prot, unsigned long vm_flags, int node, 198 | const void *caller); 199 | fcn_vmalloc_node_range_t fcn_vmalloc_node_range; 200 | #endif 201 | 202 | /** 203 | * bbapi_copy_bios() - Copy BIOS from SPI flash into RAM 204 | * @bbapi: pointer to a not initialized bbapi_object 205 | * @pos: pointer to the BIOS identifier string in flash 206 | * 207 | * We use BIOS shadowing to increase realtime performance. 208 | * The BIOS identifier string is followed by the 32-Bit BIOS API 209 | * function offset. This offset is the location of the BIOS API entry 210 | * function and is located at most 4096 bytes in front of the 211 | * BIOS memory end. So we calculate the size of the BIOS and copy it 212 | * from SPI Flash into RAM. 213 | * Accessing the BIOS in ROM while running realtime applications would 214 | * otherwise have bad effects on the realtime behaviour. 215 | * 216 | * Note: PAGE_KERNEL_EXEC omits the "no execute bit" exception 217 | * 218 | * Return: 0 for success, -ENOMEM if the allocation of kernel memory fails 219 | */ 220 | static int __init bbapi_copy_bios(struct bbapi_object *bbapi, 221 | uint8_t __iomem * pos) 222 | { 223 | const uint32_t offset = ioread32(pos + 8); 224 | const size_t size = offset + 4096; 225 | 226 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) 227 | bbapi->memory = fcn_vmalloc_node_range(size, 1, VMALLOC_START, VMALLOC_END, 228 | GFP_KERNEL, PAGE_KERNEL_EXEC, 0, NUMA_NO_NODE, __builtin_return_address(0)); 229 | #else 230 | bbapi->memory = __vmalloc(size, GFP_KERNEL, PAGE_KERNEL_EXEC); 231 | #endif 232 | if (bbapi->memory == NULL) { 233 | pr_info("__vmalloc for Beckhoff BIOS API failed\n"); 234 | return -ENOMEM; 235 | } 236 | memcpy_fromio(bbapi->memory, pos, size); 237 | bbapi->entry = bbapi->memory + offset; 238 | return 0; 239 | } 240 | 241 | /** 242 | * bbapi_find_bios() - Find BIOS in SPI flash and copy it into RAM 243 | * @bbapi: pointer to a not initialized bbapi_object 244 | * 245 | * If successful bbapi->memory and bbapi->entry point to the bios in RAM 246 | * 247 | * Return: 0 if the bios was successfully copied into RAM 248 | */ 249 | static int __init bbapi_find_bios(struct bbapi_object *bbapi) 250 | { 251 | static const size_t STEP_SIZE = 0x10; 252 | 253 | // Try to remap IO Memory to search the BIOS API in the memory 254 | if (g_bbapi_search_area > BBIOSAPI_SIGNATURE_SEARCH_AREA) { 255 | pr_warn("Search area too big\n"); 256 | return -EFAULT; 257 | } 258 | uint8_t __iomem *const start = ioremap(BBIOSAPI_SIGNATURE_PHYS_START_ADDR, 259 | g_bbapi_search_area); 260 | const uint8_t __iomem *const end = start + g_bbapi_search_area; 261 | int result = -EFAULT; 262 | uint8_t __iomem *pos; 263 | size_t off; 264 | 265 | if (start == NULL) { 266 | pr_warn("Mapping memory search area for BIOS API failed\n"); 267 | return -ENOMEM; 268 | } 269 | // Search through the remapped memory and look for the BIOS API String 270 | for (off = 0; off < STEP_SIZE; ++off) { 271 | for (pos = start + off; pos <= end - STEP_SIZE; pos += STEP_SIZE) { 272 | const uint32_t low = ioread32(pos); 273 | const uint32_t high = ioread32(pos + 4); 274 | const uint64_t lword = ((uint64_t) high << 32 | low); 275 | if (BBIOSAPI_SIGNATURE == lword) { 276 | result = bbapi_copy_bios(bbapi, pos); 277 | pr_info 278 | ("BIOS found and copied from: %p + 0x%zx | %zu\n", 279 | start, pos - start, off); 280 | goto cleanup; 281 | } 282 | } 283 | } 284 | cleanup: 285 | iounmap(start); 286 | return result; 287 | } 288 | 289 | /** 290 | * You have to hold the lock on bbapi->mutex when calling this function!!! 291 | */ 292 | static int bbapi_ioctl_mutexed(struct bbapi_object *const bbapi, 293 | const struct bbapi_struct *const cmd) 294 | { 295 | unsigned int written = 0; 296 | unsigned int ret; 297 | 298 | if (cmd->nInBufferSize > sizeof(bbapi->in)) { 299 | pr_err("%s(): nInBufferSize invalid\n", __FUNCTION__); 300 | return -EINVAL; 301 | } 302 | if (cmd->nOutBufferSize > sizeof(bbapi->out)) { 303 | pr_err("%s(): nOutBufferSize: %d invalid\n", __FUNCTION__, 304 | cmd->nOutBufferSize); 305 | return -EINVAL; 306 | } 307 | // BIOS can operate on kernel space buffers only -> make a temporary copy 308 | if (copy_from_user(bbapi->in, cmd->pInBuffer, cmd->nInBufferSize)) { 309 | pr_err("%s(): copy_from_user() failed\n", __FUNCTION__); 310 | return -EFAULT; 311 | } 312 | // Call the BIOS API 313 | ret = bbapi_call_retry(bbapi->in, bbapi->out, bbapi->entry, cmd, &written); 314 | if (ret) { 315 | pr_debug("%s(0x%x:0x%x) failed with: 0x%x\n", __func__, 316 | cmd->nIndexGroup, cmd->nIndexOffset, ret); 317 | return -(ret | BIOSAPIERR_OFFSET); 318 | } 319 | // Copy the BIOS output to the output buffer in user space 320 | if (copy_to_user(cmd->pOutBuffer, bbapi->out, written)) { 321 | pr_err("%s(): copy_to_user() failed\n", __FUNCTION__); 322 | return -EFAULT; 323 | } 324 | 325 | if (cmd->pBytesReturned) { 326 | put_user(written, cmd->pBytesReturned); 327 | } 328 | return 0; 329 | } 330 | 331 | static long bbapi_ioctl(struct file *f, unsigned int cmd, unsigned long arg) 332 | { 333 | struct bbapi_struct bbstruct; 334 | size_t size = sizeof(bbstruct); 335 | int result = -EINVAL; 336 | if (!g_bbapi.entry) { 337 | pr_warn("%s(): not initialized.\n", __FUNCTION__); 338 | return -EINVAL; 339 | } 340 | // Check if IOCTL CMD matches BBAPI Driver Command 341 | #ifdef BBAPI_CMD_LEGACY 342 | if (cmd == BBAPI_CMD_LEGACY) { 343 | size -= sizeof(bbstruct.pBytesReturned) + sizeof(bbstruct.pMode); 344 | bbstruct.pBytesReturned = NULL; 345 | bbstruct.pMode = NULL; 346 | } else 347 | #endif 348 | if (cmd != BBAPI_CMD) { 349 | pr_info("Wrong Command\n"); 350 | return -EINVAL; 351 | } 352 | // Copy data (BBAPI struct) from User Space to Kernel Module - if it fails, return error 353 | if (copy_from_user 354 | (&bbstruct, (const void __user *)arg, size)) { 355 | pr_err("copy_from_user failed\n"); 356 | return -EINVAL; 357 | } 358 | // pMode is reserved for future use 359 | if (bbstruct.pMode) { 360 | pr_info("Setting pMode to nullptr is mandatory!\n"); 361 | return -EINVAL; 362 | } 363 | 364 | if (bbstruct.nIndexOffset >= 0xB0) { 365 | pr_info("cmd: 0x%x : 0x%x not available from user mode\n", 366 | bbstruct.nIndexGroup, bbstruct.nIndexOffset); 367 | return -EACCES; 368 | } 369 | 370 | mutex_lock(&g_bbapi.mutex); 371 | result = bbapi_ioctl_mutexed(&g_bbapi, &bbstruct); 372 | mutex_unlock(&g_bbapi.mutex); 373 | return result; 374 | } 375 | 376 | static int bbapi_release(struct inode *i, struct file *f) 377 | { 378 | return 0; 379 | } 380 | 381 | static struct file_operations file_ops = { 382 | .owner = THIS_MODULE, 383 | .unlocked_ioctl = bbapi_ioctl, 384 | .release = bbapi_release, 385 | }; 386 | 387 | static void __init update_display(void) 388 | { 389 | char line[CXPWRSUPP_MAX_DISPLAY_LINE]; 390 | uint8_t enable = 0xff; 391 | 392 | snprintf(line, sizeof(line), "%s %s", UNAME_S, UTS_RELEASE); 393 | bbapi_write(BIOSIGRP_CXPWRSUPP, 394 | BIOSIOFFS_CXPWRSUPP_DISPLAYLINE2, line, sizeof(line)); 395 | 396 | bbapi_read(BIOSIGRP_GENERAL, BIOSIOFFS_GENERAL_GETBOARDNAME, line, 397 | sizeof(line)); 398 | bbapi_write(BIOSIGRP_CXPWRSUPP, BIOSIOFFS_CXPWRSUPP_DISPLAYLINE1, line, 399 | sizeof(line)); 400 | 401 | bbapi_write(BIOSIGRP_CXPWRSUPP, 402 | BIOSIOFFS_CXPWRSUPP_ENABLEBACKLIGHT, &enable, 403 | sizeof(enable)); 404 | } 405 | 406 | static void dev_release_nop(struct device *dev) 407 | { 408 | } 409 | 410 | static struct platform_device bbapi_power = { 411 | .name = "bbapi_power", 412 | .id = -1, 413 | .dev = {.release = dev_release_nop}, 414 | }; 415 | 416 | static struct platform_device bbapi_sups = { 417 | .name = "bbapi_sups", 418 | .id = -1, 419 | .dev = {.release = dev_release_nop}, 420 | }; 421 | 422 | inline static bool bbapi_supports(uint32_t group, uint32_t offset) 423 | { 424 | 425 | switch (-bbapi_read(group, offset, NULL, 0)) { 426 | case BIOSAPI_INVALIDSIZE: 427 | case BIOSAPI_INVALIDPARM: 428 | return true; 429 | default: 430 | return false; 431 | } 432 | } 433 | 434 | #define bbapi_supports_display() \ 435 | bbapi_supports(BIOSIGRP_CXPWRSUPP, BIOSIOFFS_CXPWRSUPP_ENABLEBACKLIGHT) 436 | 437 | #define bbapi_supports_power() \ 438 | bbapi_supports(BIOSIGRP_CXPWRSUPP, BIOSIOFFS_CXPWRSUPP_GETTYPE) 439 | 440 | #define bbapi_supports_sups() \ 441 | (bbapi_supports(BIOSIGRP_SUPS, BIOSIOFFS_SUPS_GPIO_PIN_EX) \ 442 | || bbapi_supports(BIOSIGRP_SUPS, BIOSIOFFS_SUPS_GPIO_PIN)) 443 | 444 | #ifdef __i386__ 445 | typedef void __iomem *(*map_func) (int64_t, uint32_t, ...); 446 | static void __iomem *ExtOsMapPhysAddr(int64_t physAddr, uint32_t memSize, ...) 447 | #else 448 | typedef __attribute__ ((ms_abi)) 449 | void __iomem *(*map_func) (int64_t, uint32_t); 450 | static __attribute__ ((ms_abi)) 451 | void __iomem *ExtOsMapPhysAddr(int64_t physAddr, uint32_t memSize) 452 | #endif 453 | { 454 | return ioremap((unsigned long)physAddr, memSize); 455 | } 456 | 457 | #ifdef __i386__ 458 | typedef void (*unmap_func) (void *pLinMem, uint32_t memSize, ...); 459 | static void ExtOsUnMapPhysAddr(void *pLinMem, uint32_t memSize, ...) 460 | #else 461 | typedef __attribute__ ((ms_abi)) 462 | void (*unmap_func) (void *pLinMem, uint32_t memSize); 463 | static __attribute__ ((ms_abi)) 464 | void ExtOsUnMapPhysAddr(void *pLinMem, uint32_t memSize) 465 | #endif 466 | { 467 | iounmap((void __iomem *)pLinMem); 468 | } 469 | 470 | struct EXTOS_FUNCTION_ENTRY { 471 | uint8_t name[8]; 472 | union { 473 | map_func map; 474 | unmap_func unmap; 475 | uint64_t placeholder; 476 | }; 477 | }; 478 | 479 | static struct EXTOS_FUNCTION_ENTRY extOsOps[] = { 480 | {"READMSR", {NULL}}, 481 | {"GETBUSDT", {NULL}}, 482 | {"MAPMEM", {.map = &ExtOsMapPhysAddr}}, 483 | {"UNMAPMEM", {.unmap = &ExtOsUnMapPhysAddr}}, 484 | {"WRITEMSR", {NULL}}, 485 | {"SETBUSDT", {NULL}}, 486 | 487 | /** MARK END OF TABLE */ 488 | {"\0\0\0\0\0\0\0\0", {NULL}}, 489 | }; 490 | 491 | static void __init bbapi_init_bios(void) 492 | { 493 | const unsigned int bios_status = 494 | bbapi_write(0, 0xFE, &extOsOps, sizeof(extOsOps)); 495 | if (bios_status) { 496 | pr_warn("Initializing BIOS failed with: 0x%x\n", bios_status); 497 | } 498 | } 499 | 500 | static void __exit bbapi_exit_bios(void) 501 | { 502 | const unsigned int bios_status = bbapi_write(0, 0xFF, NULL, 0); 503 | if (bios_status) { 504 | pr_warn("Unload BIOS failed with: 0x%x\n", bios_status); 505 | } 506 | } 507 | 508 | static const struct dmi_system_id bbapi_unsupported_list[] = { 509 | { 510 | .ident = "Hyper-V", 511 | .matches = { 512 | DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), 513 | DMI_MATCH(DMI_PRODUCT_NAME, "Virtual Machine"), 514 | DMI_MATCH(DMI_BOARD_NAME, "Virtual Machine"), 515 | }, 516 | }, 517 | { } 518 | }; 519 | 520 | static int __init bbapi_init_module(void) 521 | { 522 | int result; 523 | 524 | pr_info("%s, %s\n", DRV_DESCRIPTION, DRV_VERSION); 525 | mutex_init(&g_bbapi.mutex); 526 | 527 | if (dmi_check_system(bbapi_unsupported_list)) { 528 | pr_err("BIOS API not supported on this System!\n"); 529 | return -ENODEV; 530 | } 531 | 532 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) 533 | register_kprobe(&kp); 534 | fcn_kallsyms_lookup_name = (kallsyms_lookup_name_t)kp.addr; 535 | unregister_kprobe(&kp); 536 | 537 | fcn_vmalloc_node_range = (fcn_vmalloc_node_range_t)fcn_kallsyms_lookup_name("__vmalloc_node_range"); 538 | #endif 539 | 540 | result = bbapi_find_bios(&g_bbapi); 541 | if (result) { 542 | pr_err("BIOS API not available on this System\n"); 543 | return result; 544 | } 545 | 546 | if (bbapi_supports_power()) { 547 | result = platform_device_register(&bbapi_power); 548 | if (result) { 549 | pr_err("register %s failed\n", bbapi_power.name); 550 | goto rollback_memory; 551 | } 552 | } 553 | 554 | if (bbapi_supports_sups()) { 555 | result = platform_device_register(&bbapi_sups); 556 | if (result) { 557 | pr_err("register %s failed\n", bbapi_sups.name); 558 | goto rollback_power; 559 | } 560 | } 561 | 562 | result = 563 | simple_cdev_init(&g_bbapi.dev, "chardev", KBUILD_MODNAME, 564 | &file_ops); 565 | if (result) { 566 | pr_err("register bbapi chardev failed\n"); 567 | goto rollback_sups; 568 | } 569 | 570 | bbapi_init_bios(); 571 | 572 | if (bbapi_supports_display()) { 573 | update_display(); 574 | } 575 | return 0; 576 | 577 | rollback_sups: 578 | if (bbapi_supports_sups()) { 579 | platform_device_unregister(&bbapi_sups); 580 | } 581 | 582 | rollback_power: 583 | if (bbapi_supports_power()) { 584 | platform_device_unregister(&bbapi_power); 585 | } 586 | 587 | rollback_memory: 588 | vfree(g_bbapi.memory); 589 | return result; 590 | } 591 | 592 | static void __exit bbapi_exit(void) 593 | { 594 | if (!g_bbapi.memory) 595 | return; 596 | 597 | bbapi_exit_bios(); 598 | simple_cdev_remove(&g_bbapi.dev); 599 | 600 | if (bbapi_supports_sups()) { 601 | platform_device_unregister(&bbapi_sups); 602 | } 603 | 604 | if (bbapi_supports_power()) { 605 | platform_device_unregister(&bbapi_power); 606 | } 607 | vfree(g_bbapi.memory); 608 | } 609 | 610 | module_init(bbapi_init_module); 611 | module_exit(bbapi_exit); 612 | 613 | MODULE_DESCRIPTION(DRV_DESCRIPTION); 614 | MODULE_AUTHOR("Patrick Bruenn "); 615 | MODULE_LICENSE("GPL and additional rights"); 616 | #ifndef __FreeBSD__ 617 | MODULE_VERSION(DRV_VERSION); 618 | #else 619 | MODULE_VERSION(bbapi, 1); 620 | MODULE_DEPEND(bbapi, linuxkpi, 1, 1, 1); 621 | #endif 622 | -------------------------------------------------------------------------------- /api.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | Character Driver for Beckhoff BIOS API 4 | Author: Patrick Brünn 5 | Copyright (C) 2013 - 2018 Beckhoff Automation GmbH & Co. KG 6 | */ 7 | 8 | #ifndef __API_H_ 9 | #define __API_H_ 10 | 11 | #undef pr_fmt 12 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 13 | 14 | #include 15 | #include "simple_cdev.h" 16 | 17 | #define BBIOSAPI_SIGNATURE_PHYS_START_ADDR 0xFFE00000 // Defining the Physical start address for the search 18 | #define BBIOSAPI_SIGNATURE_SEARCH_AREA 0x001FFFFF // Defining the Memory search area size 19 | #define BBAPI_BUFFER_SIZE 256 // maximum size of a buffer shared between user and kernel space 20 | 21 | /** 22 | * struct bbapi_object - manage access to Beckhoff BIOS functions 23 | * @memory: pointer to a BIOS copy in RAM 24 | * @entry: function pointer to the BIOS API function in RAM 25 | * @in: buffer to exchange data between user space and BIOS 26 | * @out: buffer to exchange data between BIOS and user space 27 | * @dev: meta data for the character device interface 28 | * 29 | * The size of the output buffer should be large enough to satisfy the 30 | * largest BIOS command. Right now this is: BIOSIOFFS_UEEPROM_READ. 31 | */ 32 | struct bbapi_object { 33 | uint8_t *memory; 34 | void *entry; 35 | char in[BBAPI_BUFFER_SIZE]; 36 | char out[BBAPI_BUFFER_SIZE]; 37 | struct simple_cdev dev; 38 | struct mutex mutex; 39 | }; 40 | 41 | extern unsigned int bbapi_read(uint32_t group, uint32_t offset, 42 | void __kernel * out, uint32_t size); 43 | 44 | extern unsigned int bbapi_write(uint32_t group, uint32_t offset, 45 | void __kernel * in, uint32_t size); 46 | 47 | extern unsigned int bbapi_rw(uint32_t group, uint32_t offset, 48 | void __kernel * in, uint32_t size_in, 49 | void __kernel * out, uint32_t size_out, uint32_t *bytes_written); 50 | 51 | extern int bbapi_board_is(const char *boardname); 52 | #endif /* #ifndef __API_H_ */ 53 | -------------------------------------------------------------------------------- /button/Makefile: -------------------------------------------------------------------------------- 1 | TARGET = bbapi_button 2 | EXTRA_DIR = /lib/modules/$(shell uname -r)/extra/ 3 | obj-m += $(TARGET).o 4 | $(TARGET)-objs := button.o 5 | ccflags-y := -DDEBUG 6 | KBUILD_EXTRA_SYMBOLS := $(src)/../Module.symvers 7 | KDIR ?= /lib/modules/$(shell uname -r)/build 8 | 9 | all: 10 | make -C $(KDIR) M=$(PWD) modules 11 | 12 | install: 13 | - sudo rmmod $(TARGET) 14 | sudo mkdir -p $(EXTRA_DIR) 15 | sudo cp ./$(TARGET).ko $(EXTRA_DIR) 16 | sudo depmod -a 17 | sudo modprobe $(TARGET) 18 | 19 | clean: 20 | make -C $(KDIR) M=$(PWD) clean 21 | 22 | # indent the source files with the kernels Lindent script 23 | indent: button.c 24 | ../Lindent $? 25 | -------------------------------------------------------------------------------- /button/button.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | CX2100 button driver using the Beckhoff BIOS API 4 | Author: Patrick Brünn 5 | Copyright (C) 2016 - 2018 Beckhoff Automation GmbH & Co. KG 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "../api.h" 15 | #include "../TcBaDevDef.h" 16 | 17 | #define DRV_VERSION "0.2" 18 | #define DRV_DESCRIPTION "Beckhoff BIOS API button driver" 19 | 20 | #define POLL_TIME ktime_set(0, 125000 * NSEC_PER_USEC) 21 | 22 | static struct input_dev *input_dev; 23 | static struct hrtimer poll_timer; 24 | 25 | static void button_poll(struct work_struct *work) 26 | { 27 | u8 btn_state = 0; 28 | 29 | bbapi_read(BIOSIGRP_CXPWRSUPP, BIOSIOFFS_CXPWRSUPP_GETBUTTONSTATE, 30 | &btn_state, sizeof(btn_state)); 31 | input_report_abs(input_dev, ABS_X, 32 | ((btn_state >> 0) & 1) - ((btn_state >> 1) & 1)); 33 | input_report_abs(input_dev, ABS_Y, 34 | ((btn_state >> 3) & 1) - ((btn_state >> 2) & 1)); 35 | input_report_key(input_dev, BTN_0, ((btn_state >> 4) & 1)); 36 | input_sync(input_dev); 37 | } 38 | DECLARE_WORK(work, button_poll); 39 | 40 | static enum hrtimer_restart poll_timer_callback(struct hrtimer *timer) 41 | { 42 | schedule_work(&work); 43 | hrtimer_forward_now(timer, POLL_TIME); 44 | return HRTIMER_RESTART; 45 | } 46 | 47 | static int button_open(struct input_dev *dev) 48 | { 49 | hrtimer_init(&poll_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); 50 | poll_timer.function = poll_timer_callback; 51 | hrtimer_start(&poll_timer, POLL_TIME, HRTIMER_MODE_REL); 52 | return 0; 53 | } 54 | 55 | static void button_close(struct input_dev *dev) 56 | { 57 | hrtimer_cancel(&poll_timer); 58 | } 59 | 60 | static int __init button_init(void) 61 | { 62 | int error; 63 | 64 | input_dev = input_allocate_device(); 65 | if (!input_dev) { 66 | return -ENOMEM; 67 | } 68 | 69 | input_dev->name = "Beckhoff CX2100 Buttons"; 70 | input_dev->open = button_open; 71 | input_dev->close = button_close; 72 | 73 | input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); 74 | input_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0); 75 | input_set_abs_params(input_dev, ABS_X, -1, 1, 0, 0); 76 | input_set_abs_params(input_dev, ABS_Y, -1, 1, 0, 0); 77 | 78 | error = input_register_device(input_dev); 79 | if (error) { 80 | input_free_device(input_dev); 81 | } 82 | return error; 83 | } 84 | 85 | static void __exit button_exit(void) 86 | { 87 | input_unregister_device(input_dev); 88 | } 89 | 90 | module_init(button_init); 91 | module_exit(button_exit); 92 | 93 | MODULE_DESCRIPTION(DRV_DESCRIPTION); 94 | MODULE_AUTHOR("Patrick Bruenn "); 95 | MODULE_LICENSE("GPL and additional rights"); 96 | MODULE_VERSION(DRV_VERSION); 97 | -------------------------------------------------------------------------------- /config_CX01-CX5020.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | Beckhoff BIOS API driver to access hwmon sensors for Beckhoff IPCs 4 | Copyright (C) 2018 Beckhoff Automation GmbH & Co. KG 5 | Author: Patrick Bruenn 6 | */ 7 | 8 | #ifndef TEST_DEVICE 9 | #define TEST_DEVICE 0x5020 10 | 11 | /** general configuration */ 12 | #define CONFIG_GENERAL_BOARDINFO {"CX553\0", 3, 0, 128} 13 | #define CONFIG_GENERAL_BOARDNAME "CBxx53\0\0\0\0\0\0\0\0\0" 14 | #define CONFIG_GENERAL_VERSION {1, 21, 1} 15 | 16 | /** PWRCTRL configuration */ 17 | #define CONFIG_PWRCTRL_LAST_SHUTDOWN_ENABLED 0 18 | #define CONFIG_PWRCTRL_BL_REVISION {0, 14, 0} 19 | #define CONFIG_PWRCTRL_FW_REVISION {0, 17, 50} 20 | #define CONFIG_PWRCTRL_DEVICE_ID 0xD 21 | #define CONFIG_PWRCTRL_SERIAL "0000000000000000" 22 | #define CONFIG_PWRCTRL_PRODUCTION_DATE {0, 0} 23 | #define CONFIG_PWRCTRL_TEST_COUNT 0 24 | #define CONFIG_PWRCTRL_TEST_NUMBER "000000" 25 | 26 | /** S-UPS configuration */ 27 | #define CONFIG_SUPS_STATUS_OFF 0xAF 28 | 29 | /** CX Power Supply configuration */ 30 | #define CONFIG_CXPWRSUPP_DISABLED 1 31 | 32 | /** LED configuration */ 33 | #define CONFIG_LED_TC_ENABLED 1 34 | #define CONFIG_LED_USER_ENABLED 0 35 | 36 | #else 37 | #define TEST_DEVICE /* redefine to get a nice warning from gcc */ 38 | #error "TEST_DEVICE was already defined" 39 | #endif /* #ifndef TEST_DEVICE */ 40 | -------------------------------------------------------------------------------- /config_CX03-CX5140.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | Beckhoff BIOS API driver to access hwmon sensors for Beckhoff IPCs 4 | Copyright (C) 2015 - 2018 Beckhoff Automation GmbH &Co. KG 5 | Author: Patrick Bruenn 6 | */ 7 | 8 | #ifndef TEST_DEVICE 9 | #define TEST_DEVICE 0x5140 10 | 11 | /** general configuration */ 12 | #define CONFIG_GENERAL_BOARDINFO {"CX51x0\0", 2, 0, 120} 13 | #define CONFIG_GENERAL_BOARDNAME "CBxx63\0\0\0\0\0\0\0\0\0" 14 | #define CONFIG_GENERAL_VERSION {2, 20, 8} 15 | #define CONFIG_USE_GPIO_EX 1 16 | 17 | /** PWRCTRL configuration */ 18 | #define CONFIG_PWRCTRL_LAST_SHUTDOWN_ENABLED 0 19 | #define CONFIG_PWRCTRL_BL_REVISION {1, 0, 29} 20 | #define CONFIG_PWRCTRL_FW_REVISION {1, 2, 0} 21 | #define CONFIG_PWRCTRL_DEVICE_ID 0x6 22 | #define CONFIG_PWRCTRL_SERIAL "12003416390648" 23 | #define CONFIG_PWRCTRL_PRODUCTION_DATE {50, 16} 24 | #define CONFIG_PWRCTRL_TEST_COUNT 1 25 | #define CONFIG_PWRCTRL_TEST_NUMBER "151020" 26 | 27 | /** S-UPS configuration */ 28 | #define CONFIG_SUPS_STATUS_OFF 0xAA 29 | #define CONFIG_SUPS_PWRFAIL_TIMES {26883, 19117, 19118} 30 | #define CONFIG_SUPS_GPIO_PIN_EX {0, 0x4, 0x5, 0x588, 0x00400000} 31 | 32 | /** CX Power Supply configuration */ 33 | #define CONFIG_CXPWRSUPP_DISABLED 1 34 | 35 | /** LED configuration */ 36 | #define CONFIG_LED_TC_ENABLED 0 37 | #define CONFIG_LED_USER_ENABLED 0 38 | 39 | /** Watchdog configuration */ 40 | #define CONFIG_WATCHDOG_DISABLED 0 41 | 42 | #else 43 | #define TEST_DEVICE /* redefine to get a nice warning from gcc */ 44 | #error "TEST_DEVICE was already defined" 45 | #endif /* #ifndef TEST_DEVICE */ 46 | -------------------------------------------------------------------------------- /config_CX05-CX2030.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | Beckhoff BIOS API driver to access hwmon sensors for Beckhoff IPCs 4 | Copyright (C) 2014 - 2018 Beckhoff Automation GmbH & Co. KG 5 | Author: Patrick Bruenn 6 | */ 7 | 8 | #ifndef TEST_DEVICE 9 | #define TEST_DEVICE 0x21000914 10 | 11 | /** general configuration */ 12 | #define CONFIG_GENERAL_BOARDINFO {"CX20x0\0", 2, 1, 114} 13 | #define CONFIG_GENERAL_BOARDNAME "CX20x0\0\0\0\0\0\0\0\0\0" 14 | #define CONFIG_GENERAL_VERSION {2, 11, 1} 15 | 16 | /** PWRCTRL configuration */ 17 | #define CONFIG_PWRCTRL_LAST_SHUTDOWN_ENABLED 1 18 | #define CONFIG_PWRCTRL_BL_REVISION {0, 14, 0} 19 | #define CONFIG_PWRCTRL_FW_REVISION {0, 17, 54} 20 | #define CONFIG_PWRCTRL_DEVICE_ID 0xD 21 | #define CONFIG_PWRCTRL_SERIAL "09922516342481" 22 | #define CONFIG_PWRCTRL_PRODUCTION_DATE {50, 16} 23 | #define CONFIG_PWRCTRL_TEST_COUNT 1 24 | #define CONFIG_PWRCTRL_TEST_NUMBER "120200" 25 | 26 | /** S-UPS configuration */ 27 | #define CONFIG_SUPS_DISABLED 1 28 | 29 | /** CX Power Supply configuration */ 30 | #define CONFIG_CXPWRSUPP_TYPE 914 31 | #define CONFIG_CXPWRSUPP_SERIALNO 981 32 | #define CONFIG_CXPWRSUPP_FWVERSION {9, 0} 33 | 34 | /** LED configuration */ 35 | #define CONFIG_LED_TC_ENABLED 0 36 | #define CONFIG_LED_USER_ENABLED 0 37 | 38 | #else 39 | #define TEST_DEVICE /* redefine to get a nice warning from gcc */ 40 | #error "TEST_DEVICE was already defined" 41 | #endif /* #ifndef TEST_DEVICE */ 42 | -------------------------------------------------------------------------------- /config_c6015.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | Beckhoff BIOS API driver to access hwmon sensors for Beckhoff IPCs 4 | Copyright (C) 2015 - 2019 Beckhoff Automation GmbH &Co. KG 5 | Author: Patrick Bruenn 6 | */ 7 | 8 | #ifndef TEST_DEVICE 9 | #define TEST_DEVICE 0xC6015 10 | 11 | /** general configuration */ 12 | #define CONFIG_GENERAL_BOARDINFO {"CB3163\0", 5, 0, 118} 13 | #define CONFIG_GENERAL_BOARDNAME "CBxx63\0\0\0\0\0\0\0\0\0" 14 | #define CONFIG_GENERAL_VERSION {2, 20, 8} 15 | #define CONFIG_USE_GPIO_EX 1 16 | 17 | /** PWRCTRL configuration */ 18 | #define CONFIG_PWRCTRL_LAST_SHUTDOWN_ENABLED 0 19 | #define CONFIG_PWRCTRL_BL_REVISION {1, 0, 31} 20 | #define CONFIG_PWRCTRL_FW_REVISION {1, 2, 0} 21 | #define CONFIG_PWRCTRL_DEVICE_ID 0x6 22 | #define CONFIG_PWRCTRL_SERIAL "16272917280074" 23 | #define CONFIG_PWRCTRL_PRODUCTION_DATE {30, 17} 24 | #define CONFIG_PWRCTRL_TEST_COUNT 1 25 | #define CONFIG_PWRCTRL_TEST_NUMBER "131630" 26 | 27 | /** S-UPS configuration */ 28 | #define CONFIG_SUPS_DISABLED 1 29 | 30 | /** CX Power Supply configuration */ 31 | #define CONFIG_CXPWRSUPP_DISABLED 1 32 | 33 | /** LED configuration */ 34 | #define CONFIG_LED_TC_ENABLED 1 35 | #define CONFIG_LED_USER_ENABLED 0 36 | 37 | /** Watchdog configuration */ 38 | #define CONFIG_WATCHDOG_DISABLED 1 39 | 40 | #else 41 | #define TEST_DEVICE /* redefine to get a nice warning from gcc */ 42 | #error "TEST_DEVICE was already defined" 43 | #endif /* #ifndef TEST_DEVICE */ 44 | -------------------------------------------------------------------------------- /config_cx2030_cx2100-0004.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | Beckhoff BIOS API driver to access hwmon sensors for Beckhoff IPCs 4 | Copyright (C) 2014 - 2019 Beckhoff Automation GmbH & Co. KG 5 | Author: Patrick Bruenn 6 | */ 7 | 8 | #ifndef TEST_DEVICE 9 | #define TEST_DEVICE 0x21000004 10 | 11 | /** general configuration */ 12 | #define CONFIG_GENERAL_BOARDINFO {"CX20x0\0", 1, 1, 52} 13 | #define CONFIG_GENERAL_BOARDNAME "CX20x0\0\0\0\0\0\0\0\0\0" 14 | #define CONFIG_GENERAL_VERSION {2, 9, 1} 15 | 16 | /** PWRCTRL configuration */ 17 | #define CONFIG_PWRCTRL_LAST_SHUTDOWN_ENABLED 1 18 | #define CONFIG_PWRCTRL_BL_REVISION {0, 14, 0} 19 | #define CONFIG_PWRCTRL_FW_REVISION {0, 17, 36} 20 | #define CONFIG_PWRCTRL_DEVICE_ID 0xC 21 | #define CONFIG_PWRCTRL_SERIAL "0000000000000000" 22 | #define CONFIG_PWRCTRL_PRODUCTION_DATE {0, 0} 23 | #define CONFIG_PWRCTRL_TEST_COUNT 0 24 | #define CONFIG_PWRCTRL_TEST_NUMBER "000000" 25 | 26 | /** S-UPS configuration */ 27 | #define CONFIG_SUPS_DISABLED 1 28 | 29 | /** CX Power Supply configuration */ 30 | #define CONFIG_CXPWRSUPP_TYPE 4 31 | #define CONFIG_CXPWRSUPP_SERIALNO 1467 32 | #define CONFIG_CXPWRSUPP_FWVERSION {5, 0} 33 | 34 | /** LED configuration */ 35 | #define CONFIG_LED_TC_ENABLED 0 36 | #define CONFIG_LED_USER_ENABLED 0 37 | 38 | #else 39 | #define TEST_DEVICE /* redefine to get a nice warning from gcc */ 40 | #error "TEST_DEVICE was already defined" 41 | #endif /* #ifndef TEST_DEVICE */ 42 | -------------------------------------------------------------------------------- /config_cx2030_cx2100-0904.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | Beckhoff BIOS API driver to access hwmon sensors for Beckhoff IPCs 4 | Copyright (C) 2014 - 2019 Beckhoff Automation GmbH & Co. KG 5 | Author: Patrick Bruenn 6 | */ 7 | 8 | #ifndef TEST_DEVICE 9 | #define TEST_DEVICE 0x21000904 10 | 11 | /** general configuration */ 12 | #define CONFIG_GENERAL_BOARDINFO {"CX20x0\0", 2, 1, 100} 13 | #define CONFIG_GENERAL_BOARDNAME "CX20x0\0\0\0\0\0\0\0\0\0" 14 | #define CONFIG_GENERAL_VERSION {2, 11, 1} 15 | 16 | /** PWRCTRL configuration */ 17 | #define CONFIG_PWRCTRL_LAST_SHUTDOWN_ENABLED 1 18 | #define CONFIG_PWRCTRL_BL_REVISION {0, 14, 0} 19 | #define CONFIG_PWRCTRL_FW_REVISION {0, 17, 54} 20 | #define CONFIG_PWRCTRL_DEVICE_ID 0xD 21 | #define CONFIG_PWRCTRL_SERIAL "09922514060066" 22 | #define CONFIG_PWRCTRL_PRODUCTION_DATE {16, 14} 23 | #define CONFIG_PWRCTRL_TEST_COUNT 1 24 | #define CONFIG_PWRCTRL_TEST_NUMBER "120200" 25 | 26 | /** S-UPS configuration */ 27 | #define CONFIG_SUPS_DISABLED 1 28 | 29 | /** CX Power Supply configuration */ 30 | #define CONFIG_CXPWRSUPP_TYPE 904 31 | #define CONFIG_CXPWRSUPP_SERIALNO 834 32 | #define CONFIG_CXPWRSUPP_FWVERSION {5, 0} 33 | 34 | /** LED configuration */ 35 | #define CONFIG_LED_TC_ENABLED 0 36 | #define CONFIG_LED_USER_ENABLED 0 37 | 38 | #else 39 | #define TEST_DEVICE /* redefine to get a nice warning from gcc */ 40 | #error "TEST_DEVICE was already defined" 41 | #endif /* #ifndef TEST_DEVICE */ 42 | -------------------------------------------------------------------------------- /config_cx5000.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | Beckhoff BIOS API driver to access hwmon sensors for Beckhoff IPCs 4 | Copyright (C) 2018 - 2019 Beckhoff Automation GmbH & Co. KG 5 | Author: Patrick Bruenn 6 | */ 7 | 8 | #ifndef TEST_DEVICE 9 | #define TEST_DEVICE 0x5000 10 | 11 | /** general configuration */ 12 | #define CONFIG_GENERAL_BOARDINFO {"CX553\0", 1, 0, 128} 13 | #define CONFIG_GENERAL_BOARDNAME "CBxx53\0\0\0\0\0\0\0\0\0" 14 | #define CONFIG_GENERAL_VERSION {1, 21, 1} 15 | 16 | /** PWRCTRL configuration */ 17 | #define CONFIG_PWRCTRL_LAST_SHUTDOWN_ENABLED 0 18 | #define CONFIG_PWRCTRL_BL_REVISION {0, 14, 0} 19 | #define CONFIG_PWRCTRL_FW_REVISION {0, 17, 50} 20 | #define CONFIG_PWRCTRL_DEVICE_ID 0xD 21 | #define CONFIG_PWRCTRL_SERIAL "0000000000000000" 22 | #define CONFIG_PWRCTRL_PRODUCTION_DATE {0, 0} 23 | #define CONFIG_PWRCTRL_TEST_COUNT 0 24 | #define CONFIG_PWRCTRL_TEST_NUMBER "000000" 25 | 26 | /** S-UPS configuration */ 27 | #define CONFIG_SUPS_STATUS_OFF 0xAF 28 | 29 | /** CX Power Supply configuration */ 30 | #define CONFIG_CXPWRSUPP_DISABLED 1 31 | 32 | /** LED configuration */ 33 | #define CONFIG_LED_TC_ENABLED 1 34 | #define CONFIG_LED_USER_ENABLED 0 35 | 36 | #else 37 | #define TEST_DEVICE /* redefine to get a nice warning from gcc */ 38 | #error "TEST_DEVICE was already defined" 39 | #endif /* #ifndef TEST_DEVICE */ 40 | -------------------------------------------------------------------------------- /config_cx5130.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | Beckhoff BIOS API driver to access hwmon sensors for Beckhoff IPCs 4 | Copyright (C) 2015 - 2019 Beckhoff Automation GmbH &Co. KG 5 | Author: Patrick Bruenn 6 | */ 7 | 8 | #ifndef TEST_DEVICE 9 | #define TEST_DEVICE 0x5130 10 | 11 | /** general configuration */ 12 | #define CONFIG_GENERAL_BOARDINFO {"CX51x0\0", 1, 0, 81} 13 | #define CONFIG_GENERAL_BOARDNAME "CBxx63\0\0\0\0\0\0\0\0\0" 14 | #define CONFIG_GENERAL_VERSION {2, 16, 1} 15 | #define CONFIG_USE_GPIO_EX 1 16 | 17 | /** PWRCTRL configuration */ 18 | #define CONFIG_PWRCTRL_LAST_SHUTDOWN_ENABLED 0 19 | #define CONFIG_PWRCTRL_BL_REVISION {1, 0, 26} 20 | #define CONFIG_PWRCTRL_FW_REVISION {1, 0, 97} 21 | #define CONFIG_PWRCTRL_DEVICE_ID 0x6 22 | #define CONFIG_PWRCTRL_SERIAL "12003415020116" 23 | #define CONFIG_PWRCTRL_PRODUCTION_DATE {7, 15} 24 | #define CONFIG_PWRCTRL_TEST_COUNT 1 25 | #define CONFIG_PWRCTRL_TEST_NUMBER "151020" 26 | 27 | /** S-UPS configuration */ 28 | #define CONFIG_SUPS_STATUS_OFF 0xAA 29 | #define CONFIG_SUPS_GPIO_PIN_EX {0, 0x4, 0x5, 0x588, 0x00400000} 30 | 31 | /** CX Power Supply configuration */ 32 | #define CONFIG_CXPWRSUPP_DISABLED 1 33 | 34 | /** LED configuration */ 35 | #define CONFIG_LED_TC_ENABLED 0 36 | #define CONFIG_LED_USER_ENABLED 0 37 | 38 | /** Watchdog configuration */ 39 | #define CONFIG_WATCHDOG_DISABLED 0 40 | 41 | #else 42 | #define TEST_DEVICE /* redefine to get a nice warning from gcc */ 43 | #error "TEST_DEVICE was already defined" 44 | #endif /* #ifndef TEST_DEVICE */ 45 | -------------------------------------------------------------------------------- /display/Makefile: -------------------------------------------------------------------------------- 1 | TARGET = bbapi_display 2 | EXTRA_DIR = /lib/modules/$(shell uname -r)/extra/ 3 | obj-m += $(TARGET).o 4 | $(TARGET)-objs := display.o 5 | ccflags-y := -DDEBUG 6 | KBUILD_EXTRA_SYMBOLS := $(src)/../Module.symvers 7 | KDIR ?= /lib/modules/$(shell uname -r)/build 8 | 9 | all: 10 | make -C $(KDIR) M=$(PWD) modules 11 | 12 | install: 13 | - sudo rmmod $(TARGET) 14 | sudo mkdir -p $(EXTRA_DIR) 15 | sudo cp ./$(TARGET).ko $(EXTRA_DIR) 16 | sudo depmod -a 17 | sudo modprobe $(TARGET) 18 | 19 | clean: 20 | make -C $(KDIR) M=$(PWD) clean 21 | 22 | # indent the source files with the kernels Lindent script 23 | indent: display.c 24 | ../Lindent $? 25 | -------------------------------------------------------------------------------- /display/display.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | Text display driver using the Beckhoff BIOS API 4 | Author: Patrick Brünn 5 | Copyright (C) 2014-2018 Beckhoff Automation GmbH & Co. KG 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #if (LINUX_VERSION_CODE < KERNEL_VERSION(4,12,0)) 16 | #include 17 | #else 18 | #include 19 | #endif 20 | 21 | #include "../api.h" 22 | #include "../TcBaDevDef.h" 23 | 24 | #define DRV_VERSION "0.3" 25 | #define DRV_DESCRIPTION "Beckhoff BIOS API text display driver" 26 | 27 | struct display_buffer { 28 | size_t row; 29 | size_t col; 30 | }; 31 | 32 | #define NUM_ROWS ((size_t)2) 33 | #define NUM_COLS ((size_t)16) 34 | static char g_fb[NUM_ROWS][NUM_COLS + 1]; 35 | static DEFINE_MUTEX(g_mutex); 36 | 37 | static void fb_init(void) 38 | { 39 | mutex_lock(&g_mutex); 40 | memset(g_fb, ' ', sizeof(g_fb)); 41 | g_fb[0][NUM_COLS] = '\0'; 42 | g_fb[1][NUM_COLS] = '\0'; 43 | mutex_unlock(&g_mutex); 44 | } 45 | 46 | static int display_open(struct inode *const i, struct file *const f) 47 | { 48 | f->private_data = kzalloc(sizeof(struct display_buffer), GFP_KERNEL); 49 | return NULL == f->private_data; 50 | } 51 | 52 | static int display_release(struct inode *const i, struct file *const f) 53 | { 54 | kfree(f->private_data); 55 | return 0; 56 | } 57 | 58 | static ssize_t display_write(struct file *const f, const char __user * buf, 59 | size_t len, loff_t * off) 60 | { 61 | struct display_buffer *const db = f->private_data; 62 | size_t pos = 0; 63 | char next; 64 | 65 | while (pos < len) { 66 | get_user(next, buf + pos); 67 | if (isprint(next) && db->row < NUM_ROWS) { 68 | g_fb[db->row][db->col] = next; 69 | db->col++; 70 | } else { 71 | switch (next) { 72 | case '\b': /* move cursor to previous character */ 73 | if (db->col) { 74 | db->col--; 75 | } else if (db->row) { 76 | db->col = NUM_COLS - 1; 77 | db->row--; 78 | } 79 | break; 80 | case '\t': /* move cursor to next character */ 81 | db->col++; 82 | break; 83 | case '\n': /* move cursor to start of next row */ 84 | db->col = 0; 85 | db->row++; 86 | break; 87 | case '\f': /* clear screen and move cursor to column 0 of row 0 */ 88 | db->col = 0; 89 | db->row = 0; 90 | fb_init(); 91 | break; 92 | case '\r': /* move cursor to first character in row */ 93 | db->col = 0; 94 | break; 95 | case '\021': /* enable backlight */ 96 | { 97 | u8 enable = 0xff; 98 | bbapi_write(BIOSIGRP_CXPWRSUPP, 99 | BIOSIOFFS_CXPWRSUPP_ENABLEBACKLIGHT, 100 | &enable, sizeof(enable)); 101 | break; 102 | } 103 | case '\023': /* disable backlight */ 104 | { 105 | u8 disable = 0; 106 | bbapi_write(BIOSIGRP_CXPWRSUPP, 107 | BIOSIOFFS_CXPWRSUPP_ENABLEBACKLIGHT, 108 | &disable, sizeof(disable)); 109 | break; 110 | } 111 | default: 112 | break; 113 | } 114 | } 115 | 116 | /* calculate new positions */ 117 | if (db->col >= NUM_COLS) { 118 | db->col = 0; 119 | db->row = min(NUM_ROWS, db->row + 1); 120 | } 121 | ++pos; 122 | } 123 | 124 | bbapi_write(BIOSIGRP_CXPWRSUPP, BIOSIOFFS_CXPWRSUPP_DISPLAYLINE1, 125 | g_fb[0], sizeof(g_fb[0])); 126 | bbapi_write(BIOSIGRP_CXPWRSUPP, BIOSIOFFS_CXPWRSUPP_DISPLAYLINE2, 127 | g_fb[1], sizeof(g_fb[0])); 128 | return len; 129 | } 130 | 131 | static struct file_operations display_ops = { 132 | .owner = THIS_MODULE, 133 | .open = display_open, 134 | .release = display_release, 135 | .write = display_write, 136 | }; 137 | 138 | static struct miscdevice display_device = { 139 | .minor = MISC_DYNAMIC_MINOR, 140 | .name = "cx_display", 141 | .fops = &display_ops, 142 | }; 143 | 144 | static int __init bbapi_display_init_module(void) 145 | { 146 | pr_info("%s, %s\n", DRV_DESCRIPTION, DRV_VERSION); 147 | fb_init(); 148 | return misc_register(&display_device); 149 | } 150 | 151 | static void __exit bbapi_display_exit(void) 152 | { 153 | misc_deregister(&display_device); 154 | pr_info("Text display unregistered\n"); 155 | } 156 | 157 | module_init(bbapi_display_init_module); 158 | module_exit(bbapi_display_exit); 159 | 160 | MODULE_DESCRIPTION(DRV_DESCRIPTION); 161 | MODULE_AUTHOR("Patrick Bruenn "); 162 | MODULE_LICENSE("GPL and additional rights"); 163 | MODULE_VERSION(DRV_VERSION); 164 | -------------------------------------------------------------------------------- /display_example.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | Example code to demonstrate the usage of the CX2100 power supply display 4 | 5 | Some ASCII control characters are used to control the display behavior: 6 | '\021': switch the backlight on 7 | '\023': switch the backlight off 8 | '\f': clear screen and move cursor to the first character of the first row 9 | '\b': move cursor to previous character 10 | '\n': move cursor to start of next row 11 | '\r': move cursor to first character in row 12 | '\t': move cursor to next character 13 | 14 | Copyright (C) 2014 - 2018 Beckhoff Automation GmbH & Co. KG 15 | Author: Patrick Bruenn 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #define DEVICE_NAME "/dev/cx_display" 25 | 26 | /** for delay and multithreading only */ 27 | #include 28 | #include 29 | #include 30 | 31 | void backlight_example() 32 | { 33 | static const char off[] = "\023\fbacklight is off"; 34 | static const char on[] = "\021backlight is on\n"; 35 | const int fd = open(DEVICE_NAME, O_WRONLY); 36 | if (-1 == fd) { 37 | perror(NULL); 38 | return; 39 | } 40 | 41 | write(fd, off, sizeof(off)); 42 | std::this_thread::sleep_for(std::chrono::seconds(2)); 43 | write(fd, on, sizeof(on)); 44 | std::this_thread::sleep_for(std::chrono::seconds(2)); 45 | close(fd); 46 | } 47 | 48 | void simple_example() 49 | { 50 | const int fd = open(DEVICE_NAME, O_WRONLY); 51 | if (-1 == fd) { 52 | perror(NULL); 53 | return; 54 | } 55 | 56 | static const char first[] = "\fHello world!"; 57 | write(fd, first, sizeof(first)); 58 | std::this_thread::sleep_for(std::chrono::seconds(2)); 59 | static const char second[] = "\fI am alive!\nand duallined!"; 60 | write(fd, second, sizeof(second)); 61 | std::this_thread::sleep_for(std::chrono::seconds(2)); 62 | close(fd); 63 | } 64 | 65 | void complex_example() 66 | { 67 | const int fd = open(DEVICE_NAME, O_WRONLY); 68 | if (-1 == fd) { 69 | perror(NULL); 70 | return; 71 | } 72 | 73 | static const char clearscreen[] = "\fHello there?"; 74 | write(fd, clearscreen, sizeof(clearscreen)); 75 | std::this_thread::sleep_for(std::chrono::seconds(2)); 76 | 77 | static const char overwrite_char[] = "\b\b\b\b\b\bworld!"; 78 | write(fd, overwrite_char, sizeof(overwrite_char)); 79 | std::this_thread::sleep_for(std::chrono::seconds(2)); 80 | 81 | static const char append[] = "\nI am alive?"; 82 | write(fd, append, sizeof(append)); 83 | std::this_thread::sleep_for(std::chrono::seconds(2)); 84 | 85 | static const char overwrite_line[] = "\rIt's\t\t\t\t\t\t!"; 86 | write(fd, overwrite_line, sizeof(overwrite_line)); 87 | std::this_thread::sleep_for(std::chrono::seconds(2)); 88 | close(fd); 89 | } 90 | 91 | void RunAtPos(size_t pos) 92 | { 93 | const int fd = open(DEVICE_NAME, O_WRONLY); 94 | if (-1 == fd) { 95 | perror(NULL); 96 | return; 97 | } 98 | 99 | /** move cursor to our position */ 100 | for (size_t i = 0; i <= pos; ++i) { 101 | static const char tab = '\t'; 102 | write(fd, &tab, 1); 103 | } 104 | 105 | /** print some characters */ 106 | for (unsigned char c = '~'; c != ('/' + pos % 10); c--) { 107 | const unsigned char replace_char[2] = { '\b', c }; 108 | write(fd, replace_char, sizeof(replace_char)); 109 | /** sleep a little to let the other threads work, too */ 110 | std::this_thread::sleep_for(std::chrono::milliseconds(50)); 111 | } 112 | close(fd); 113 | } 114 | 115 | void multithreaded_example() 116 | { 117 | std::vector < std::thread > threads(32); 118 | for (size_t i = 0; i < threads.size(); ++i) { 119 | threads[i] = std::thread(&RunAtPos, i); 120 | } 121 | 122 | for (auto & t:threads) { 123 | t.join(); 124 | } 125 | } 126 | 127 | int main(int argc, char *argv[]) 128 | { 129 | /** 130 | * use ASCII code DC1(XON) and DC3(XOFF) to control the backlight 131 | */ 132 | backlight_example(); 133 | 134 | /** 135 | * Only one thread accesses the display 136 | */ 137 | simple_example(); 138 | complex_example(); 139 | 140 | /** 141 | * Multiple threads writing simultanously to different areas of the display 142 | */ 143 | multithreaded_example(); 144 | return 0; 145 | } 146 | -------------------------------------------------------------------------------- /linuxkpi/linux/platform_device.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | FreeBSD wrapper to reuse Beckhoff BIOS API Linux driver 4 | Copyright (C) 2019 Beckhoff Automation GmbH & Co. KG 5 | Author: Patrick Bruenn 6 | */ 7 | 8 | #include 9 | 10 | #define platform_device_register(x) 0 11 | #define platform_device_unregister(x) 0 12 | struct platform_device { 13 | const char *const name; 14 | const int id; 15 | const struct { 16 | void(*release)(struct device*); 17 | } dev; 18 | }; 19 | -------------------------------------------------------------------------------- /linuxkpi/linux/version.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | FreeBSD wrapper to reuse Beckhoff BIOS API Linux driver 4 | Copyright (C) 2019 Beckhoff Automation GmbH & Co. KG 5 | Author: Patrick Bruenn 6 | */ 7 | 8 | #define LINUX_VERSION_CODE 0 9 | #define KERNEL_VERSION(a, b, c) 0xffffffff 10 | -------------------------------------------------------------------------------- /power/Makefile: -------------------------------------------------------------------------------- 1 | TARGET = bbapi_power 2 | EXTRA_DIR = /lib/modules/$(shell uname -r)/extra/ 3 | obj-m += $(TARGET).o 4 | $(TARGET)-objs := power.o 5 | ccflags-y := -DDEBUG 6 | KBUILD_EXTRA_SYMBOLS := $(src)/../Module.symvers 7 | KDIR ?= /lib/modules/$(shell uname -r)/build 8 | 9 | all: 10 | make -C $(KDIR) M=$(PWD) modules 11 | 12 | install: 13 | - sudo rmmod $(TARGET) 14 | sudo mkdir -p $(EXTRA_DIR) 15 | sudo cp ./$(TARGET).ko $(EXTRA_DIR) 16 | sudo depmod -a 17 | sudo modprobe $(TARGET) 18 | 19 | clean: 20 | make -C $(KDIR) M=$(PWD) clean 21 | 22 | # indent the source files with the kernels Lindent script 23 | indent: power.c 24 | ../Lindent $? 25 | -------------------------------------------------------------------------------- /power/power.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | CX power supply driver using the Beckhoff BIOS API 4 | Author: Patrick Brünn 5 | Copyright (C) 2015 - 2018 Beckhoff Automation GmbH & Co. KG 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "../api.h" 16 | #include "../TcBaDevDef.h" 17 | 18 | #define DRV_VERSION "0.3" 19 | #define DRV_DESCRIPTION "Beckhoff BIOS API power supply driver" 20 | 21 | struct bbapi_cx2100_info { 22 | const char *manufacturer; 23 | char serial[16]; 24 | 25 | struct workqueue_struct *monitor_wqueue; 26 | struct delayed_work monitor; 27 | struct power_supply *psy; 28 | 29 | uint16_t charging_current_mA; 30 | uint8_t capacity_percent; 31 | uint8_t power_status; 32 | uint8_t battery_present; 33 | uint32_t battery_runtime_s; 34 | char temp_C; 35 | }; 36 | 37 | static int ups_get_status(const struct bbapi_cx2100_info *pbi) 38 | { 39 | #define CXUPS_POWERSTATUS_ONBATTERIES 0x02 40 | #define CXUPS_POWERSTATUS_ONLINE 0x01 41 | 42 | if (CXUPS_POWERSTATUS_ONBATTERIES == pbi->power_status) { 43 | return POWER_SUPPLY_STATUS_DISCHARGING; 44 | } 45 | 46 | if (CXUPS_POWERSTATUS_ONLINE != pbi->power_status) { 47 | return POWER_SUPPLY_STATUS_UNKNOWN; 48 | } 49 | 50 | if (pbi->capacity_percent >= 100) { 51 | return POWER_SUPPLY_STATUS_FULL; 52 | } 53 | 54 | if (pbi->charging_current_mA) { 55 | return POWER_SUPPLY_STATUS_CHARGING; 56 | } 57 | return POWER_SUPPLY_STATUS_NOT_CHARGING; 58 | } 59 | 60 | static void bbapi_cx2100_read_status(struct bbapi_cx2100_info *pbi) 61 | { 62 | bbapi_read(BIOSIGRP_CXPWRSUPP, BIOSIOFFS_CXPWRSUPP_GETTEMP, 63 | &pbi->temp_C, sizeof(pbi->temp_C)); 64 | bbapi_read(BIOSIGRP_CXUPS, BIOSIOFFS_CXUPS_GETPOWERSTATUS, 65 | &pbi->power_status, sizeof(pbi->power_status)); 66 | bbapi_read(BIOSIGRP_CXUPS, BIOSIOFFS_CXUPS_GETBATTERYPRESENT, 67 | &pbi->battery_present, sizeof(pbi->battery_present)); 68 | bbapi_read(BIOSIGRP_CXUPS, BIOSIOFFS_CXUPS_GETBATTERYCAPACITY, 69 | &pbi->capacity_percent, sizeof(pbi->capacity_percent)); 70 | bbapi_read(BIOSIGRP_CXUPS, BIOSIOFFS_CXUPS_GETBATTERYRUNTIME, 71 | &pbi->battery_runtime_s, sizeof(pbi->battery_runtime_s)); 72 | bbapi_read(BIOSIGRP_CXUPS, BIOSIOFFS_CXUPS_GETCHARGINGCURRENT, 73 | &pbi->charging_current_mA, sizeof(pbi->charging_current_mA)); 74 | } 75 | 76 | static void bbapi_power_monitor(struct work_struct *work) 77 | { 78 | struct bbapi_cx2100_info *pbi = container_of(work, 79 | struct bbapi_cx2100_info, 80 | monitor.work); 81 | 82 | bbapi_cx2100_read_status(pbi); 83 | if (pbi->psy) { 84 | power_supply_changed(pbi->psy); 85 | } 86 | queue_delayed_work(pbi->monitor_wqueue, &pbi->monitor, HZ * 5); 87 | } 88 | 89 | static enum power_supply_property cx2100_0904_props[] = { 90 | POWER_SUPPLY_PROP_STATUS, 91 | POWER_SUPPLY_PROP_PRESENT, 92 | POWER_SUPPLY_PROP_ONLINE, 93 | POWER_SUPPLY_PROP_CAPACITY, 94 | POWER_SUPPLY_PROP_TEMP, 95 | POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, 96 | POWER_SUPPLY_PROP_MODEL_NAME, 97 | POWER_SUPPLY_PROP_MANUFACTURER, 98 | POWER_SUPPLY_PROP_SERIAL_NUMBER, 99 | }; 100 | 101 | static int bbapi_power_get_property(struct power_supply *psy, 102 | enum power_supply_property psp, 103 | union power_supply_propval *val) 104 | { 105 | const struct bbapi_cx2100_info *pbi = power_supply_get_drvdata(psy); 106 | int ret = 0; 107 | 108 | switch (psp) { 109 | case POWER_SUPPLY_PROP_STATUS: 110 | val->intval = ups_get_status(pbi); 111 | break; 112 | case POWER_SUPPLY_PROP_PRESENT: 113 | val->intval = pbi->battery_present; 114 | break; 115 | case POWER_SUPPLY_PROP_ONLINE: 116 | val->intval = !(pbi->power_status == CXUPS_POWERSTATUS_ONLINE); 117 | break; 118 | case POWER_SUPPLY_PROP_CAPACITY: 119 | val->intval = pbi->capacity_percent; 120 | break; 121 | case POWER_SUPPLY_PROP_TEMP: 122 | val->intval = 10 * pbi->temp_C; 123 | break; 124 | case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: 125 | val->intval = pbi->battery_runtime_s; 126 | break; 127 | case POWER_SUPPLY_PROP_MODEL_NAME: 128 | val->strval = psy->desc->name; 129 | break; 130 | case POWER_SUPPLY_PROP_MANUFACTURER: 131 | val->strval = pbi->manufacturer; 132 | break; 133 | case POWER_SUPPLY_PROP_SERIAL_NUMBER: 134 | val->strval = pbi->serial; 135 | break; 136 | default: 137 | ret = -EINVAL; 138 | } 139 | return ret; 140 | } 141 | 142 | static const struct power_supply_desc cx2100_0914_desc = { 143 | .name = "cx2100_0914", 144 | .type = POWER_SUPPLY_TYPE_BATTERY, 145 | .properties = cx2100_0904_props, 146 | .num_properties = ARRAY_SIZE(cx2100_0904_props), 147 | .get_property = bbapi_power_get_property, 148 | }; 149 | 150 | static const struct power_supply_desc cx2100_0904_desc = { 151 | .name = "cx2100_0904", 152 | .type = POWER_SUPPLY_TYPE_BATTERY, 153 | .properties = cx2100_0904_props, 154 | .num_properties = ARRAY_SIZE(cx2100_0904_props), 155 | .get_property = bbapi_power_get_property, 156 | }; 157 | 158 | #define cx2100_read(offset, buffer) \ 159 | bbapi_read(BIOSIGRP_CXPWRSUPP, offset, &buffer, sizeof(buffer)) 160 | 161 | static int init_workqueue(struct bbapi_cx2100_info *pbi, struct device *parent) 162 | { 163 | INIT_DELAYED_WORK(&pbi->monitor, bbapi_power_monitor); 164 | pbi->monitor_wqueue = create_singlethread_workqueue(dev_name(parent)); 165 | if (!pbi->monitor_wqueue) { 166 | dev_err(parent, "wqueue init failed\n"); 167 | return -ESRCH; 168 | } 169 | 170 | if (!queue_delayed_work(pbi->monitor_wqueue, &pbi->monitor, HZ * 1)) { 171 | destroy_workqueue(pbi->monitor_wqueue); 172 | return -ESRCH; 173 | } 174 | return 0; 175 | } 176 | 177 | static int init_cx2100_09x4(struct bbapi_cx2100_info *pbi, 178 | const struct power_supply_desc *desc, 179 | struct device *parent) 180 | { 181 | static struct power_supply_config psy_cfg = { }; 182 | uint32_t serial = 0; 183 | 184 | cx2100_read(BIOSIOFFS_CXPWRSUPP_GETSERIALNO, serial); 185 | snprintf(pbi->serial, sizeof(pbi->serial), "%u", serial); 186 | 187 | pbi->manufacturer = "Beckhoff Automation"; 188 | 189 | psy_cfg.drv_data = pbi; 190 | pbi->psy = power_supply_register(parent, desc, &psy_cfg); 191 | if (IS_ERR(pbi->psy)) { 192 | dev_err(parent, "failed to register power supply\n"); 193 | return PTR_ERR(pbi->psy); 194 | } 195 | return init_workqueue(pbi, parent); 196 | } 197 | 198 | static int bbapi_power_init(struct bbapi_cx2100_info *pbi, 199 | struct device *parent) 200 | { 201 | #define CXPWRSUPP_TYPE_CX2100_0914 914u 202 | #define CXPWRSUPP_TYPE_CX2100_0904 904u 203 | #define CXPWRSUPP_TYPE_CX2100_0014 14u 204 | #define CXPWRSUPP_TYPE_CX2100_0004 4u 205 | uint32_t type; 206 | 207 | if (!cx2100_read(BIOSIOFFS_CXPWRSUPP_GETTYPE, type)) { 208 | switch (type) { 209 | case CXPWRSUPP_TYPE_CX2100_0914: 210 | return init_cx2100_09x4(pbi, &cx2100_0914_desc, parent); 211 | case CXPWRSUPP_TYPE_CX2100_0904: 212 | return init_cx2100_09x4(pbi, &cx2100_0904_desc, parent); 213 | case CXPWRSUPP_TYPE_CX2100_0014: 214 | return -ENODEV; 215 | case CXPWRSUPP_TYPE_CX2100_0004: 216 | return -ENODEV; 217 | default: 218 | pr_warn("unknown cx2100 type: %x|%u\n", type, type); 219 | return -ENODEV; 220 | } 221 | } 222 | return -ENODEV; 223 | } 224 | 225 | static int bbapi_power_probe(struct platform_device *pdev) 226 | { 227 | struct bbapi_cx2100_info *pbi; 228 | int status = 0; 229 | 230 | pbi = kzalloc(sizeof(*pbi), GFP_KERNEL); 231 | if (!pbi) { 232 | return -ENOMEM; 233 | } 234 | dev_set_drvdata(&pdev->dev, pbi); 235 | 236 | status = bbapi_power_init(pbi, &pdev->dev); 237 | if (status) { 238 | kfree(pbi); 239 | } 240 | return status; 241 | } 242 | 243 | static int bbapi_power_remove(struct platform_device *pdev) 244 | { 245 | struct bbapi_cx2100_info *pbi = platform_get_drvdata(pdev); 246 | 247 | if (pbi->psy) { 248 | cancel_delayed_work_sync(&pbi->monitor); 249 | destroy_workqueue(pbi->monitor_wqueue); 250 | power_supply_unregister(pbi->psy); 251 | } 252 | kfree(pbi); 253 | return 0; 254 | } 255 | 256 | static struct platform_driver bbapi_power_driver = { 257 | .driver = { 258 | .name = KBUILD_MODNAME, 259 | }, 260 | .probe = bbapi_power_probe, 261 | .remove = bbapi_power_remove, 262 | }; 263 | 264 | module_platform_driver(bbapi_power_driver); 265 | MODULE_DESCRIPTION(DRV_DESCRIPTION); 266 | MODULE_AUTHOR("Patrick Bruenn "); 267 | MODULE_LICENSE("GPL and additional rights"); 268 | MODULE_VERSION(DRV_VERSION); 269 | -------------------------------------------------------------------------------- /scripts/cx2100_demo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | JOYSTICK=${1:-/dev/input/js0} 4 | DISPLAY=/dev/cx_display 5 | PIPE=test.pipe 6 | 7 | page=0 8 | my_pages=( show_date "show_eth 0" "show_eth 1" show_load show_mem) 9 | 10 | show_date() { 11 | echo -e "\f`date +"%d.%m.%Y\n%H:%M:%S"`" > ${DISPLAY} 12 | } 13 | 14 | show_eth() { 15 | echo -e "\feth$1:\n`ifconfig eth$1 | grep "inet " | awk -F'[: ]+' '{ print $4 }'`" > ${DISPLAY} 16 | } 17 | 18 | show_load() { 19 | echo -e "\fload:\n`cat /proc/loadavg | awk '{print $1,$2,$3}'`" > ${DISPLAY} 20 | } 21 | 22 | show_mem() { 23 | echo -e "\fmem: used free\n`free -h | awk '{if (NR==2) {print $2,$3,$4}}'`" > ${DISPLAY} 24 | } 25 | 26 | inc_page() { 27 | page=$(($(($page + 1)) % ${#my_pages[@]})) 28 | } 29 | 30 | dec_page() { 31 | page=$(($((${#my_pages[@]} + $page - 1)) % ${#my_pages[@]})) 32 | } 33 | 34 | update_page() { 35 | ${my_pages[$(($page % ${#my_pages[@]}))]} 36 | } 37 | 38 | decode_value() { 39 | case $1 in 40 | "8001") echo $2;; 41 | "7fff") echo $3;; 42 | *) echo "UNKOWN $1";; 43 | esac 44 | } 45 | 46 | decode_key() { 47 | key=`echo $1 | awk '{printf $9}'` 48 | value=`echo $1 | awk '{printf $8}'` 49 | 50 | case $key in 51 | "0102") decode_value "$value" "DOWN" "UP";; 52 | "0002") decode_value "$value" "LEFT" "RIGHT";; 53 | "0001") echo "ENTER";; 54 | esac 55 | } 56 | 57 | setup_event_pipe() { 58 | if [[ ! -p ${PIPE} ]]; then 59 | mkfifo ${PIPE} 60 | fi 61 | 62 | stdbuf -oL hexdump ${JOYSTICK}> ${PIPE} & 63 | PID=$! 64 | trap "{ rm ${PIPE}; kill $PID; exit 255; }" EXIT 65 | } 66 | 67 | # main() starts here... 68 | setup_event_pipe 69 | while true 70 | do 71 | update_page 72 | while read -rs -t 0.5 line < ${PIPE} 73 | do 74 | case $(decode_key "${line}") in 75 | "UP") printf "Up\n";; 76 | "DOWN") printf "Down\n";; 77 | "RIGHT") inc_page; printf "Right\n";; 78 | "LEFT") dec_page; printf "Left\n";; 79 | "ENTER") printf "Enter\n";; 80 | esac 81 | update_page 82 | done 83 | done 84 | -------------------------------------------------------------------------------- /scripts/poll_pwrfail.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This example script shows how to use the 'sups_pwrfail' gpio, provided 3 | # bbapi_power on platforms with S-UPS. 4 | # 5 | # Copyright (C) 2015 - 2016 Beckhoff Automation GmbH 6 | # Author: Patrick Bruenn 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining 9 | # a copy of this software and associated documentation files 10 | # (the "Software"), to deal in the Software without restriction, 11 | # including without limitation the rights to use, copy, modify, merge, 12 | # publish, distribute, sublicense, and/or sell copies of the Software, 13 | # and to permit persons to whom the Software is furnished to do so, 14 | # subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | if [ $# -ne 1 ]; then 28 | echo -e "Usage:\n $0 \n\nexample:\n $0 511\n" 29 | echo -e "If you don't know your , take a look into your kernel log:" 30 | echo -e " dmesg | grep \"registered bbapi_power as\"\n\n" 31 | exit -1 32 | fi 33 | 34 | gpio_id=$1 35 | gpio=/sys/class/gpio/sups_pwrfail/value 36 | 37 | echo ${gpio_id} > /sys/class/gpio/export 38 | if ! [ -r ${gpio} ]; then 39 | echo "Unable to read'${gpio}'" 40 | exit 1 41 | fi 42 | 43 | while [ $(<${gpio}) -eq 0 ]; do 44 | sleep 0.1 45 | printf "." 46 | done 47 | echo -e "\nPower loss detected -> send SIGPWR to 'init' for immediate shutdown" 48 | #kill -s SIGPWR 1 49 | -------------------------------------------------------------------------------- /sensors_example.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | Example code to read sensor values from the BBAPI 4 | 5 | Copyright (C) 2016 - 2018 Beckhoff Automation GmbH & Co. KG 6 | Author: Patrick Bruenn 7 | */ 8 | 9 | #include "TcBaDevDef.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | int main() 16 | { 17 | int bbapi_dev = open("/dev/bbapi", O_RDWR); 18 | int status; 19 | 20 | if (-1 == bbapi_dev) { 21 | printf("Open '/dev/bbapi' failed\n"); 22 | return -1; 23 | } 24 | 25 | uint32_t num_sensors; 26 | struct bbapi_struct cmd_num_sensors = { 27 | BIOSIGRP_SYSTEM, 28 | BIOSIOFFS_SYSTEM_COUNT_SENSORS, 29 | NULL, 30 | 0, 31 | &num_sensors, 32 | sizeof(num_sensors) 33 | }; 34 | 35 | status = ioctl(bbapi_dev, BBAPI_CMD, &cmd_num_sensors); 36 | if (status) { 37 | printf("Read number of sensors failed\n"); 38 | return status; 39 | } 40 | 41 | while (num_sensors > 0) { 42 | char text[256]; 43 | SENSORINFO info; 44 | struct bbapi_struct cmd_read_sensor = { 45 | BIOSIGRP_SYSTEM, 46 | num_sensors, 47 | NULL, 48 | 0, 49 | &info, 50 | sizeof(info) 51 | }; 52 | 53 | status = ioctl(bbapi_dev, BBAPI_CMD, &cmd_read_sensor); 54 | if (status) { 55 | printf("Read sensor #%u failed with 0x%x\n", 56 | num_sensors, status); 57 | return status; 58 | } 59 | SENSORINFO_snprintf(&info, text, sizeof(text)); 60 | printf("%02d: %s\n", num_sensors, text); 61 | --num_sensors; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /simple_cdev.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | Character Driver for Beckhoff BIOS API 4 | Author: Patrick Brünn 5 | Copyright (C) 2013 - 2018 Beckhoff Automation GmbH & Co. KG 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include "simple_cdev.h" 12 | 13 | int simple_cdev_init(struct simple_cdev *dev, const char *classname, 14 | const char *devicename, struct file_operations *file_ops) 15 | { 16 | if (alloc_chrdev_region(&dev->dev, 0, 1, KBUILD_MODNAME) < 0) { 17 | pr_warn("alloc_chrdev_region() failed!\n"); 18 | return -1; 19 | } 20 | 21 | cdev_init(&dev->cdev, file_ops); 22 | dev->cdev.owner = THIS_MODULE; 23 | kobject_set_name(&dev->cdev.kobj, "%s", devicename); 24 | if (cdev_add(&dev->cdev, dev->dev, 1) == -1) { 25 | pr_warn("cdev_add() failed!\n"); 26 | goto rollback_region; 27 | } 28 | 29 | #if (LINUX_VERSION_CODE < KERNEL_VERSION(6,4,0)) 30 | if ((dev->class = class_create(THIS_MODULE, classname)) == NULL) { 31 | #else 32 | (void)classname; 33 | if ((dev->class = class_create(devicename)) == NULL) { 34 | #endif 35 | pr_warn("class_create() failed!\n"); 36 | goto rollback_cdev; 37 | } 38 | 39 | if (device_create(dev->class, NULL, dev->dev, NULL, "%s", devicename) == NULL) { 40 | pr_warn("device_create() failed!\n"); 41 | goto rollback_class; 42 | } 43 | return 0; 44 | 45 | rollback_class: 46 | class_destroy(dev->class); 47 | rollback_cdev: 48 | cdev_del(&dev->cdev); 49 | rollback_region: 50 | unregister_chrdev_region(dev->dev, 1); 51 | return -1; 52 | } 53 | 54 | void simple_cdev_remove(struct simple_cdev *dev) 55 | { 56 | device_destroy(dev->class, dev->dev); 57 | class_destroy(dev->class); 58 | cdev_del(&dev->cdev); 59 | unregister_chrdev_region(dev->dev, 1); 60 | } 61 | -------------------------------------------------------------------------------- /simple_cdev.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | Character Driver for Beckhoff BIOS API 4 | Author: Patrick Brünn 5 | Copyright (C) 2013 - 2018 Beckhoff Automation GmbH & Co. KG 6 | */ 7 | 8 | #ifndef _SIMPLE_CDEV_H_ 9 | #define _SIMPLE_CDEV_H_ 10 | 11 | #include 12 | #include 13 | 14 | struct simple_cdev { 15 | dev_t dev; // First device number 16 | struct cdev cdev; // Character device structure 17 | struct class *class; // Device class 18 | }; 19 | 20 | extern int simple_cdev_init(struct simple_cdev *dev, const char *classname, 21 | const char *devicename, 22 | struct file_operations *file_ops); 23 | extern void simple_cdev_remove(struct simple_cdev *dev); 24 | #endif /* #ifndef _SIMPLE_CDEV_H_ */ 25 | -------------------------------------------------------------------------------- /sups/Makefile: -------------------------------------------------------------------------------- 1 | TARGET = bbapi_sups 2 | EXTRA_DIR = /lib/modules/$(shell uname -r)/extra/ 3 | obj-m += $(TARGET).o 4 | $(TARGET)-objs := sups.o 5 | ccflags-y := -DDEBUG 6 | KBUILD_EXTRA_SYMBOLS := $(src)/../Module.symvers 7 | KDIR ?= /lib/modules/$(shell uname -r)/build 8 | 9 | all: 10 | make -C $(KDIR) M=$(PWD) modules 11 | 12 | install: 13 | - sudo rmmod $(TARGET) 14 | sudo mkdir -p $(EXTRA_DIR) 15 | sudo cp ./$(TARGET).ko $(EXTRA_DIR) 16 | sudo depmod -a 17 | sudo modprobe $(TARGET) 18 | 19 | clean: 20 | make -C $(KDIR) M=$(PWD) clean 21 | 22 | # indent the source files with the kernels Lindent script 23 | indent: sups.c 24 | ../Lindent $? 25 | -------------------------------------------------------------------------------- /sups/sups.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | 1-second UPS driver using the Beckhoff BIOS API 4 | Author: Patrick Brünn 5 | Copyright (C) 2015 - 2018 Beckhoff Automation GmbH & Co. KG 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "../api.h" 14 | #include "../TcBaDevDef.h" 15 | 16 | #define DRV_VERSION "0.2" 17 | #define DRV_DESCRIPTION "Beckhoff BIOS API 1-second UPS driver" 18 | 19 | struct bbapi_sups_info { 20 | struct gpio_chip gpio_chip; 21 | struct Bapi_GpioInfoEx gpio_info; 22 | }; 23 | 24 | #define sups_read(offset, buffer) \ 25 | bbapi_read(BIOSIGRP_SUPS, offset, &buffer, sizeof(buffer)) 26 | 27 | static int sups_gpio_get(struct gpio_chip *chip, unsigned nr) 28 | { 29 | struct bbapi_sups_info *pbi = 30 | container_of(chip, struct bbapi_sups_info, gpio_chip); 31 | 32 | return inl(pbi->gpio_info.address) & pbi->gpio_info.bitmask; 33 | } 34 | 35 | static const char *sups_gpio_names[] = { 36 | "sups_pwrfail", 37 | }; 38 | 39 | static const struct gpio_chip sups_gpio_chip = { 40 | .label = KBUILD_MODNAME, 41 | .owner = THIS_MODULE, 42 | .get = sups_gpio_get, 43 | .base = -1, 44 | .ngpio = 1, 45 | .names = sups_gpio_names, 46 | }; 47 | 48 | static int init_sups(struct bbapi_sups_info *pbi) 49 | { 50 | int status; 51 | 52 | if (sups_read(BIOSIOFFS_SUPS_GPIO_PIN_EX, pbi->gpio_info)) { 53 | struct TSUps_GpioInfo legacy; 54 | 55 | if (sups_read(BIOSIOFFS_SUPS_GPIO_PIN, legacy)) { 56 | pr_err("BIOSIOFFS_SUPS_GPIO_PIN not supported\n"); 57 | return -ENODEV; 58 | } 59 | 60 | pbi->gpio_info.address = legacy.ioAddr + legacy.offset; 61 | pbi->gpio_info.bitmask = legacy.params; 62 | pbi->gpio_info.length = 4; 63 | } 64 | 65 | if (!request_region 66 | (pbi->gpio_info.address, pbi->gpio_info.length, 67 | sups_gpio_names[0])) { 68 | pr_err("request_region() failed\n"); 69 | return -ENODEV; 70 | } 71 | 72 | memcpy(&pbi->gpio_chip, &sups_gpio_chip, sizeof(pbi->gpio_chip)); 73 | status = gpiochip_add(&pbi->gpio_chip); 74 | if (status) { 75 | release_region(pbi->gpio_info.address, pbi->gpio_info.length); 76 | return status; 77 | } 78 | 79 | pr_info("registered %s as gpiochip%d with #%d GPIOs.\n", 80 | pbi->gpio_chip.label, pbi->gpio_chip.base, 81 | pbi->gpio_chip.ngpio); 82 | return 0; 83 | } 84 | 85 | static int bbapi_sups_probe(struct platform_device *pdev) 86 | { 87 | struct bbapi_sups_info *pbi; 88 | int status = 0; 89 | 90 | pbi = kzalloc(sizeof(*pbi), GFP_KERNEL); 91 | if (!pbi) { 92 | return -ENOMEM; 93 | } 94 | dev_set_drvdata(&pdev->dev, pbi); 95 | 96 | status = init_sups(pbi); 97 | if (status) { 98 | kfree(pbi); 99 | } 100 | return status; 101 | } 102 | 103 | static int bbapi_sups_remove(struct platform_device *pdev) 104 | { 105 | struct bbapi_sups_info *pbi = platform_get_drvdata(pdev); 106 | 107 | if (pbi->gpio_info.address) { 108 | gpiochip_remove(&pbi->gpio_chip); 109 | release_region(pbi->gpio_info.address, pbi->gpio_info.length); 110 | } 111 | kfree(pbi); 112 | return 0; 113 | } 114 | 115 | static struct platform_driver bbapi_sups_driver = { 116 | .driver = { 117 | .name = KBUILD_MODNAME, 118 | }, 119 | .probe = bbapi_sups_probe, 120 | .remove = bbapi_sups_remove, 121 | }; 122 | 123 | module_platform_driver(bbapi_sups_driver); 124 | MODULE_DESCRIPTION(DRV_DESCRIPTION); 125 | MODULE_AUTHOR("Patrick Bruenn "); 126 | MODULE_LICENSE("GPL and additional rights"); 127 | MODULE_VERSION(DRV_VERSION); 128 | -------------------------------------------------------------------------------- /test_config.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | Beckhoff BIOS API driver to access hwmon sensors for Beckhoff IPCs 4 | Copyright (C) 2014 - 2019 Beckhoff Automation GmbH & Co. KG 5 | Author: Patrick Bruenn 6 | */ 7 | 8 | #pragma once 9 | 10 | /** test mode selection */ 11 | #define CONFIG_INTERACTIVE 1 12 | 13 | /** reused ranges */ 14 | #define BOOTCOUNTER_RANGE 1, 15000 15 | #define OPERATION_TIME_RANGE 1, 1500000 16 | #define _24VOLT_RANGE 23500, 28000 17 | 18 | #if defined(__i386__) 19 | #define CONFIG_GENERAL_PLATFORM 0x00 20 | #elif defined(__x86_64__) 21 | #define CONFIG_GENERAL_PLATFORM 0x01 22 | #endif 23 | 24 | #define FILE_PATH "/dev/bbapi" // Path to character Device 25 | 26 | /** select test device */ 27 | //#include "config_c6015.h" 28 | //#include "config_cx5000.h" 29 | //#include "config_cx5130.h" 30 | //#include "config_cx2030_cx2100-0004.h" 31 | //#include "config_cx2030_cx2100-0904.h" 32 | 33 | 34 | /** PWRCTRL configuration */ 35 | #define CONFIG_PWRCTRL_OPERATION_TIME_RANGE OPERATION_TIME_RANGE 36 | #define CONFIG_PWRCTRL_MIN_TEMP_RANGE 10, 20 37 | #define CONFIG_PWRCTRL_MAX_TEMP_RANGE 70, 112 38 | #define CONFIG_PWRCTRL_MIN_VOLT_RANGE 49, 50 39 | #define CONFIG_PWRCTRL_MAX_VOLT_RANGE 49, 50 40 | #define CONFIG_PWRCTRL_BOOT_COUNTER_RANGE BOOTCOUNTER_RANGE 41 | #define CONFIG_PWRCTRL_POSITION 0x00 42 | 43 | /** S-UPS configuration */ 44 | #ifndef CONFIG_SUPS_DISABLED 45 | #define CONFIG_SUPS_STATUS_ON 0xC0 46 | #define CONFIG_SUPS_REVISION {1, 9} 47 | #define CONFIG_SUPS_POWERFAILCOUNT_RANGE BOOTCOUNTER_RANGE 48 | #define CONFIG_SUPS_SHUTDOWN_TYPE 0xFF 49 | #define CONFIG_SUPS_ACTIVE_COUNT 0 50 | #define CONFIG_SUPS_INTERNAL_PWRF_STATUS 0 51 | #define CONFIG_SUPS_TEST_RESULT 0 52 | #define CONFIG_SUPS_GPIO_PIN {0x480, 0x28, 0x1} 53 | #endif 54 | 55 | /** CX Power Supply configuration */ 56 | #define CONFIG_CXPWRSUPP_BOOTCOUNTER_RANGE BOOTCOUNTER_RANGE 57 | #define CONFIG_CXPWRSUPP_OPERATIONTIME_RANGE OPERATION_TIME_RANGE 58 | #define CONFIG_CXPWRSUPP_5VOLT_RANGE 4900, 5100 59 | #define CONFIG_CXPWRSUPP_12VOLT_RANGE 11900, 12500 60 | #define CONFIG_CXPWRSUPP_24VOLT_RANGE _24VOLT_RANGE 61 | #define CONFIG_CXPWRSUPP_TEMP_RANGE -35, 85 62 | #define CONFIG_CXPWRSUPP_CURRENT_RANGE 600, 4550 63 | #define CONFIG_CXPWRSUPP_POWER_RANGE 10000, 101000 64 | #define CONFIG_CXPWRSUPP_BUTTON_STATE 0x00 65 | 66 | /** CX UPS configuration */ 67 | #define CONFIG_CXUPS_ENABLED 0 68 | #define CONFIG_CXUPS_FIRMWAREVER {1, 0} 69 | #define CONFIG_CXUPS_POWERSTATUS 1 // (BYTE) (0 := Unknown, 1 := Online, 2 := OnBatteries) 70 | #define CONFIG_CXUPS_BATTERYSTATUS 1 // (BYTE) (0 := Unknown, 1 := Ok, 2 := Change batteries) 71 | #define CONFIG_CXUPS_BATTERYCAPACITY 100 72 | #define CONFIG_CXUPS_BATTERYRUNTIME_RANGE 1, 6000 73 | #define CONFIG_CXUPS_BOOTCOUNTER_RANGE 0, 1 74 | #define CONFIG_CXUPS_OPERATIONTIME_RANGE OPERATION_TIME_RANGE 75 | #define CONFIG_CXUPS_POWERFAILCOUNT 0 76 | #define CONFIG_CXUPS_BATTERYCRITICAL 0 77 | #define CONFIG_CXUPS_BATTERYPRESENT 1 78 | #define CONFIG_CXUPS_OUTPUTVOLT_RANGE 9000, 12000 79 | #define CONFIG_CXUPS_INPUTVOLT_RANGE _24VOLT_RANGE 80 | #define CONFIG_CXUPS_CURRENT 0 81 | #define CONFIG_CXUPS_CURRENT_RANGE 8000, 10000 82 | #define CONFIG_CXUPS_POWER 0 83 | #define CONFIG_CXUPS_POWER_RANGE 20000, 100000 84 | #define CONFIG_CXUPS_TEMP_RANGE 0, 85 85 | 86 | /** Watchdog configuration */ 87 | #define CONFIG_WATCHDOG_OPTIONS WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING 88 | -------------------------------------------------------------------------------- /tools/10_get_fructose.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | SCRIPT_PATH="$( cd "$( dirname "$0" )" && pwd )" 4 | NAME=fructose-1.4.0 5 | FILENAME=${NAME}.tar.gz 6 | OS=$(uname -s) 7 | 8 | if [ "x${OS}y" = "xLinuxy" ]; then 9 | SHA512_CMD=sha512sum 10 | else 11 | SHA512_CMD=shasum 12 | fi 13 | 14 | echo "$SHA512_CMD" 15 | pwd 16 | 17 | cd ${SCRIPT_PATH} 18 | 19 | if ! ${SHA512_CMD} -c ${NAME}.sha512; then 20 | wget https://downloads.sourceforge.net/project/fructose/fructose/${NAME}/${FILENAME} 21 | fi 22 | 23 | rm -rf "${SCRIPT_PATH}/fructose" 24 | tar xf ${FILENAME} 25 | mv ${NAME}/fructose/include/fructose/ ${SCRIPT_PATH}/ 26 | rm -rf ${NAME}/ 27 | -------------------------------------------------------------------------------- /tools/fructose-1.4.0.sha512: -------------------------------------------------------------------------------- 1 | a15772cb662934ea3181a0c7e86202a9cba6395fdd914a8303d17fc47e1c5eaf4296e54e27a5ae0c60a21dc9f290f51699c6878918410d8da402ecb8f76a4473 fructose-1.4.0.tar.gz 2 | -------------------------------------------------------------------------------- /tools/fructose/double_compare.h: -------------------------------------------------------------------------------- 1 | /* FRUCTOSE C++ unit test library. 2 | Copyright (c) 2012 Andrew Peter Marlow. All rights reserved. 3 | http://www.andrewpetermarlow.co.uk. 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public 7 | License as published by the Free Software Foundation; either 8 | version 2.1 of the License, or (at your option) any later version. 9 | 10 | This library is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public 16 | License along with this library; if not, write to the Free Software 17 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | 20 | #ifndef INCLUDED_FRUCTOSE_DOUBLECOMPARE 21 | #define INCLUDED_FRUCTOSE_DOUBLECOMPARE 22 | 23 | namespace fructose { 24 | 25 | /** 26 | * This namespace contains functions that implement "fuzzy" 27 | * equality and relational operations as a set of function calls 28 | * on a pair of 'double' values 'a' and 'b'. The term "fuzzy equality" 29 | * expresses the concept that 'a' and 'b' are "close enough"; any 30 | * difference that exists between the internal representations of 'a' and 31 | * 'b' is not significant, and should be neglected for comparison purposes. 32 | * 33 | * Fuzzy comparison operations are of general use in computing but there 34 | * is nothing in the C++ standard (yet) to address this. So each project 35 | * that uses floating point numbers tends to define its own. 36 | * The one here is general-purpose but has been encased in the fructose 37 | * namespace since it is used by fructose to provide unit test assertions 38 | * for floating point numbers. The reader will notice that the functions 39 | * are general enough to have wider applicability but they are (at the 40 | * moment, in a namespace that is part of a unit test library). 41 | * This may change in some later version of fructose, depending on any 42 | * feedback from the user community. There are other ways to get these 43 | * sorts of comparison functions (pending the issue being addressed 44 | * by the standard and then implemented by compiler writers). 45 | * Some widely-used Open Source libraries offer similar functions. 46 | * For example, there are algorithms in boost such as close_at_tolerance 47 | * and check_is_close. These functions exist here in order to avoid 48 | * a dependency on boost. 49 | * 50 | * There are two kinds of fuzzy comparison: comparison using fixed 51 | * (aka absolute) tolerance value and comparison using relative tolerance 52 | * value. Absolute tolerances are used when and b are expected to be quite 53 | * close so an absolute value can be used to compare the distance between them. 54 | * This technique becomes inappropriate when the values are large, 55 | * e.g one million and one million and two are very close because they 56 | * are within 0.002% of each other. To handle this case, relative tolerance 57 | * is used. The relative difference between a and b is calculated as 58 | * fabs(a-b)/average(a,b). If this is less than or equal to a relative 59 | * tolerance value then a and b compare as equal. The relative tolerance 60 | * value is thus similar to the absolute tolerance value in that it does not 61 | * result in a comparion to within a percentage of a or b, but 62 | * it does take their relative magnitudes into account. 63 | * See "The Art of Computer Programming, volume 2" by Knuth, 64 | * for a more detailed discussion on comparing floating point numbers. 65 | * 66 | * Thus, using the algorithms in this namespace, 'a' and 'b' have 67 | * fuzzy equality if either their relative difference is less than or 68 | * equal to some user-specified or default tolerance value, or else 69 | * their absolute difference is less than or equal to a separate 70 | * user-specified or default tolerance value. The fuzzy inequality 71 | * and relational operators are all defined with respect to fuzzy equality. 72 | * 73 | * The table below shows how the fuzzy comparison operations map onto 74 | * the function provided by this namespace: 75 | * \code 76 | * N O T A T I O N O N L Y 77 | * ------------------------------------------------------------- 78 | * C++ operator | fuzzy operator | namespace function name 79 | * ------------------------------------------------------------- 80 | * == | ~eq | eq(a, b, abstol, reltol) 81 | * != | ~ne | ne(a, b, absrol, reltol) 82 | * < | ~lt | lt(a, b, abstol, reltol) 83 | * <= | ~le | le(a, b, abstol, reltol) 84 | * >= | ~ge | ge(a, b, abstol, reltol) 85 | * > | ~gt | gt(a, b, abstol, reltol) 86 | * ------------------------------------------------------------- 87 | * \endcode 88 | * 89 | * Using the Backus-Naur notation "A := B" to mean "A is defined as B", 90 | * we define the comparision algorithms as follows: 91 | * 92 | * \code 93 | * a ~eq b := ( (a == b) || 94 | * ( ( fabs(a - b) <= absolute_tolerance ) || 95 | * ( fabs(a - b) / ( fabs(a + b) / 2 ) <= relative_tolerance ) ) ) 96 | * a ~ne b := !(a ~eq b) 97 | * a ~lt b := !(a ~eq b) && (a < b) 98 | * a ~le b := (a ~eq b) || (a < b) 99 | * a ~ge b := (a ~eq b) || (a > b) 100 | * a ~gt b := !(a ~eq b) && (a > b) 101 | * \endcode 102 | * 103 | * where fabs(double) is the standard absolute value function and 104 | * other symbols have their usual C++ meaning. Note that the equality 105 | * operations are symmetric. 106 | * 107 | * The functions implemented in this class are well behaved for all 108 | * values of 'relative_tolerance' and 'absolute_tolerance'. If either 109 | * relative_tolerance <= 0 or absolute_tolerance <= 0, that respective 110 | * "fuzzy comparison" is suppressed. If both relative_tolerance <= 0 111 | * and absolute_tolerance <= 0, the six fuzzy operations behave as 112 | * runtime-intensive versions of their non-fuzzy counterparts. 113 | * 114 | * Note that the definition of fuzzy equality used in this class has 115 | * one intermediate singularity. When (fabs(a - b) > absolute_tolerance 116 | * && (a == -b)) is true, the pseudo-expression (a ~eq b) above has a 117 | * zero denominator. In this case, the test for relative fuzzy equality 118 | * is suppressed. Note also that this intermediate singularity does NOT 119 | * lead to a special case behavior of fuzzy comparisons. By definition, 120 | * the relative difference is the quotient of the absolute difference and 121 | * the absolute average, so the case (a == -b) truly represents an "infinite 122 | * relative difference", and thus fuzzy equality via the relative difference 123 | * should be false (although absolute fuzzy equality may still prevail). 124 | * 125 | * \attention 126 | * The functions in this namespace are implemented using the definitions 127 | * provided, and as such are vulnerable to the limitations of the internal 128 | * representations of 'double'. In particular, if (a + b) or (a - b) cannot 129 | * be represented, the functions will fail outright. As 'a' or 'b' approach 130 | * the limits of precision of representation -- that "approach" being defined 131 | * by the fuzzy tolerances -- the algorithms used in this namespace become 132 | * increasingly unreliable. The user is responsible for determining the 133 | * limits of applicability of this namespace to a given calculation, and for 134 | * coding accordingly. 135 | * 136 | * The following example tabulates the numerical results for a set 137 | * of function calls to the six fuzzy comparison functions of the form 138 | * \code 139 | * double_compare::eq(x, y, relative_tolerance, absolute_tolerance) 140 | * \endcode 141 | * where 'relative_tolerance' and 'absolute_tolerance' are the relative 142 | * and absolute tolerances,respectively. For convenience, the true relative 143 | * difference is tabulated (to five decimal places) in the column with the 144 | * heading 'diff'. 145 | * \code 146 | * x | y | rel | abs | diff | eq ne lt le ge gt 147 | * ------------------------------------------------------------------ 148 | * 99.0 | 100.0 | 0.010 | 0.001 | 0.01005 | 0 1 1 1 0 0 149 | * 100.0 | 99.0 | 0.010 | 0.001 | 0.01005 | 0 1 0 0 1 1 150 | * 99.0 | 100.0 | 0.011 | 0.001 | 0.01005 | 1 0 0 1 1 0 151 | * 99.0 | 100.0 | 0.010 | 0.990 | 0.01005 | 0 1 1 1 0 0 152 | * 99.0 | 100.0 | 0.010 | 1.000 | 0.01005 | 1 0 0 1 1 0 153 | * ------------------------------------------------------------------ 154 | * 100.0 | 101.0 | 0.009 | 0.001 | 0.00995 | 0 1 1 1 0 0 155 | * 101.0 | 100.0 | 0.009 | 0.001 | 0.00995 | 0 1 0 0 1 1 156 | * 100.0 | 101.0 | 0.010 | 0.001 | 0.00995 | 1 0 0 1 1 0 157 | * 100.0 | 101.0 | 0.009 | 0.990 | 0.00995 | 0 1 1 1 0 0 158 | * 100.0 | 101.0 | 0.009 | 1.000 | 0.00995 | 1 0 0 1 1 0 159 | * \endcode 160 | * 161 | * Here are some notes on usage: 162 | * Given a 'double' value 'x' and two functions 'f' and 'g' each taking one 163 | * 'double' and returning 'double': 164 | * 165 | * \code 166 | * double g(double x); 167 | * \endcode 168 | * 169 | * we can do something if the two functions, each evaluated at 'x', are not 170 | * close enough by our own explicit criteria. 171 | * 172 | * \code 173 | * if (double_compare::ne(f(x), g(x), 1e-8)) 174 | * { 175 | * doSomething(); 176 | * } 177 | * \endcode 178 | * 179 | **/ 180 | namespace double_compare 181 | { 182 | /// default relative tolerance 183 | const double relative_tolerance_default = 1e-12; 184 | 185 | /// default absolute tolerance 186 | const double absolute_tolerance_default = 1e-24; 187 | 188 | /** 189 | * @brief Return 0 if the specifed 'a' and 'b' have 190 | * fuzzy equality (a ~eq b). 191 | * 192 | * Otherwise, return a positive value if a > b, or a negative value 193 | * if a < b. This is analogous to the return value of strcmp, which 194 | * allow easy implementation of all the numerical comparison operations 195 | * in terms of fuzzy_compare. 196 | * Fuzzy equality between 'a' and 'b' is defined in terms of the 197 | * specified 'relative_tolerance' and 'absolute_tolerance' such that 198 | * the expression 199 | *\code 200 | * (a == b) || fabs(a - b) <= absolute_tolerance || 201 | * (fabs(a - b) / fabs((a + b) / 2.0)) <= relative_tolerance) 202 | *\endcode 203 | * evaluates to 'true'. As a consequence of this definition of 204 | * relative difference, the special case of (a == -b && a != 0) 205 | * is treated as the maximum relative difference: no value of 206 | * 'relative_tolerance' can force fuzzy equality (although an 207 | * appropriate value of 'absolute_tolerance' can). 208 | * If relative_tolerance <= 0 or absolute_tolerance <= 0, the 209 | * respective aspect of fuzzy comparison is suppressed, but the 210 | * function is still valid. Note that although this function 211 | * may be called directly, its primary purpose is to implement 212 | * the fuzzy quasi-boolean equality and relational functions named 213 | * 'eq', 'ne', 'lt', 'le', 'ge', and 'gt', corresponding to the 214 | * operators '==', '!=', '<', '<=', '>=', and '>', respectively. 215 | **/ 216 | int fuzzy_compare(double a, double b, double relative_tolerance, 217 | double absolute_tolerance); 218 | 219 | /** 220 | * @brief Return a non-zero value if the specified 'a' and 'b' satisfy 221 | * the fuzzy equality relation (a ~eq b), and 0 otherwise. 222 | * 223 | * Optionally specify the relative tolerance 'relative_tolerance', or 224 | * 'relative_tolerance' and the absolute tolerance 'absolute_tolerance', 225 | * used to determine fuzzy equality. If optional parameters are not 226 | * specified, reasonable implementation-dependent default tolerances 227 | * are used. If relative_tolerance <= 0 or absolute_tolerance <= 0, 228 | * the respective aspect of fuzzy comparison is suppressed, but the 229 | * function is still valid. 230 | **/ 231 | bool eq(double a, double b); 232 | bool eq(double a, double b, double relative_tolerance); 233 | bool eq(double a, double b, double relative_tolerance, double absolute_tolerance); 234 | 235 | /** 236 | * @brief Return a non-zero value if the specified 'a' and 'b' satisfy 237 | * the fuzzy inequality relation (a ~ne b), and 0 otherwise. 238 | * 239 | * Optionally specify the relative tolerance 'relative_tolerance', or 240 | * 'relative_tolerance' and the absolute tolerance 'absolute_tolerance', 241 | * used to determine fuzzy equality. If optional parameters are not 242 | * specified, reasonable implementation-dependent default tolerances 243 | * are used. If relative_tolerance <= 0 or absolute_tolerance <= 0, 244 | * the respective aspect of fuzzy comparison is suppressed, but the 245 | * function is still valid. 246 | **/ 247 | bool ne(double a, double b); 248 | bool ne(double a, double b, double relative_tolerance); 249 | bool ne(double a, double b, double relative_tolerance, double absolute_tolerance); 250 | 251 | /** 252 | * @brief Return a non-zero value if the specified 'a' and 'b' satisfy 253 | * the fuzzy less-than relation (a ~lt b) and 0 otherwise. 254 | * 255 | * Optionally specify the relative tolerance 'relative_tolerance', 256 | * or 'relative_tolerance' and the absolute tolerance 257 | * 'absolute_tolerance', used to determine fuzzy equality. 258 | * If optional parameters are not specified, reasonable implementation- 259 | * dependent default tolerances are used. If relative_tolerance <= 0 or 260 | * absolute_tolerance <= 0, the respective aspect of fuzzy comparison is 261 | * suppressed, but the function is still valid. 262 | **/ 263 | bool lt(double a, double b); 264 | bool lt(double a, double b, double relative_tolerance); 265 | bool lt(double a, double b, double relative_tolerance, double absolute_tolerance); 266 | 267 | /** 268 | * @brief Return a non-zero value if the specified 'a' and 'b' satisfy 269 | * the fuzzy less-than-or-equal-to relation (a ~le b), and 0 otherwise. 270 | * 271 | * Optionally specify the relative tolerance 'relative_tolerance', or 272 | * 'relative_tolerance' and the absolute tolerance 'absolute_tolerance', 273 | * used to determine fuzzy equality. If optional parameters are not 274 | * specified, reasonable implementation-dependent default tolerances 275 | * are used. If relative_tolerance <= 0 or absolute_tolerance <= 0, 276 | * the respective aspect of fuzzy comparison is suppressed, but the 277 | * function is still valid. 278 | **/ 279 | bool le(double a, double b); 280 | bool le(double a, double b, double relative_tolerance); 281 | bool le(double a, double b, double relative_tolerance, double absolute_tolerance); 282 | 283 | /** 284 | * @brief Return a non-zero value if the specified 'a' and 'b' satisfy 285 | * the fuzzy greater-than-or-equal-to relation (a ~ge b), and 0 otherwise. 286 | * 287 | * Optionally specify the relative tolerance 'relative_tolerance', or 288 | * 'relative_tolerance' and the absolute tolerance 'absolute_tolerance', 289 | * used to determine fuzzy equality. If optional parameters are not 290 | * specified, default tolerances are used. If relative_tolerance <= 0 291 | * or absolute_tolerance <= 0, the respective aspect of fuzzy 292 | * comparison is suppressed, but the function is still valid. 293 | **/ 294 | bool ge(double a, double b); 295 | bool ge(double a, double b, double relative_tolerance); 296 | bool ge(double a, double b, double relative_tolerance, double absolute_tolerance); 297 | 298 | /** 299 | * @brief Return a non-zero value if the specified 'a' and 'b' satisfy 300 | * the fuzzy greater-than relation (a ~gt b), and 0 otherwise. 301 | * 302 | * Optionally specify the relative tolerance 'relative_tolerance', or 303 | * 'relative_tolerance' and the absolute tolerance 'absolute_tolerance', 304 | * used to determine fuzzy equality.If optional parameters are not 305 | * specified, reasonable implementation-dependent default tolerances 306 | * are used. If relative_tolerance <= 0 or absolute_tolerance <= 0, 307 | * the respective aspect of fuzzy comparison is suppressed, but the 308 | * function is still valid. 309 | **/ 310 | bool gt(double a, double bl); 311 | bool gt(double a, double b, double relative_tolerance); 312 | bool gt(double a, double b, double relative_tolerance, double absolute_tolerance); 313 | 314 | /** 315 | * Return the absolute value of the specified 'input'. 316 | * This is used instead of std::fabs to avoid a dependency 317 | * on the math library. 318 | */ 319 | double fabsval(double input); 320 | } 321 | 322 | // ==================== 323 | // INLINE definitions 324 | // ==================== 325 | 326 | inline 327 | double double_compare::fabsval(double input) 328 | { 329 | return 0.0 <= input ? input : -input; 330 | } 331 | 332 | inline 333 | int 334 | double_compare::fuzzy_compare(double a, double b, 335 | double relative_tolerance, 336 | double absolute_tolerance) 337 | { 338 | if (a == b) 339 | { 340 | // Special case: equality. Done. 341 | return 0; 342 | } 343 | 344 | if (a == -b) 345 | { 346 | // Special case: Relative difference 347 | if (fabsval(a - b) <= absolute_tolerance) 348 | { 349 | // is "infinite" 350 | // Fuzzy equality (via abs. tol. only) 351 | return 0; 352 | } 353 | else if (a > b) 354 | { 355 | return 1; // Fuzzy greater than 356 | } 357 | else 358 | { 359 | return -1; // Fuzzy less than 360 | } 361 | } 362 | 363 | const double diff = fabsval(a - b); 364 | const double average = fabsval((a + b) / 2.0); 365 | 366 | if (diff <= absolute_tolerance || diff / average <= relative_tolerance) 367 | { 368 | return 0; // Fuzzy equality. 369 | } 370 | else if (a > b) 371 | { 372 | return 1; // Fuzzy greater than 373 | } 374 | else 375 | { 376 | return -1; // Fuzzy less than 377 | } 378 | } 379 | 380 | inline 381 | bool 382 | double_compare::eq(double a, double b) 383 | { 384 | return fuzzy_compare(a, b, relative_tolerance_default, absolute_tolerance_default) == 0; 385 | } 386 | 387 | inline 388 | bool 389 | double_compare::eq(double a, double b, double relative_tolerance) 390 | { 391 | return fuzzy_compare(a, b, relative_tolerance, absolute_tolerance_default) == 0; 392 | } 393 | 394 | inline 395 | bool 396 | double_compare::eq(double a, double b, double relative_tolerance, double absolute_tolerance) 397 | { 398 | return fuzzy_compare(a, b, relative_tolerance, absolute_tolerance) == 0; 399 | } 400 | 401 | inline 402 | bool 403 | double_compare::ne(double a, double b) 404 | { 405 | return fuzzy_compare(a, b, relative_tolerance_default, absolute_tolerance_default) != 0; 406 | } 407 | 408 | inline 409 | bool 410 | double_compare::ne(double a, double b, double relative_tolerance) 411 | { 412 | return fuzzy_compare(a, b, relative_tolerance, absolute_tolerance_default) != 0; 413 | } 414 | 415 | inline 416 | bool 417 | double_compare::ne(double a, double b, double relative_tolerance, double absolute_tolerance) 418 | { 419 | return fuzzy_compare(a, b, relative_tolerance, absolute_tolerance) != 0; 420 | } 421 | 422 | inline 423 | bool 424 | double_compare::lt(double a, double b) 425 | { 426 | return fuzzy_compare(a, b, relative_tolerance_default, absolute_tolerance_default) < 0; 427 | } 428 | 429 | inline 430 | bool 431 | double_compare::lt(double a, double b, double relative_tolerance) 432 | { 433 | return fuzzy_compare(a, b, relative_tolerance, absolute_tolerance_default) < 0; 434 | } 435 | 436 | inline 437 | bool 438 | double_compare::lt(double a, double b, double relative_tolerance, double absolute_tolerance) 439 | { 440 | return fuzzy_compare(a, b, relative_tolerance, absolute_tolerance) < 0; 441 | } 442 | 443 | inline 444 | bool 445 | double_compare::le(double a, double b) 446 | { 447 | return fuzzy_compare(a, b, relative_tolerance_default, absolute_tolerance_default) <= 0; 448 | } 449 | 450 | inline 451 | bool 452 | double_compare::le(double a, double b, double relative_tolerance) 453 | { 454 | return fuzzy_compare(a, b, relative_tolerance, absolute_tolerance_default) <= 0; 455 | } 456 | 457 | inline 458 | bool 459 | double_compare::le(double a, double b, double relative_tolerance, double absolute_tolerance) 460 | { 461 | return fuzzy_compare(a, b, relative_tolerance, absolute_tolerance) <= 0; 462 | } 463 | 464 | inline 465 | bool 466 | double_compare::ge(double a, double b) 467 | { 468 | return fuzzy_compare(a, b, relative_tolerance_default, absolute_tolerance_default) >= 0; 469 | } 470 | 471 | inline 472 | bool 473 | double_compare::ge(double a, double b, double relative_tolerance) 474 | { 475 | return fuzzy_compare(a, b, relative_tolerance, absolute_tolerance_default) >= 0; 476 | } 477 | 478 | inline 479 | bool 480 | double_compare::ge(double a, double b, double relative_tolerance, double absolute_tolerance) 481 | { 482 | return fuzzy_compare(a, b, relative_tolerance, absolute_tolerance) >= 0; 483 | } 484 | 485 | inline 486 | bool 487 | double_compare::gt(double a, double b) 488 | { 489 | return fuzzy_compare(a, b, relative_tolerance_default, absolute_tolerance_default) > 0; 490 | } 491 | 492 | inline 493 | bool 494 | double_compare::gt(double a, double b, double relative_tolerance) 495 | { 496 | return fuzzy_compare(a, b, relative_tolerance, absolute_tolerance_default) > 0; 497 | } 498 | 499 | inline 500 | bool 501 | double_compare::gt(double a, double b, double relative_tolerance, double absolute_tolerance) 502 | { 503 | return fuzzy_compare(a, b, relative_tolerance, absolute_tolerance) > 0; 504 | } 505 | 506 | 507 | } // namespace 508 | 509 | #endif 510 | -------------------------------------------------------------------------------- /tools/fructose/fructose.h: -------------------------------------------------------------------------------- 1 | /* FRUCTOSE C++ unit test library. 2 | Copyright (c) 2012 Andrew Peter Marlow. All rights reserved. 3 | http://www.andrewpetermarlow.co.uk. 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public 7 | License as published by the Free Software Foundation; either 8 | version 2.1 of the License, or (at your option) any later version. 9 | 10 | This library is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public 16 | License along with this library; if not, write to the Free Software 17 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | 20 | #ifndef INCLUDED_FRUCTOSE_FRUCTOSE 21 | #define INCLUDED_FRUCTOSE_FRUCTOSE 22 | 23 | // As you can see, this is just a convenience header 24 | // that saves you giving the two includes below. 25 | 26 | #include 27 | #include 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /tools/fructose/test_base.h: -------------------------------------------------------------------------------- 1 | /* FRUCTOSE C++ unit test library. 2 | Copyright (c) 2014 Andrew Peter Marlow. All rights reserved. 3 | http://www.andrewpetermarlow.co.uk. 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public 7 | License as published by the Free Software Foundation; either 8 | version 2.1 of the License, or (at your option) any later version. 9 | 10 | This library is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public 16 | License along with this library; if not, write to the Free Software 17 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | 20 | #ifndef INCLUDED_FRUCTOSE_TEST_BASE 21 | #define INCLUDED_FRUCTOSE_TEST_BASE 22 | 23 | #include "fructose/test_root.h" 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | /*! \mainpage FRUCTOSE - FRamework for Unit testing C++ for Test driven development Of SoftwarE 33 | * 34 | * \section intro_sec Introduction 35 | * 36 | * FRUCTOSE provides a framework for quickly developing test harnesses for 37 | * use in C++ unit tests. These harnesses are assumed to be part of using 38 | * TDD (Test Driven Development). A given harness will run all the tests 39 | * by default and silently (unless there are errors) but the harness can 40 | * also be used interactively, for selected tests, and with optional 41 | * verbose output. 42 | * 43 | * \section features Features 44 | * 45 | * - Assertions. 46 | * Unlike some other unit test frameworks, FRUCTOSE does not use the 47 | * C macro facility 'assert' for its assertions. 48 | * This means that the harness can run all its tests through to completion 49 | * instead of core dumping at the first failure. 50 | * 51 | * - Loop assertions. 52 | * Macros are provided which help the developer track down the 53 | * reason for assertion failures for data held in static tables. 54 | * What is needed in these cases in addition to the file and line 55 | * number of the assertion is the line number of the data that 56 | * was tested in the assert. 57 | * 58 | * - Exception assertions. 59 | * The test harness may assert that a condition should result in 60 | * the throwing of an exception of a specified type. If it does not 61 | * then the assertion fails. Similarly, a harness may assert that 62 | * no exception is to be thrown upon the evaluation of a condition; 63 | * if one is then the assertion fails. 64 | * 65 | * - floating point compare assertions. 66 | * Floating point comparisons can be asserted for using relative 67 | * or absolute tolerances. 68 | * 69 | * - Fine control over test selection. 70 | * The tests are named in the harness and may be selected by name 71 | * from the command line. By default all tests are run. 72 | * 73 | * - Each test can receive its own command line parameters. 74 | * Each test can obtain any test-specific parameters that were 75 | * passed using the command line. 76 | * 77 | * - Simple test harnesses. 78 | * The harness just defines one class, where each public function 79 | * is designed to be one test case. 80 | * The harness class inherits from fructose::test_base, which 81 | * provides it with three functions: 82 | * -# add_test to add a named test 83 | * -# run with no arguments runs all tests 84 | * -# run with argc and argv runs the tests specified on the command line. 85 | * 86 | * Note: for compatibility with older versions of fructose, 87 | * two additional routines are provided: 88 | * -# get_suite returns a list of tests to run based on parsing the command line 89 | * -# run runs the tests returned by get_suite. 90 | * 91 | * - verbose command line argument option. 92 | * The functions in the test harness class have access to the 93 | * function verbose(), which returns true if the -v flag was given 94 | * on the command line. This is a debugging aid during TDD whereby the 95 | * harness can print useful intermediate values that might shed light 96 | * on why a test is failing. 97 | * 98 | * \section why Why another unit test framework? 99 | * 100 | * \subsection cppunit CppUnit woes 101 | * 102 | * This framework was arrived after several weeks of struggling 103 | * with CppUnit. At the time CppUnit was not even buildable on 104 | * the main platform being used (Solaris). It has subsequently been 105 | * ported but this was just one obstacle among many. 106 | * CppUnit was judged to be too heavy duty for the needs of most 107 | * simple unit test harnesses so a more light duty library was developed. 108 | * This one has just two classes, test_base and test_root, and a 109 | * handful of macros for convenient assertion testing. 110 | * 111 | * \subsection depends Unit test framework dependencies 112 | * 113 | * Some other unit test frameworks rely on other components that are 114 | * not very lightweight and not always very portable. 115 | * FRUCTOSE also has tried to avoid dependencies on other external 116 | * libraries. It is completely standalone. 117 | * It is all done with inlined templates and so does not require 118 | * a library to be linked in. 119 | * 120 | * \subsection simplicity Test harnesses must be simple 121 | * 122 | * Other frameworks tend to require alot of the test harnesses. 123 | * There are sometimes many classes to write and several files. 124 | * The objective with FRUCTOSE was to have a class that is comprised 125 | * of just 3 files; the header, the implementation and the test harness. 126 | * A FRUCTOSE test harness requires just one class to be defined. 127 | * Each public function of that class is designed to be a test case. 128 | * 129 | * \section not What FRUCTOSE does not do 130 | * 131 | * FRUCTOSE does not attempt to provide output tailored for any 132 | * particular reporting mechanism, it just writes any errors 133 | * (along with any verbose output) to std::cout. It is this not 134 | * designed to directly support web-based unit test report 135 | * summaries, unlike CppUnit. 136 | * 137 | * \section misc Miscellaneous notes 138 | * 139 | * FRUCTOSE always writes its output on std::cout. It does not ever write 140 | * to std::cerr unless it is invoked wrongly, e.g with a named test where the 141 | * test name is unknown. The rational behind this is that the harness 142 | * works no matter how many tests pass or fail. This is particularly 143 | * useful when the verbose flag is enabled since verbose output is expected 144 | * to also go to std::cout. This avoids problems with std::cout and std::cerr 145 | * being out of sync (std::cout is buffered by std::cerr is not). 146 | * 147 | * If one or more of the tests fail then the exit status is set to 148 | * EXIT_FAILURE. This is so that any reporting tools built around the 149 | * invocation of these test harnesses can easily determine whether any 150 | * harnesses produced test failures. 151 | */ 152 | namespace fructose { 153 | 154 | /** 155 | * Base class for test container classes 156 | * 157 | * The test container class, i.e., the one which contains the test cases 158 | * as members, must derive from test_base. 159 | * 160 | * Synopsis: 161 | * 162 | * @code 163 | * #include "fructose/fructose.h" 164 | * 165 | * [...] 166 | * 167 | * class my_test : public test_base 168 | * { 169 | * public: 170 | * my_test(int val1, int val2); 171 | * void test_equality(const std::string& test_name); 172 | * void test_foobar(const std::string& test_name); 173 | * [...] 174 | * }; 175 | * 176 | * [...] 177 | * 178 | * void my_test::test_equality(const std::string& the_name) 179 | * { 180 | * fructose_assert(my_test(1,2) == my_test(1,2)); 181 | * } 182 | * 183 | * [...] 184 | * 185 | * void my_test::test_foobar(const std::string& test_name) 186 | * { 187 | * fructose_assert_exception(my_test(1,4).foobar(), MathException); 188 | * } 189 | * 190 | * int main(int argc, char* argv[]) 191 | * { 192 | * my_test tests; 193 | * 194 | * tests.add_test("testEquality", &my_test::test_equality); 195 | * tests.add_test("testAddition", &my_test::test_foobar); 196 | * 197 | * [...] 198 | * 199 | * return tests.run(argc, argv); 200 | * } 201 | * 202 | * @endcode 203 | * 204 | * There should be a test driver for each library module (*.cpp file with 205 | * associated header), structured like the example above. Test cases are 206 | * represented as member functions of an ad-hoc class ("test container class") 207 | * which must derive from the template test_base with itself as the template 208 | * argument. (This is the Curiously Recurring Template Pattern CRTP.) 209 | * 210 | * In writing the test code, the test assert macros defined below come 211 | * in handy. 212 | * - fructose_assert(condition) 213 | * - fructose_loop1_assert(line, 1stLoopCounter, condition) 214 | * - fructose_loop2_assert(line, 1stLoopCounter, 2ndLoopCounter, condition) 215 | * 216 | * Test drivers accept a set of standard command line arguments which 217 | * are displayed if the -h option is set: 218 | * 219 | * @code 220 | * 221 | * USAGE: 222 | * 223 | * ./example [-h] [-r] [-a] [-v] [--] ... 224 | * 225 | * Where: 226 | * 227 | * -h, --help produces this help 228 | * 229 | * -r, --reverse reverses the sense of test assertions. 230 | 231 | * -a, --assert_fatal is used to make the first test failure fatal. 232 | * 233 | * -v, --verbose turns on extra trace for those tests that have made use of it. 234 | * 235 | * --, --ignore_rest Ignores the rest of the labeled arguments following this flag. 236 | * 237 | * 238 | * (accepted multiple times) 239 | * test names 240 | * 241 | * Any number of test names may be supplied on the command line. If the 242 | * name 'all' is included then all tests will be run. 243 | * 244 | * 245 | * Supported test names are: 246 | * test_equality 247 | * test_foobar 248 | * 249 | * @endcode 250 | * 251 | * Notice the list of configured test cases displayed after the command line 252 | * options. Test cases can be named explicitly when running the driver, which 253 | * restricts execution to the named test cases. 254 | * 255 | * @code 256 | * $ testdriver -v test_foobar 257 | * running test_foobar 258 | * @endcode 259 | * 260 | * This is useful when trying to focus on one test case. 261 | */ 262 | template 263 | class test_base : public test_root 264 | { 265 | public: 266 | 267 | /** 268 | * Type of test case functions 269 | * 270 | * Test cases are implemented as member functions of the test container 271 | * class, which must be derived from this template (CRTP). 272 | * These methods return void and accept one parameter: the 273 | * name of the test case. 274 | */ 275 | typedef void (test_container::*test_case)(const std::string&); 276 | 277 | // compiler-generated default constructor would be OK 278 | // but gives warnings with GCC's -Weffc++. 279 | 280 | test_base() 281 | : m_tests() 282 | , m_available_tests(suite()) 283 | , m_exceptionPending("") 284 | { 285 | } 286 | 287 | /** 288 | * Register a test case 289 | * 290 | * Register a member function implementing a test case against the 291 | * name of the test case 292 | * 293 | * @param name - The name of the test case 294 | * @param the_test - The member function implementing the test case 295 | */ 296 | void add_test(const std::string& name, test_case the_test); 297 | 298 | /** 299 | * Run statically configured tests 300 | * 301 | * This function runs all the registered tests in the sequence in 302 | * which they were registered. 303 | * 304 | * @return - An integer suitable as main exit status: EXIT_SUCCESS 305 | * if the the tests were successful, EXIT_FAILURE otherwise. 306 | */ 307 | int run(); 308 | 309 | /** 310 | * Run the test specified on the command line 311 | * (runs all tests if none specified). 312 | * Flags are set for various command line options 313 | * such as the verbose flag and first assertion is fatal. 314 | */ 315 | int run(int argc, char* argv[]); 316 | 317 | /** 318 | * Run tests configured in test suite 319 | * 320 | * This function runs the tests named in the test suite in the sequence 321 | * by the suite. If the suite contains a name which is not registered, 322 | * a warning message is issued. 323 | * 324 | * @param suite - The test suite 325 | * 326 | * @return - An integer suitable as main exit status: EXIT_SUCCESS 327 | * if the the tests were successful, EXIT_FAILURE otherwise. 328 | */ 329 | int run(const suite& suite); 330 | 331 | /** 332 | * Return a list of test names from the command line. 333 | * 334 | * This function returns a suite containing all the test cases named on 335 | * the command line. If none are named, or if the keyword "all" is passed, 336 | * all the configured test cases are included. 337 | * 338 | * Note: This routine is provided for compatibility with older 339 | * versions of fructose. New programs should use the function 340 | * run(argc, argv). 341 | * 342 | * @param argc - The argument count passed to main 343 | * @param argv - The argument vector passed to main 344 | * 345 | * @return The configured test suite 346 | */ 347 | suite get_suite(int argc, char* argv[]); 348 | 349 | private: 350 | 351 | /** 352 | * Helper routine to provide the main functionality of run but 353 | * with added exception handling so the test harness does not have 354 | * to do it. 355 | */ 356 | int do_run(const suite& suite); 357 | 358 | /** 359 | * Collection of test cases, keyed by their names 360 | */ 361 | std::map > m_tests; 362 | 363 | /* 364 | * Defines the sequence in which tests have to be run. 365 | */ 366 | suite m_available_tests; 367 | 368 | /* 369 | * If the fructose machinery itself has an error, then 370 | * it is stored as a pending exception in this string. 371 | * This enables the string to be checked when run is called. 372 | */ 373 | std::string m_exceptionPending; 374 | }; 375 | 376 | // ==================== 377 | // INLINE definitions 378 | // ==================== 379 | 380 | template 381 | inline void 382 | test_base::add_test(const std::string& name, 383 | test_case the_test) 384 | { 385 | if (m_exceptionPending.length() > 0) 386 | { 387 | return; 388 | } 389 | 390 | typename std::map >::const_iterator it = m_tests.find(name); 391 | if (it == m_tests.end()) 392 | { 393 | m_tests[name] = std::make_pair(the_test, test_info(name)); 394 | m_available_tests.push_back(name); 395 | } 396 | else 397 | { 398 | std::stringstream str; 399 | str << "add_test called with test name '" << name 400 | << "' which has already been added."; 401 | m_exceptionPending = str.str(); 402 | } 403 | } 404 | 405 | template 406 | inline int 407 | test_base::run() 408 | { 409 | return run(m_available_tests); 410 | } 411 | 412 | template 413 | inline int 414 | test_base::run(int argc, char* argv[]) 415 | { 416 | int exitStatus = EXIT_SUCCESS; 417 | 418 | if (m_exceptionPending.length() > 0) 419 | { 420 | std::cout << "ERROR in use of FRUCTOSE: " << m_exceptionPending << std::endl; 421 | exitStatus = EXIT_FAILURE; 422 | } 423 | else 424 | { 425 | test_root::suite the_suite = get_suite(argc, argv); 426 | exitStatus = run(the_suite); 427 | } 428 | 429 | return exitStatus; 430 | } 431 | 432 | template 433 | inline int 434 | test_base::run(const test_root::suite& suite) 435 | { 436 | try 437 | { 438 | return do_run(suite); 439 | } 440 | catch(...) 441 | { 442 | return fructose::test_root::exception_handler(); 443 | } 444 | } 445 | 446 | template 447 | inline int 448 | test_base::do_run(const suite& suite) 449 | { 450 | test_container* runner = dynamic_cast(this); 451 | 452 | if (runner == 0) 453 | { 454 | throw std::runtime_error( 455 | "problem in test set-up; probable cause: " 456 | "test container class not passed to test_base template"); 457 | } 458 | 459 | for (typename suite::const_iterator it = suite.begin(); it != suite.end(); ++it) 460 | { 461 | std::pair value = m_tests[it->m_test_name]; 462 | test_case test_case = value.first; 463 | if (test_case) 464 | { 465 | const std::string title = "Running test case " + it->m_test_name; 466 | if (verbose()) 467 | { 468 | std::cout << std::endl << title << std::endl 469 | << underline(title) << '\n' << std::endl; 470 | } 471 | runner->setup(); 472 | runner->set_test_info(&(*it)); 473 | try 474 | { 475 | (runner->*test_case)(it->m_test_name); 476 | runner->teardown(); 477 | } 478 | catch(std::exception& ex) 479 | { 480 | runner->teardown(); 481 | set_exception_happened(); 482 | std::cout << ex.what() << std::endl; 483 | } 484 | } 485 | else 486 | { 487 | std::cerr << "No such test case: " << it->m_test_name << std::endl; 488 | } 489 | } 490 | 491 | return return_status(); 492 | } 493 | 494 | template 495 | inline 496 | test_root::suite test_base::get_suite(int argc, char* argv[]) 497 | { 498 | return do_get_suite(m_available_tests, argc, argv); 499 | } 500 | 501 | } // namespace 502 | 503 | // Macros for test harness code generator. 504 | 505 | #define FRUCTOSE_STRUCT(name) struct name : public fructose::test_base 506 | #define FRUCTOSE_CLASS(name) class name : public fructose::test_base 507 | #define FRUCTOSE_TEST(name) void name(const std::string& test_name) 508 | 509 | #endif 510 | -------------------------------------------------------------------------------- /unittest.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | Beckhoff BIOS API driver to access hwmon sensors for Beckhoff IPCs 4 | Copyright (C) 2014 - 2018 Beckhoff Automation GmbH & Co. KG 5 | Author: Patrick Bruenn 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #ifndef __FreeBSD__ 17 | #include 18 | #include 19 | #endif /* #ifndef __FreeBSD__ */ 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #include "TcBaDevDef.h" 26 | 27 | #include 28 | 29 | /** 30 | * Unittest configuration 31 | */ 32 | #include "test_config.h" 33 | 34 | #ifndef TEST_DEVICE 35 | #error "no test configuration is enabled in test_config.h" 36 | #else 37 | 38 | #ifdef __FreeBSD__ 39 | #undef CONFIG_WATCHDOG_DISABLED 40 | #define CONFIG_WATCHDOG_DISABLED 1 41 | #endif /* #ifdef __FreeBSD__ */ 42 | 43 | #define pr_info printf 44 | 45 | using namespace fructose; 46 | 47 | static void WaitForUserInput(void) 48 | { 49 | if (CONFIG_INTERACTIVE) { 50 | std::cin.get(); 51 | } else { 52 | std::cout << '\n'; 53 | } 54 | } 55 | 56 | template 57 | struct BiosString 58 | { 59 | char data[N]; 60 | BiosString(const char* text = NULL) 61 | { 62 | if(text) { 63 | strncpy(data, text, sizeof(data)); 64 | } else { 65 | memset(data, 0, sizeof(data)); 66 | } 67 | } 68 | 69 | bool operator==(const BiosString& ref) const { 70 | return 0 == memcmp(this, &ref, sizeof(*this)); 71 | } 72 | 73 | int snprintf(char* buffer, size_t len) const { 74 | return ::snprintf(buffer, len, "%.*s", (int)sizeof(data), data); 75 | }; 76 | }; 77 | 78 | template 79 | struct BiosTriple 80 | { 81 | T data[3]; 82 | BiosTriple(T first = 0, T second = 0, T third = 0) 83 | : data{first, second, third} 84 | { 85 | } 86 | 87 | bool operator==(const BiosTriple& ref) const { 88 | return 0 == memcmp(this, &ref, sizeof(*this)); 89 | } 90 | 91 | int snprintf(char* buffer, size_t len) const { 92 | return ::snprintf(buffer, len, "%d.%d-%d", data[0], data[1], data[2]); 93 | }; 94 | }; 95 | typedef BiosTriple BiosVersion; 96 | 97 | struct BiosPair 98 | { 99 | uint8_t first; 100 | uint8_t second; 101 | BiosPair(uint8_t x = 0, uint8_t y = 0) 102 | : first(x), second(y) 103 | { 104 | } 105 | 106 | bool operator==(const BiosPair& ref) const { 107 | return 0 == memcmp(this, &ref, sizeof(*this)); 108 | } 109 | 110 | int snprintf(char* buffer, size_t len) const { 111 | return ::snprintf(buffer, len, "%d-%d", first, second); 112 | }; 113 | }; 114 | 115 | int ioctl_read(int file, uint32_t group, uint32_t offset, void* out, uint32_t size, uint32_t *pBytesReturned) 116 | { 117 | struct bbapi_struct data {group, offset, NULL, 0, out, size 118 | #if BIOSAPIERR_OFFSET > 0 119 | , pBytesReturned 120 | #endif 121 | }; 122 | if (-1 == ioctl(file, BBAPI_CMD, &data)) { 123 | pr_info("%s(): failed for group: 0x%x offset: 0x%x with errno: %s\n", __FUNCTION__, group, offset, strerror(errno)); 124 | return -1; 125 | } 126 | return 0; 127 | } 128 | 129 | int ioctl_write(int file, uint32_t group, uint32_t offset, const void* in, uint32_t size) 130 | { 131 | struct bbapi_struct data {group, offset, in, size, NULL, 0}; 132 | if (-1 == ioctl(file, BBAPI_CMD, &data)) { 133 | pr_info("%s(): failed for group: 0x%x offset: 0x%x with errno: %s\n", __FUNCTION__, group, offset, strerror(errno)); 134 | return -1; 135 | } 136 | return 0; 137 | } 138 | 139 | struct BiosApi 140 | { 141 | BiosApi(unsigned long group = 0) 142 | : m_File(open(FILE_PATH, O_RDWR)), 143 | m_Group(group) 144 | { 145 | if (-1 == m_File) { 146 | pr_info("Open device file '%s' failed!\n", FILE_PATH); 147 | throw new std::runtime_error("Open device file failed!"); 148 | } 149 | } 150 | 151 | ~BiosApi() 152 | { 153 | close(m_File); 154 | }; 155 | 156 | void setGroupOffset(unsigned long group) 157 | { 158 | m_Group = group; 159 | } 160 | 161 | int ioctl_read(uint32_t offset, void* out, unsigned long size, uint32_t *pBytesReturned) const 162 | { 163 | return ::ioctl_read(m_File, m_Group, offset, out, size, pBytesReturned); 164 | } 165 | 166 | int ioctl_write(uint32_t offset, const void* in, unsigned long size) const 167 | { 168 | return ::ioctl_write(m_File, m_Group, offset, in, size); 169 | } 170 | 171 | protected: 172 | const int m_File; 173 | unsigned long m_Group; 174 | }; 175 | 176 | struct TestBBAPI : fructose::test_base 177 | { 178 | #define UNITTEST_DO_COMPARE true 179 | #define CHECK_VALUE(MSG, INDEX_OFFSET, EXPECTATION, TYPE) \ 180 | test_range(#INDEX_OFFSET, INDEX_OFFSET, EXPECTATION, EXPECTATION, MSG, UNITTEST_DO_COMPARE) 181 | #define CHECK_CLASS(MSG, INDEX_OFFSET, EXPECTATION, TYPE) \ 182 | test_object(#INDEX_OFFSET, INDEX_OFFSET, EXPECTATION, MSG, UNITTEST_DO_COMPARE) 183 | #define CHECK_RANGE(MSG, INDEX_OFFSET, RANGE, TYPE) \ 184 | test_range(#INDEX_OFFSET, INDEX_OFFSET, RANGE, MSG, UNITTEST_DO_COMPARE) 185 | #define READ_OBJECT(MSG, INDEX_OFFSET, TYPE) \ 186 | test_object(#INDEX_OFFSET, INDEX_OFFSET, 0, MSG, false) 187 | 188 | void test_CXPowerSupply(const std::string& test_name) 189 | { 190 | #if CONFIG_CXPWRSUPP_DISABLED 191 | pr_info("\nCX power supply test case disabled\n"); 192 | return; 193 | #else 194 | bbapi.setGroupOffset(BIOSIGRP_CXPWRSUPP); 195 | pr_info("\nCX power supply test results:\n=============================\n"); 196 | CHECK_VALUE("Type: %04d\n", BIOSIOFFS_CXPWRSUPP_GETTYPE, CONFIG_CXPWRSUPP_TYPE, uint32_t); 197 | CHECK_VALUE("Serial: %04d\n", BIOSIOFFS_CXPWRSUPP_GETSERIALNO, CONFIG_CXPWRSUPP_SERIALNO, uint32_t); 198 | CHECK_CLASS("Fw ver.: %s\n", BIOSIOFFS_CXPWRSUPP_GETFWVERSION, CONFIG_CXPWRSUPP_FWVERSION, BiosPair); 199 | CHECK_RANGE("Boot #: %04d\n", BIOSIOFFS_CXPWRSUPP_GETBOOTCOUNTER, CONFIG_CXPWRSUPP_BOOTCOUNTER_RANGE, uint32_t); 200 | CHECK_RANGE("Optime: %04d min.\n",BIOSIOFFS_CXPWRSUPP_GETOPERATIONTIME, CONFIG_CXPWRSUPP_OPERATIONTIME_RANGE, uint32_t); 201 | CHECK_RANGE("act. 5V: %5d mV\n", BIOSIOFFS_CXPWRSUPP_GET5VOLT, CONFIG_CXPWRSUPP_5VOLT_RANGE, uint16_t); 202 | CHECK_RANGE("max. 5V: %5d mV\n", BIOSIOFFS_CXPWRSUPP_GETMAX5VOLT, CONFIG_CXPWRSUPP_5VOLT_RANGE, uint16_t); 203 | CHECK_RANGE("act. 12V: %5d mV\n", BIOSIOFFS_CXPWRSUPP_GET12VOLT, CONFIG_CXPWRSUPP_12VOLT_RANGE, uint16_t); 204 | CHECK_RANGE("max. 12V: %5d mV\n", BIOSIOFFS_CXPWRSUPP_GETMAX12VOLT, CONFIG_CXPWRSUPP_12VOLT_RANGE, uint16_t); 205 | CHECK_RANGE("act. 24V: %5d mV\n", BIOSIOFFS_CXPWRSUPP_GET24VOLT, CONFIG_CXPWRSUPP_24VOLT_RANGE, uint16_t); 206 | CHECK_RANGE("max. 24V: %5d mV\n", BIOSIOFFS_CXPWRSUPP_GETMAX24VOLT, CONFIG_CXPWRSUPP_24VOLT_RANGE, uint16_t); 207 | CHECK_RANGE("act. temp.: %5d C°\n", BIOSIOFFS_CXPWRSUPP_GETTEMP, CONFIG_CXPWRSUPP_TEMP_RANGE, int8_t); 208 | CHECK_RANGE("min. temp.: %5d C°\n", BIOSIOFFS_CXPWRSUPP_GETMINTEMP, CONFIG_CXPWRSUPP_TEMP_RANGE, int8_t); 209 | CHECK_RANGE("max. temp.: %5d C°\n", BIOSIOFFS_CXPWRSUPP_GETMAXTEMP, CONFIG_CXPWRSUPP_TEMP_RANGE, int8_t); 210 | CHECK_RANGE("act. current: %5d mA\n", BIOSIOFFS_CXPWRSUPP_GETCURRENT, CONFIG_CXPWRSUPP_CURRENT_RANGE, uint16_t); 211 | CHECK_RANGE("max. current: %5d mA\n", BIOSIOFFS_CXPWRSUPP_GETMAXCURRENT, CONFIG_CXPWRSUPP_CURRENT_RANGE, uint16_t); 212 | CHECK_RANGE("act. power: %5d mW\n", BIOSIOFFS_CXPWRSUPP_GETPOWER, CONFIG_CXPWRSUPP_POWER_RANGE, uint32_t); 213 | CHECK_RANGE("max. power: %5d mW\n", BIOSIOFFS_CXPWRSUPP_GETMAXPOWER, CONFIG_CXPWRSUPP_POWER_RANGE, uint32_t); 214 | CHECK_VALUE("button state: 0x%02x\n", BIOSIOFFS_CXPWRSUPP_GETBUTTONSTATE, CONFIG_CXPWRSUPP_BUTTON_STATE, uint8_t); 215 | #endif 216 | } 217 | 218 | void test_CXPowerSupply_display(const std::string& test_name) 219 | { 220 | #if CONFIG_CXPWRSUPP_DISABLED 221 | pr_info("\nCX power supply write test case disabled\n"); 222 | return; 223 | #else 224 | static const char empty[16+1] = " "; 225 | static const char line1[16+1] = "1234567890123456"; 226 | static const char line2[16+1] = "6543210987654321"; 227 | bbapi.setGroupOffset(BIOSIGRP_CXPWRSUPP); 228 | pr_info("\nCX power supply display test:\n=============================\n"); 229 | uint8_t backlight = 0; 230 | fructose_assert(!bbapi.ioctl_write(BIOSIOFFS_CXPWRSUPP_ENABLEBACKLIGHT, &backlight, sizeof(backlight))); 231 | fructose_assert(!bbapi.ioctl_write(BIOSIOFFS_CXPWRSUPP_DISPLAYLINE1, &empty, sizeof(empty))); 232 | fructose_assert(!bbapi.ioctl_write(BIOSIOFFS_CXPWRSUPP_DISPLAYLINE2, &empty, sizeof(empty))); 233 | pr_info("Backlight should be OFF\n"); 234 | std::this_thread::sleep_for(std::chrono::seconds(1)); 235 | backlight = 0xff; 236 | fructose_assert(!bbapi.ioctl_write(BIOSIOFFS_CXPWRSUPP_ENABLEBACKLIGHT, &backlight, sizeof(backlight))); 237 | pr_info("Backlight should be ON\n"); 238 | pr_info("Display should be empty\n"); 239 | std::this_thread::sleep_for(std::chrono::seconds(1)); 240 | fructose_assert(!bbapi.ioctl_write(BIOSIOFFS_CXPWRSUPP_DISPLAYLINE1, &line1, sizeof(line1))); 241 | fructose_assert(!bbapi.ioctl_write(BIOSIOFFS_CXPWRSUPP_DISPLAYLINE2, &line2, sizeof(line2))); 242 | pr_info("Display should show:\n%s\n%s\n\n", line1, line2); 243 | #endif 244 | } 245 | 246 | void test_CXUPS(const std::string& test_name) 247 | { 248 | if(!CONFIG_CXUPS_ENABLED) { 249 | pr_info("\nCX UPS test case disabled\n"); 250 | return; 251 | } 252 | bbapi.setGroupOffset(BIOSIGRP_CXUPS); 253 | pr_info("\nCX UPS test results:\n====================\n"); 254 | CHECK_VALUE("UPS enabled: 0x%02x\n", BIOSIOFFS_CXUPS_GETENABLED, CONFIG_CXUPS_ENABLED, uint8_t); 255 | CHECK_CLASS("Fw ver.: %s\n", BIOSIOFFS_CXUPS_GETFIRMWAREVER, CONFIG_CXUPS_FIRMWAREVER, BiosPair); 256 | CHECK_VALUE("Power status: 0x%02x\n", BIOSIOFFS_CXUPS_GETPOWERSTATUS, CONFIG_CXUPS_POWERSTATUS, uint8_t); 257 | CHECK_VALUE("Battery status: 0x%02x\n", BIOSIOFFS_CXUPS_GETBATTERYSTATUS, CONFIG_CXUPS_BATTERYSTATUS, uint8_t); 258 | CHECK_VALUE("Battery capacity: %9d %\n", BIOSIOFFS_CXUPS_GETBATTERYCAPACITY, CONFIG_CXUPS_BATTERYCAPACITY, uint8_t); 259 | CHECK_RANGE("Battery runtime: %9d sec.\n", BIOSIOFFS_CXUPS_GETBATTERYRUNTIME, CONFIG_CXUPS_BATTERYRUNTIME_RANGE, uint32_t); 260 | CHECK_RANGE("Boot: %9d #\n", BIOSIOFFS_CXUPS_GETBOOTCOUNTER, CONFIG_CXUPS_BOOTCOUNTER_RANGE, uint32_t); 261 | CHECK_RANGE("Optime: %9d min.\n", BIOSIOFFS_CXUPS_GETOPERATIONTIME, CONFIG_CXUPS_OPERATIONTIME_RANGE, uint32_t); 262 | CHECK_VALUE("Power fail: %9d #\n", BIOSIOFFS_CXUPS_GETPOWERFAILCOUNT, CONFIG_CXUPS_POWERFAILCOUNT, uint32_t); 263 | CHECK_VALUE("Battery critical: 0x%02x\n", BIOSIOFFS_CXUPS_GETBATTERYCRITICAL, CONFIG_CXUPS_BATTERYCRITICAL, uint8_t); 264 | CHECK_VALUE("Battery present: 0x%02x\n", BIOSIOFFS_CXUPS_GETBATTERYPRESENT, CONFIG_CXUPS_BATTERYPRESENT, uint8_t); 265 | CHECK_RANGE("act. output: %9d mV\n", BIOSIOFFS_CXUPS_GETOUTPUTVOLT, CONFIG_CXUPS_OUTPUTVOLT_RANGE, uint16_t); 266 | CHECK_RANGE("max. output: %9d mV\n", BIOSIOFFS_CXUPS_GETMAXOUTPUTVOLT, CONFIG_CXUPS_OUTPUTVOLT_RANGE, uint16_t); 267 | CHECK_RANGE("act. input: %9d mV\n", BIOSIOFFS_CXUPS_GETINPUTVOLT, CONFIG_CXUPS_INPUTVOLT_RANGE, uint16_t); 268 | CHECK_RANGE("max. input: %9d mV\n", BIOSIOFFS_CXUPS_GETMAXINPUTVOLT, CONFIG_CXUPS_INPUTVOLT_RANGE, uint16_t); 269 | CHECK_RANGE("act. temp.: %9d C°\n", BIOSIOFFS_CXUPS_GETTEMP, CONFIG_CXUPS_TEMP_RANGE, int8_t); 270 | CHECK_RANGE("min. temp.: %9d C°\n", BIOSIOFFS_CXUPS_GETMINTEMP, CONFIG_CXUPS_TEMP_RANGE, int8_t); 271 | CHECK_RANGE("max. temp.: %9d C°\n", BIOSIOFFS_CXUPS_GETMAXTEMP, CONFIG_CXUPS_TEMP_RANGE, int8_t); 272 | CHECK_VALUE("act. charging: %9d mA\n", BIOSIOFFS_CXUPS_GETCHARGINGCURRENT, CONFIG_CXUPS_CURRENT, uint16_t); 273 | CHECK_RANGE("max. charging: %9d mA\n", BIOSIOFFS_CXUPS_GETMAXCHARGINGCURRENT, CONFIG_CXUPS_CURRENT_RANGE, uint16_t); 274 | CHECK_VALUE("act. charging: %9d mW\n", BIOSIOFFS_CXUPS_GETCHARGINGPOWER, CONFIG_CXUPS_POWER, uint32_t); 275 | CHECK_RANGE("max. charging: %9d mW\n", BIOSIOFFS_CXUPS_GETMAXCHARGINGPOWER, CONFIG_CXUPS_POWER_RANGE, uint32_t); 276 | CHECK_VALUE("act. discharging: %9d mA\n", BIOSIOFFS_CXUPS_GETDISCHARGINGCURRENT, CONFIG_CXUPS_CURRENT, uint16_t); 277 | CHECK_RANGE("max. discharging: %9d mA\n", BIOSIOFFS_CXUPS_GETMAXDISCHARGINGCURRENT, CONFIG_CXUPS_CURRENT_RANGE, uint16_t); 278 | CHECK_VALUE("act. discharging: %9d mW\n", BIOSIOFFS_CXUPS_GETDISCHARGINGPOWER, CONFIG_CXUPS_POWER, uint32_t); 279 | CHECK_RANGE("max. discharging: %9d mW\n", BIOSIOFFS_CXUPS_GETMAXDISCHARGINGPOWER, CONFIG_CXUPS_POWER_RANGE, uint32_t); 280 | #if 0 281 | //#define CONFIG_CXUPS_RESTARTMODE 0x0000000C // OBSOLETE: Don't use it! Get restart mode, W:0, R:1 (BYTE) (0 := Disabled, 1 := Enabled) 282 | // #define BIOSIOFFS_CXUPS_SETRESTARTMODE 0x0000000D // OBSOLETE: Don't use it! Set restart mode, W:1 (BYTE) (0 := Disable, 1 := Enable), R:0 283 | #define BIOSIOFFS_CXUPS_SETSHUTDOWNMODE 0x0000000E // Set shutdown mode, W:1 (BYTE) (0 := Disable, 1:= Enable), R:0 284 | #define CONFIG_CXUPS_BATTSERIALNUMBER 0x00000011 // Get serial number W:0, R:4 285 | #define CONFIG_CXUPS_BATTHARDWAREVERSION 0x00000015 // Get hardware version W:0, R:2 286 | #define CONFIG_CXUPS_BATTPRODUCTIONDATE 0x00000019 // Get production date W:0, R:4 287 | #define CONFIG_CXUPS_LASTBATTCHANGEDATE 0x00000097 // Gets last battery change date , W:0, R:4 288 | #define BIOSIOFFS_CXUPS_SETLASTBATTCHANGEDATE 0x00000098 // Sets last battery change date W:4, R:0 289 | #define CONFIG_CXUPS_BATTRATEDCAPACITY 0x00000099 // Get rated capacity W:0, R:4 [mAh] 290 | #define CONFIG_CXUPS_SMBUSADDRESS 0x000000F0 // Get SMBus address W:0, R:2 (hHost, address) 291 | #endif 292 | } 293 | 294 | void test_General(const std::string& test_name) 295 | { 296 | bbapi.setGroupOffset(BIOSIGRP_GENERAL); 297 | pr_info("\nGeneral test results:\n=====================\n"); 298 | CHECK_CLASS("Mainboard: %s\n", BIOSIOFFS_GENERAL_GETBOARDINFO, CONFIG_GENERAL_BOARDINFO, BADEVICE_MBINFO); 299 | CHECK_CLASS("Board: %s\n", BIOSIOFFS_GENERAL_GETBOARDNAME, CONFIG_GENERAL_BOARDNAME, BiosString); 300 | CHECK_VALUE("platform: 0x%02x (0x00->32 bit, 0x01-> 64bit)\n", BIOSIOFFS_GENERAL_GETPLATFORMINFO, CONFIG_GENERAL_PLATFORM, uint8_t); 301 | CHECK_CLASS("BIOS API %s\n", BIOSIOFFS_GENERAL_VERSION, CONFIG_GENERAL_VERSION, BADEVICE_VERSION); 302 | } 303 | 304 | void test_LED(const std::string& test_name, const std::string& led_name, uint32_t offset) 305 | { 306 | const size_t num_colors = 4; 307 | const char colors [num_colors][6] = { 308 | "off", 309 | "red", 310 | "blue", 311 | "green", 312 | }; 313 | for (uint8_t color = num_colors - 1; color < num_colors; --color) { 314 | fructose_assert(!bbapi.ioctl_write(offset, &color, sizeof(color))); 315 | pr_info("%s should be %s, hit ENTER to continue", led_name.c_str(), colors[color]); 316 | WaitForUserInput(); 317 | } 318 | } 319 | 320 | void test_manual_LEDs(const std::string& test_name) 321 | { 322 | bbapi.setGroupOffset(BIOSIGRP_LED); 323 | pr_info("\nLED test:\n=========\n"); 324 | if (CONFIG_LED_TC_ENABLED) { 325 | test_LED(test_name, "TwinCAT LED", BIOSIOFFS_LED_SET_TC); 326 | } else { 327 | pr_info("TwinCAT LED test case disabled\n"); 328 | } 329 | 330 | if (CONFIG_LED_USER_ENABLED) { 331 | test_LED(test_name, "User LED", BIOSIOFFS_LED_SET_USER); 332 | } else { 333 | pr_info("User LED test case disabled\n"); 334 | } 335 | } 336 | 337 | void test_PwrCtrl(const std::string& test_name) 338 | { 339 | bbapi.setGroupOffset(BIOSIGRP_PWRCTRL); 340 | pr_info("\nPower control test results:\n===========================\n"); 341 | CHECK_CLASS("Bl ver.: %s\n", BIOSIOFFS_PWRCTRL_BOOTLDR_REV, CONFIG_PWRCTRL_BL_REVISION, BiosVersion); 342 | CHECK_CLASS("Fw ver.: %s\n", BIOSIOFFS_PWRCTRL_FIRMWARE_REV, CONFIG_PWRCTRL_FW_REVISION, BiosVersion); 343 | CHECK_VALUE("Device id: 0x%02x\n", BIOSIOFFS_PWRCTRL_DEVICE_ID, CONFIG_PWRCTRL_DEVICE_ID, uint8_t); 344 | CHECK_RANGE("Optime: %04d min.\n",BIOSIOFFS_PWRCTRL_OPERATING_TIME, CONFIG_PWRCTRL_OPERATION_TIME_RANGE, uint32_t); 345 | READ_OBJECT("Temp. [min-max]: %s °C\n", BIOSIOFFS_PWRCTRL_BOARD_TEMP, BiosPair); 346 | READ_OBJECT("Volt. [min-max]: %s V\n", BIOSIOFFS_PWRCTRL_INPUT_VOLTAGE, BiosPair); 347 | CHECK_CLASS("Serial: %s\n", BIOSIOFFS_PWRCTRL_SERIAL_NUMBER, CONFIG_PWRCTRL_SERIAL, BiosString); 348 | CHECK_RANGE("Boot #: %04d\n", BIOSIOFFS_PWRCTRL_BOOT_COUNTER, CONFIG_PWRCTRL_BOOT_COUNTER_RANGE, uint16_t); 349 | CHECK_CLASS("Production date: %s\n", BIOSIOFFS_PWRCTRL_PRODUCTION_DATE, CONFIG_PWRCTRL_PRODUCTION_DATE, BiosPair); 350 | CHECK_VALUE("µC Position: 0x%02x\n", BIOSIOFFS_PWRCTRL_BOARD_POSITION, CONFIG_PWRCTRL_POSITION, uint8_t); 351 | if (CONFIG_PWRCTRL_LAST_SHUTDOWN_ENABLED) { 352 | READ_OBJECT("Last shutdown reason: %s\n", BIOSIOFFS_PWRCTRL_SHUTDOWN_REASON, BiosVersion); 353 | } 354 | CHECK_VALUE("Test count: %03d\n", BIOSIOFFS_PWRCTRL_TEST_COUNTER, CONFIG_PWRCTRL_TEST_COUNT, uint8_t); 355 | CHECK_CLASS("Test number: %s\n", BIOSIOFFS_PWRCTRL_TEST_NUMBER, CONFIG_PWRCTRL_TEST_NUMBER, BiosString); 356 | } 357 | 358 | void test_SUPS(const std::string& test_name) 359 | { 360 | #if CONFIG_SUPS_DISABLED 361 | pr_info("S-UPS test case disabled\n"); 362 | return; 363 | #else 364 | bbapi.setGroupOffset(BIOSIGRP_SUPS); 365 | pr_info("\nSUPS test results:\n==================\n"); 366 | uint8_t enable = 0; 367 | fructose_assert(!bbapi.ioctl_write(BIOSIOFFS_SUPS_ENABLE, &enable, sizeof(enable))); 368 | std::this_thread::sleep_for(std::chrono::milliseconds(1500)); 369 | CHECK_VALUE("Status: 0x%02x\n", BIOSIOFFS_SUPS_STATUS, CONFIG_SUPS_STATUS_OFF, uint8_t); 370 | enable = 1; 371 | fructose_assert(!bbapi.ioctl_write(BIOSIOFFS_SUPS_ENABLE, &enable, sizeof(enable))); 372 | std::this_thread::sleep_for(std::chrono::milliseconds(1500)); 373 | CHECK_VALUE("Status: 0x%02x\n", BIOSIOFFS_SUPS_STATUS, CONFIG_SUPS_STATUS_ON, uint8_t); 374 | 375 | CHECK_CLASS("Revision: %s\n", BIOSIOFFS_SUPS_REVISION, CONFIG_SUPS_REVISION, BiosPair); 376 | CHECK_RANGE("Power fail: %9d #\n", BIOSIOFFS_SUPS_PWRFAIL_COUNTER, CONFIG_SUPS_POWERFAILCOUNT_RANGE, uint16_t); 377 | READ_OBJECT("Power failed: %s\n", BIOSIOFFS_SUPS_PWRFAIL_TIMES, BiosTriple); 378 | 379 | uint8_t shutdownType[] {0x01, 0xA1, 0xFF}; 380 | for(size_t i = 0; i < sizeof(shutdownType); ++i) { 381 | fructose_assert(!bbapi.ioctl_write(BIOSIOFFS_SUPS_SET_SHUTDOWN_TYPE, &shutdownType[i], sizeof(shutdownType[0]))); 382 | CHECK_VALUE("Shutdown type: 0x%02x\n", BIOSIOFFS_SUPS_GET_SHUTDOWN_TYPE, shutdownType[i], uint8_t); 383 | } 384 | 385 | CHECK_VALUE("S-UPS active: %9d #\n", BIOSIOFFS_SUPS_ACTIVE_COUNT, CONFIG_SUPS_ACTIVE_COUNT, uint8_t); 386 | CHECK_VALUE("S-UPS Power fail: %9d #\n", BIOSIOFFS_SUPS_INTERNAL_PWRF_STATUS, CONFIG_SUPS_INTERNAL_PWRF_STATUS, uint8_t); 387 | CHECK_VALUE("Capacitor test: %9d #\n", BIOSIOFFS_SUPS_TEST_RESULT, CONFIG_SUPS_TEST_RESULT, uint8_t); 388 | #ifdef CONFIG_SUPS_GPIO_PIN_EX 389 | CHECK_CLASS("GPIO: %s\n", BIOSIOFFS_SUPS_GPIO_PIN_EX, CONFIG_SUPS_GPIO_PIN_EX, Bapi_GpioInfoEx); 390 | #else 391 | CHECK_CLASS("GPIO: %s\n", BIOSIOFFS_SUPS_GPIO_PIN, CONFIG_SUPS_GPIO_PIN, TSUps_GpioInfo); 392 | #endif 393 | #endif 394 | } 395 | 396 | void test_System(const std::string& test_name) 397 | { 398 | bbapi.setGroupOffset(BIOSIGRP_SYSTEM); 399 | uint32_t num_sensors; 400 | uint32_t bytesReturned; 401 | bbapi.ioctl_read(BIOSIOFFS_SYSTEM_COUNT_SENSORS, &num_sensors, sizeof(num_sensors), &bytesReturned); 402 | pr_info("\nSystem test results:\n====================\n"); 403 | while (num_sensors > 0) { 404 | SENSORINFO info; 405 | pr_info("%02d:", num_sensors); 406 | CHECK_CLASS("%s\n", num_sensors, info, SENSORINFO); 407 | --num_sensors; 408 | } 409 | } 410 | 411 | private: 412 | BiosApi bbapi; 413 | 414 | template 415 | void test_object(const std::string& nIndexOffset, const unsigned long offset, const T expectedValue, const std::string& msg, const bool doCompare) 416 | { 417 | uint32_t bytesReturned; 418 | char text[256]; 419 | T value {0}; 420 | fructose_loop_assert(nIndexOffset, -1 != bbapi.ioctl_read(offset, &value, sizeof(value), &bytesReturned)); 421 | if (doCompare) { 422 | fructose_loop_assert(nIndexOffset, value == expectedValue); 423 | } 424 | value.snprintf(text, sizeof(text)); 425 | pr_info(msg.c_str(), text); 426 | } 427 | 428 | template 429 | void test_range(const std::string& nIndexOffset, const unsigned long offset, const T lower, const T upper, const std::string& msg, const bool doCompare) 430 | { 431 | uint32_t bytesReturned; 432 | T value {0}; 433 | fructose_loop_assert(nIndexOffset, -1 != bbapi.ioctl_read(offset, &value, sizeof(value), &bytesReturned)); 434 | if (doCompare) { 435 | fructose_loop2_assert(nIndexOffset, lower, value, lower <= value); 436 | fructose_loop2_assert(nIndexOffset, upper, value, upper >= value); 437 | } 438 | pr_info(msg.c_str(), value); 439 | } 440 | }; 441 | 442 | void RunAtPos(size_t pos) 443 | { 444 | const int fd = open("/dev/cx_display", O_WRONLY); 445 | if (-1 == fd) { 446 | perror(NULL); 447 | return; 448 | } 449 | 450 | /** move cursor to our position */ 451 | for (size_t i = 0; i <= pos; ++i) { 452 | static const char tab = '\t'; 453 | write(fd, &tab, 1); 454 | } 455 | 456 | /** print some characters */ 457 | for (unsigned char c = '~'; c != ('/' + pos % 10); c--) { 458 | const unsigned char replace_char[2] = {'\b', c}; 459 | write(fd, replace_char, sizeof(replace_char)); 460 | /** sleep a little to let the other threads work, too */ 461 | std::this_thread::sleep_for(std::chrono::milliseconds(50)); 462 | } 463 | close(fd); 464 | } 465 | 466 | struct TestDisplay : fructose::test_base 467 | { 468 | void test_Simple(const std::string& test_name) 469 | { 470 | #if CONFIG_CXPWRSUPP_DISABLED 471 | pr_info("CX2100 text display test disabled\n"); 472 | return; 473 | #else 474 | (std::cout << "Simple display test running...").flush(); 475 | const int fd_1 = open("/dev/cx_display", O_WRONLY); 476 | const int fd_2 = open("/dev/cx_display", O_WRONLY); 477 | static const std::string line("\x11\f\bx\n\bxx\t\t\t\t\t\t\t\t\t\t\t\t\t\tx"); 478 | static const std::string line_2("\x11\r\b\r\b\r#\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t#"); 479 | static const std::string line_3("0123456789ABCDEFFEDCBA9876543210XXXXXX"); 480 | static const std::string light_off("\x13"); 481 | static const char *should = "|x x|\n|x x|\n"; 482 | static const char *should_2 = "|# x|\n|x #|\n"; 483 | static const char *should_3 = "|0123456789ABCDEF|\n|FEDCBA9876543210|\n"; 484 | static const char *bar_h = "===================\n"; 485 | fructose_assert_eq((ssize_t)line.size(), write(fd_1, line.c_str(), line.size())); 486 | pr_info("Display should look like this:\n%s%s%s\nhit ENTER to continue", bar_h, should, bar_h); 487 | WaitForUserInput(); 488 | 489 | fructose_assert_eq((ssize_t)light_off.size(), write(fd_1, light_off.c_str(), light_off.size())); 490 | pr_info("Display backlight should be switched off\nhit ENTER to continue"); 491 | WaitForUserInput(); 492 | 493 | fructose_assert_eq((ssize_t)line_2.size(), write(fd_1, line_2.c_str(), line_2.size())); 494 | pr_info("Display should look like this:\n%s%s%s\nhit ENTER to continue", bar_h, should_2, bar_h); 495 | WaitForUserInput(); 496 | 497 | fructose_assert_eq((ssize_t)line_3.size(), write(fd_2, line_3.c_str(), line_3.size())); 498 | pr_info("Display should look like this:\n%s%s%s\nhit ENTER to continue", bar_h, should_3, bar_h); 499 | WaitForUserInput(); 500 | 501 | fructose_assert_ne(-1, fd_1); 502 | fructose_assert_ne(-1, fd_2); 503 | fructose_assert_eq(0, close(fd_1)); 504 | fructose_assert_eq(0, close(fd_2)); 505 | if (!error()) { 506 | std::cout << " done." << std::endl; 507 | } 508 | #endif 509 | } 510 | 511 | void test_SMP(const std::string& test_name) 512 | { 513 | #if CONFIG_CXPWRSUPP_DISABLED 514 | pr_info("CX2100 text display test disabled\n"); 515 | return; 516 | #else 517 | (std::cout << "Multithreaded display test running...").flush(); 518 | std::vector threads(32); 519 | for (size_t i = 0; i < threads.size(); ++i) { 520 | threads[i] = std::thread(&RunAtPos, i); 521 | } 522 | 523 | for (auto& t: threads) { 524 | t.join(); 525 | } 526 | std::cout << " done." << std::endl; 527 | #endif 528 | } 529 | }; 530 | 531 | struct TestWatchdog : fructose::test_base 532 | { 533 | static const int SHORT_TIMEOUT = 2; 534 | void test_Simple(const std::string& test_name) 535 | { 536 | #if CONFIG_WATCHDOG_DISABLED 537 | pr_info("Simple watchdog test disabled\n"); 538 | return; 539 | #else 540 | (std::cout << "Simple watchdog test running...").flush(); 541 | const int timeout_sec = SHORT_TIMEOUT; 542 | const int fd = open("/dev/watchdog", O_WRONLY); 543 | fructose_assert_ne(-1, fd); 544 | fructose_assert_eq(0, ioctl(fd, WDIOC_SETTIMEOUT, &timeout_sec)); 545 | for (int i = 0; i < 2 * timeout_sec; ++i) { 546 | std::this_thread::sleep_for(std::chrono::seconds(1)); 547 | fructose_assert_eq(1, write(fd, "V", 1)); 548 | } 549 | fructose_assert_eq(0, close(fd)); 550 | std::this_thread::sleep_for(std::chrono::seconds(timeout_sec)); 551 | if (!error()) { 552 | std::cout << " done." << std::endl; 553 | } 554 | #endif 555 | } 556 | 557 | void test_IOCTL(const std::string& test_name) 558 | { 559 | pr_info("\nWatchdog test:\n==============\n"); 560 | #if CONFIG_WATCHDOG_DISABLED 561 | pr_info("IOCTL watchdog test disabled\n"); 562 | return; 563 | #else 564 | (std::cout << "IOCTL watchdog test running...").flush(); 565 | 566 | const int fd = open("/dev/watchdog", O_WRONLY); 567 | fructose_assert_ne(-1, fd); 568 | 569 | // durations over 255 sec. have to be mapped to minutes 570 | const int timeout_min = 256; 571 | int read = 0; 572 | fructose_assert_eq(0, ioctl(fd, WDIOC_SETTIMEOUT, &timeout_min)); 573 | fructose_assert_eq(0, ioctl(fd, WDIOC_GETTIMEOUT, &read)); 574 | fructose_assert_eq(4 * 60, read); 575 | 576 | // setting a too large timeout should fail, old value should remain 577 | const int timeout_overflow = BBAPI_WATCHDOG_MAX_TIMEOUT_SEC + 1; 578 | fructose_assert_eq(-1, ioctl(fd, WDIOC_SETTIMEOUT, &timeout_overflow)); 579 | fructose_assert_eq(0, ioctl(fd, WDIOC_GETTIMEOUT, &read)); 580 | fructose_assert_eq(4 * 60, read); 581 | 582 | // test maximum seconds 583 | const int timeout_sec = 255; 584 | fructose_assert_eq(0, ioctl(fd, WDIOC_SETTIMEOUT, &timeout_sec)); 585 | fructose_assert_eq(0, ioctl(fd, WDIOC_GETTIMEOUT, &read)); 586 | fructose_assert_eq(timeout_sec, read); 587 | 588 | // test maximum minutes 589 | const int timeout_max_minutes = BBAPI_WATCHDOG_MAX_TIMEOUT_SEC; 590 | fructose_assert_eq(0, ioctl(fd, WDIOC_SETTIMEOUT, &timeout_max_minutes)); 591 | fructose_assert_eq(0, ioctl(fd, WDIOC_GETTIMEOUT, &read)); 592 | fructose_assert_eq(timeout_max_minutes, read); 593 | 594 | const char identity[] = "bbapi_wdt\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; 595 | const uint32_t options = CONFIG_WATCHDOG_OPTIONS; 596 | struct watchdog_info ident; 597 | fructose_assert_eq(0, ioctl(fd, WDIOC_GETSUPPORT, &ident)); 598 | fructose_assert_eq(0, memcmp(identity, ident.identity, sizeof(ident.identity))); 599 | fructose_assert_eq(0U, ident.firmware_version); 600 | fructose_assert_eq(options, ident.options); 601 | 602 | // pretimeout is not supported by BBAPI watchdog 603 | const int pretimeout = 10; 604 | int pre; 605 | fructose_assert_eq(-1, ioctl(fd, WDIOC_SETPRETIMEOUT, &pretimeout)); 606 | fructose_assert_eq(0, ioctl(fd, WDIOC_GETPRETIMEOUT, &pre)); 607 | fructose_assert_eq(0, pre); 608 | 609 | // timeleft is not supported by BBAPI watchdog 610 | int timeleft; 611 | fructose_assert_eq(-1, ioctl(fd, WDIOC_GETTIMELEFT, &timeleft)); 612 | fructose_assert_eq(1, write(fd, "V", 1)); 613 | fructose_assert_eq(0, close(fd)); 614 | std::cout << "\nBBAPI watchdog feature 'to early trigger' not supported\n"; 615 | #endif 616 | } 617 | 618 | void test_KeepAlive(const std::string& test_name) 619 | { 620 | #if CONFIG_WATCHDOG_DISABLED 621 | pr_info("Watchdog keepalive test disabled\n"); 622 | return; 623 | #else 624 | (std::cout << "Watchdog keep alive ping test running...").flush(); 625 | const int timeout = SHORT_TIMEOUT; 626 | const int fd = open("/dev/watchdog", O_WRONLY); 627 | fructose_assert_ne(-1, fd); 628 | fructose_assert_eq(0, ioctl(fd, WDIOC_SETTIMEOUT, &timeout)); 629 | for (int i = 0; i < 2 * timeout; ++i) { 630 | int status = 0; 631 | std::this_thread::sleep_for(std::chrono::seconds(1)); 632 | fructose_assert_eq(0, ioctl(fd, WDIOC_KEEPALIVE)); 633 | fructose_assert_eq(0, ioctl(fd, WDIOC_GETSTATUS, &status)); 634 | fructose_assert(status & WDIOF_KEEPALIVEPING); 635 | fructose_assert_eq(0, ioctl(fd, WDIOC_GETSTATUS, &status)); 636 | fructose_assert_eq(0, status & WDIOF_KEEPALIVEPING); 637 | } 638 | fructose_assert_eq(1, write(fd, "V", 1)); 639 | fructose_assert_eq(0, close(fd)); 640 | if (!error()) { 641 | std::cout << " done." << std::endl; 642 | } 643 | #endif 644 | } 645 | }; 646 | 647 | struct TestOneTime : fructose::test_base 648 | { 649 | 650 | void test_SUPS(const std::string& test_name) 651 | { 652 | fructose_assert(!bbapi.ioctl_write(BIOSIOFFS_SUPS_CAPACITY_TEST, NULL, 0)); 653 | } 654 | 655 | void test_MagicClose(const std::string& test_name) 656 | { 657 | #if CONFIG_WATCHDOG_DISABLED 658 | pr_info("Watchdog magic close test disabled\n"); 659 | return; 660 | #else 661 | (std::cout << "MagicClose watchdog test running...").flush(); 662 | const int timeout = TestWatchdog::SHORT_TIMEOUT; 663 | const int fd = open("/dev/watchdog", O_WRONLY); 664 | fructose_assert_ne(-1, fd); 665 | fructose_assert_eq(0, ioctl(fd, WDIOC_SETTIMEOUT, &timeout)); 666 | for (int i = 0; i < 2 * timeout; ++i) { 667 | std::this_thread::sleep_for(std::chrono::seconds(1)); 668 | fructose_assert_eq(1, write(fd, "V", 1)); 669 | } 670 | fructose_assert_eq(1, write(fd, "\0", 1)); 671 | fructose_assert_eq(0, close(fd)); 672 | (std::cout << "WARNING watchdog will fire soon...").flush(); 673 | std::this_thread::sleep_for(std::chrono::seconds(timeout + 2)); 674 | fructose_fail("You shouldn't see, if the watchdog would work as expected!"); 675 | #endif 676 | } 677 | private: 678 | BiosApi bbapi; 679 | }; 680 | 681 | int main(int argc, char *argv[]) 682 | { 683 | int failures = 0; 684 | 685 | TestDisplay displayTest; 686 | displayTest.add_test("test_Simple", &TestDisplay::test_Simple); 687 | displayTest.add_test("test_SMP", &TestDisplay::test_SMP); 688 | failures += displayTest.run(argc, argv); 689 | 690 | TestBBAPI bbapiTest; 691 | bbapiTest.add_test("test_General", &TestBBAPI::test_General); 692 | bbapiTest.add_test("test_PwrCtrl", &TestBBAPI::test_PwrCtrl); 693 | bbapiTest.add_test("test_SUPS", &TestBBAPI::test_SUPS); 694 | bbapiTest.add_test("test_System", &TestBBAPI::test_System); 695 | bbapiTest.add_test("test_CXPowerSupply", &TestBBAPI::test_CXPowerSupply); 696 | bbapiTest.add_test("test_CXUPS", &TestBBAPI::test_CXUPS); 697 | bbapiTest.add_test("test_CXPowerSupply_display", &TestBBAPI::test_CXPowerSupply_display); 698 | bbapiTest.add_test("test_manual_LEDs", &TestBBAPI::test_manual_LEDs); 699 | failures += bbapiTest.run(argc, argv); 700 | 701 | TestWatchdog wdTest; 702 | wdTest.add_test("test_IOCTL", &TestWatchdog::test_IOCTL); 703 | wdTest.add_test("test_Simple", &TestWatchdog::test_Simple); 704 | wdTest.add_test("test_KeepAlive", &TestWatchdog::test_KeepAlive); 705 | failures += wdTest.run(argc, argv); 706 | 707 | TestOneTime oneTimeTest; 708 | oneTimeTest.add_test("test_SUPS", &TestOneTime::test_SUPS); 709 | oneTimeTest.add_test("test_MagicClose", &TestOneTime::test_MagicClose); 710 | // failures += oneTimeTest.run(argc, argv); 711 | return failures; 712 | } 713 | #endif /* #ifndef TEST_DEVICE */ 714 | -------------------------------------------------------------------------------- /unittest/load-module.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -l 2 | 3 | set -e 4 | 5 | insmod ./bbapi.ko 6 | insmod ./button/bbapi_button.ko 7 | insmod ./display/bbapi_display.ko 8 | insmod ./power/bbapi_power.ko 9 | insmod ./sups/bbapi_sups.ko 10 | insmod ./wdt/bbapi_wdt.ko 11 | 12 | dmesg | grep bbapi 13 | -------------------------------------------------------------------------------- /unittest/test-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -l 2 | 3 | set -e 4 | 5 | device_tag=$1 6 | 7 | ./unittest.bin${device_tag} 8 | 9 | echo "All tests completed" 10 | echo "We have none, yet ;-)" 11 | -------------------------------------------------------------------------------- /wdt/Makefile: -------------------------------------------------------------------------------- 1 | TARGET = bbapi_wdt 2 | EXTRA_DIR = /lib/modules/$(shell uname -r)/extra/ 3 | obj-m += $(TARGET).o 4 | $(TARGET)-objs := watchdog.o 5 | KBUILD_EXTRA_SYMBOLS := $(src)/../Module.symvers 6 | KDIR ?= /lib/modules/$(shell uname -r)/build 7 | 8 | all: 9 | make -C $(KDIR) M=$(PWD) modules 10 | 11 | install: 12 | - sudo rmmod $(TARGET) 13 | sudo mkdir -p $(EXTRA_DIR) 14 | sudo cp ./$(TARGET).ko $(EXTRA_DIR) 15 | sudo depmod -a 16 | sudo modprobe $(TARGET) 17 | 18 | clean: 19 | make -C $(KDIR) M=$(PWD) clean 20 | 21 | # indent the source files with the kernels Lindent script 22 | indent: watchdog.c 23 | ../Lindent $? 24 | -------------------------------------------------------------------------------- /wdt/watchdog.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | Watchdog driver using the Beckhoff BIOS API 4 | Author: Patrick Brünn 5 | Copyright (C) 2014 - 2018 Beckhoff Automation GmbH & Co. KG 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "../api.h" 14 | #include "../TcBaDevDef.h" 15 | 16 | #define DRV_VERSION "0.6" 17 | #define DRV_DESCRIPTION "Beckhoff BIOS API watchdog driver" 18 | 19 | #undef pr_fmt 20 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 21 | 22 | #define WDOG_KEEPALIVE 15 /* == BIT_NUMBER(WDIOF_KEEPALIVEPING) */ 23 | #define BBAPI_WATCHDOG_TIMEOUT_SEC 60 /* default timeout for watchdog */ 24 | 25 | static bool nowayout = WATCHDOG_NOWAYOUT; 26 | module_param(nowayout, bool, 0); 27 | MODULE_PARM_DESC(nowayout, 28 | "Watchdog cannot be stopped once started (default=" 29 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 30 | 31 | static int bbapi_wd_write(uint32_t offset, void *in, uint32_t size) 32 | { 33 | return -bbapi_write(BIOSIGRP_WATCHDOG, offset, in, size); 34 | } 35 | 36 | static int wd_start(struct watchdog_device *const wd) 37 | { 38 | static const uint32_t offset_enable = 39 | BIOSIOFFS_WATCHDOG_ACTIVATE_PWRCTRL; 40 | static const uint32_t offset_timeout = 41 | BIOSIOFFS_WATCHDOG_ENABLE_TRIGGER; 42 | static const uint32_t offset_timebase = BIOSIOFFS_WATCHDOG_CONFIG; 43 | uint8_t enable = 1; 44 | uint8_t timebase = wd->timeout > 255; 45 | uint8_t timeout = timebase ? wd->timeout / 60 : wd->timeout; 46 | 47 | int result = bbapi_wd_write(offset_enable, &enable, sizeof(enable)); 48 | switch (result) { 49 | case BIOSAPI_SRVNOTSUPP: 50 | pr_info("change watchdog mode not supported\n"); 51 | case BIOSAPIERR_NOERR: 52 | break; 53 | default: 54 | pr_warn("select PwrCtrl IO failed with: 0x%x\n", result); 55 | return result; 56 | } 57 | 58 | result = bbapi_wd_write(offset_timebase, &timebase, sizeof(timebase)); 59 | if (result) { 60 | pr_warn("set timebase failed with: 0x%x\n", result); 61 | return result; 62 | } 63 | 64 | result = bbapi_wd_write(offset_timeout, &timeout, sizeof(timeout)); 65 | if (result) { 66 | pr_warn("enable watchdog failed with: 0x%x\n", result); 67 | } 68 | return result; 69 | } 70 | 71 | static int wd_ping(struct watchdog_device *wd) 72 | { 73 | int result; 74 | 75 | set_bit(WDOG_KEEPALIVE, &wd->status); 76 | 77 | result = bbapi_wd_write(BIOSIOFFS_WATCHDOG_IORETRIGGER, NULL, 0); 78 | if (BIOSAPI_SRVNOTSUPP == result) { 79 | pr_info_once("this platform doesn't support io retrigger\n"); 80 | return wd_start(wd); 81 | } 82 | return result; 83 | } 84 | 85 | /** 86 | * Linux watchdog API uses seconds in a range of unsigned int, Beckhoff 87 | * BIOS API uses minutes or seconds as a timebase together with an 88 | * uint8_t timeout value. This function converts Linux watchdog seconds 89 | * into BBAPI timebase + timeout 90 | * As long as sec fits into a uint8_t we use seconds as a time base 91 | */ 92 | static int wd_set_timeout(struct watchdog_device *wd, unsigned int sec) 93 | { 94 | if (sec > 255) { 95 | wd->timeout = sec - (sec % 60); 96 | } else { 97 | wd->timeout = sec; 98 | } 99 | return wd_start(wd); 100 | } 101 | 102 | static unsigned int wd_status(struct watchdog_device *const wd) 103 | { 104 | const unsigned int value = wd->status; 105 | clear_bit(WDOG_KEEPALIVE, &wd->status); 106 | return value; 107 | } 108 | 109 | static int wd_stop(struct watchdog_device *wd) 110 | { 111 | const uint32_t offset = BIOSIOFFS_WATCHDOG_ENABLE_TRIGGER; 112 | uint8_t disable = 0; 113 | 114 | if (bbapi_wd_write(offset, &disable, sizeof(disable))) { 115 | pr_warn("%s(): disable watchdog failed\n", __FUNCTION__); 116 | return -1; 117 | } 118 | return 0; 119 | } 120 | 121 | static const struct watchdog_ops wd_ops = { 122 | .owner = THIS_MODULE, 123 | .start = wd_start, 124 | .stop = wd_stop, 125 | .ping = wd_ping, 126 | .status = wd_status, 127 | .set_timeout = wd_set_timeout, 128 | }; 129 | 130 | static const struct watchdog_info wd_info = { 131 | .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, 132 | .firmware_version = 0, 133 | .identity = KBUILD_MODNAME, 134 | }; 135 | 136 | static struct watchdog_device g_wd = { 137 | .info = &wd_info, 138 | .ops = &wd_ops, 139 | .timeout = BBAPI_WATCHDOG_TIMEOUT_SEC, 140 | .max_timeout = BBAPI_WATCHDOG_MAX_TIMEOUT_SEC, 141 | }; 142 | 143 | static int __init bbapi_watchdog_init_module(void) 144 | { 145 | pr_info("%s, %s (nowayout=%d)\n", DRV_DESCRIPTION, DRV_VERSION, 146 | nowayout); 147 | watchdog_set_nowayout(&g_wd, nowayout); 148 | return watchdog_register_device(&g_wd); 149 | } 150 | 151 | static void __exit bbapi_watchdog_exit(void) 152 | { 153 | watchdog_unregister_device(&g_wd); 154 | pr_info("Watchdog unregistered\n"); 155 | } 156 | 157 | module_init(bbapi_watchdog_init_module); 158 | module_exit(bbapi_watchdog_exit); 159 | 160 | MODULE_DESCRIPTION(DRV_DESCRIPTION); 161 | MODULE_AUTHOR("Patrick Bruenn "); 162 | MODULE_LICENSE("GPL and additional rights"); 163 | MODULE_VERSION(DRV_VERSION); 164 | --------------------------------------------------------------------------------