├── .gitignore ├── License.txt ├── README.md ├── build ├── bulletin.xml ├── component │ └── VMW-esx-7.0.1-thgpio-1.0-0.00001.zip ├── sverify │ └── thgpio.sva ├── thgpio ├── thgpio-nostrip ├── thgpio.map └── vib │ ├── thgpio-0.0.1-1OEM.701.1.0.40650718.aarch64.vib │ └── thgpio-0.1.0-1OEM.701.1.0.40650718.aarch64.vib ├── gpio.h ├── gpio_charDev.c ├── gpio_charDev.h ├── gpio_drv.c ├── gpio_drv.h ├── gpio_os.c ├── gpio_types.h └── pyUtil ├── gpioLib └── __init__.py ├── gpio_fanShim.py └── gpio_util.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.sh 2 | *.sc 3 | gpio_acpi_device.py 4 | bump* 5 | #build 6 | .build 7 | .vscode 8 | Makefile 9 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | Native ESXi GPIO Driver 2 | Copyright (c) 2020 Tom Hebel 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Note: all the source code contained in this repository is for educational use only, and is provided with no support, endorsement, warranty, guarantee or implied fitness for a particular purpose. It does not constitute sample driver code. Do not reach out to VMware to support this code. Do not copy and reuse in any commercial or product setting. 2 | 3 | # thgpio 4 | Native ESXi driver for RPi 4's GPIO interface. 5 | 6 | Allows controlling the pins of the Raspberry Pi 4's GPIO interface. 7 | 8 | Still very much work-in-progress! 9 | 10 | Branch main contains: 11 | - the base GPIO hardware driver 12 | - a Python library for interacting with the GPIO MMIO memory directly 13 | - a Python utility for interacting with the GPIO interface 14 | - a Python utility for interacting with the Pimoroni FanShim 15 | 16 | ### Videos 17 | 18 | These demos use the Pimoroni FanShim as an example. Any GPIO accessory should work analogously. 19 | 20 | Branch main: https://youtu.be/Mw8L1XAi8qA?t=170 21 | 22 | Branch fanShimButton: https://youtu.be/YjSxgKr7gZg 23 | 24 | Branch charDev: https://youtu.be/Ri1py3AzIsU?t=107 25 | 26 | ### Installation 27 | 28 | To install the driver, download the VIB file in the build directory and copy it to your RPi. 29 | 30 | Install it as described here: https://kb.vmware.com/s/article/2008939 31 | 32 | Note: you will need to reboot the RPi after installing the VIB. 33 | 34 | I would recommend downloading the Python library in ./pyUtil/gpioLib/ as well so you don't have to worry about all the bit twiddling. 35 | 36 | ### Post-Installation Setup 37 | 38 | You may need to manually change the `GPIO_DEVICE_PATH` variable in `./pyUtil/gpioLib/__init__.py` to reflect the correct name for the character device. This is due to a limitation in the VMKernel character device API. 39 | 40 | 1. Open the `__init__.py` file ( https://github.com/thebel1/thgpio/blob/main/pyUtil/gpioLib/__init__.py ) 41 | 2. Locate the line `GPIO_DEVICE_PATH = '/dev/vmgfx32'` 42 | 3. Run the command `ls /dev/` in the ESXi shell and locate the vmgfx* device(s) 43 | 4. Note the device number corresponding to the GPIO device 44 | 4.1. If you installed the GPIO driver first, the file path should be `/dev/vmgfx32` 45 | 4.2. If you installed the GPIO driver second, the file should be `/dev/vmgfx33` 46 | 4.3. It just keeps counting up from there as far as I can tell 47 | 5. Go back to the `__init__.py` file and set `GPIO_DEVICE_PATH` to the correct device path 48 | 6. Save the file and you're set 49 | -------------------------------------------------------------------------------- /build/bulletin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | thgpio_1.0-0.00001 6 | 7 | 8 | 9 | Tom Hebel 10 | 11 | 12 | 13 | thgpio: Native GPIO Driver 14 | 15 | 16 | general 17 | 18 | 19 | enhancement 20 | 21 | 22 | important 23 | 24 | 25 | extension 26 | 27 | 28 | 29 | Native GPIO Driver module for vmkernel 30 | 31 | 32 | 33 | http://kb.example.com/kb/example.html 34 | 35 | 36 | 37 | thebel@vmware.com 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 2020-10-30T10:42:17+00:00 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | THX_bootbank_thgpio_0.1.0-1OEM.701.1.0.40650718 -------------------------------------------------------------------------------- /build/component/VMW-esx-7.0.1-thgpio-1.0-0.00001.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebel1/thgpio/81084b3f965f3eefa0bbed7e676e53d37ab0bef6/build/component/VMW-esx-7.0.1-thgpio-1.0-0.00001.zip -------------------------------------------------------------------------------- /build/sverify/thgpio.sva: -------------------------------------------------------------------------------- 1 | vmware-esx-nativeddk-devtools-7.0.0-1.0.15843807.x86_64 2 | vmware-esx-nativeddkarm64-devtools-7.0.1-1.0.40650718.x86_64 3 | vmware-esx-nativeddk-devtools-7.0.0-1.0.16966451.x86_64 4 | vmware-esx-nativeddkarm64-toolchain-2.0.0-1.x86_64 5 | vmware-esx-nativeddkarm64-devtools-sockets-7.0.1-1.0.40650718.x86_64 6 | aarch64-vmk-linux-gnu-gcc (GCC) 6.4.0 7 | Copyright (C) 2017 Free Software Foundation, Inc. 8 | This is free software; see the source for copying conditions. There is NO 9 | warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 10 | 11 | aarch64-vmk-linux-gnu-g++ (GCC) 6.4.0 12 | Copyright (C) 2017 Free Software Foundation, Inc. 13 | This is free software; see the source for copying conditions. There is NO 14 | warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 15 | 16 | LLD 8.0.1 (compatible with GNU linkers) 17 | /opt/vmware/toolchain/cayman_esx_toolchain_gcc6-978272870a6446e021754e84f5d75e0c6c53a9b7/usr/bin/aarch64-vmk-linux-gnu-gcc -march="armv8-a" --sysroot="/opt/vmware/toolchain/cayman_esx_glibc_2_17-8ef7e61952433909958dd15ee027ca4e03896da7/sysroot-aarch64" -fwrapv -pipe -fno-strict-aliasing -Wno-unused-but-set-variable -fno-working-directory -g -O2 -ffreestanding -nostdinc -fno-common -funwind-tables -fasynchronous-unwind-tables -Wall -Werror -Wno-error="inline" -Wformat-nonliteral -Wstrict-prototypes -fno-strict-aliasing -freg-struct-return -falign-jumps="1" -falign-functions="4" -falign-loops="1" -march="armv8-a" -mgeneral-regs-only -DVMK_ARM_BRINGUP -finline-limit="2000" -fomit-frame-pointer -Wlogical-op -Wno-pointer-sign -Wno-strict-prototypes -Wno-enum-compare -Wno-declaration-after-statement -std="gnu90" -DNO_FLOATING_POINT -DUSE_32_BIT -DVMKERNEL -DVMKERNEL_MODULE -DVMK_DEVKIT_HAS_API_VMKAPI_BASE -DVMK_DEVKIT_HAS_API_VMKAPI_ENS -DVMK_DEVKIT_HAS_API_VMKAPI_GPU -DVMK_DEVKIT_HAS_API_VMKAPI_ISCSI -DVMK_DEVKIT_HAS_API_VMKAPI_NET -DVMK_DEVKIT_HAS_API_VMKAPI_NPIV -DVMK_DEVKIT_HAS_API_VMKAPI_NVME_DRV -DVMK_DEVKIT_HAS_API_VMKAPI_RDMA -DVMK_DEVKIT_HAS_API_VMKAPI_SCSI -DVMK_DEVKIT_HAS_API_VMKAPI_SOCKETS -DVMK_DEVKIT_HAS_NATIVE_DDK -DVMK_DEVKIT_USES_BINARY_COMPATIBLE_APIS -DVMK_DEVKIT_USES_PUBLIC_APIS -DVMX86_RELEASE -DVMX86_SERVER -D_GLIBCXX_USE_CXX11_ABI="0" -D__KERNEL__ -D__KMDK__ -D__MODULE_LICENSE__="ThirdParty:MIT" -D__MODULE_VERSION__="0.1.0-1OEM.701.1.0.40650718" -Ipartners/samples/public/thgpio -Ipartners/samples/public/thgpio/.build/build/version -Ipartners/samples/public/thgpio/.build/build/HEADERS/vmkapi-current-all-public-bincomp/generic/release -isystem /opt/vmware/toolchain/cayman_esx_toolchain_gcc6-978272870a6446e021754e84f5d75e0c6c53a9b7/usr/lib/gcc/aarch64-vmk-linux-gnu/6.4.0/include -E bora/modules/vmkapi/module_info/module_info.c -U__TIME__ -U__DATE__ 18 | WD: /opt/vmware/nativeddkarm64-7.0.1-40650718/src 19 | BD: /opt/vmware/nativeddkarm64-7.0.1-40650718/src/partners/samples/public/thgpio/.build 20 | ff5edde8b8fc17827f6a83cb952b9832 partners/samples/public/thgpio/.build/build/vmkdriver-thgpio/release/vmkernel64-arm64/SUBDIRS/bora/modules/vmkapi/module_info/module_info.i 21 | /opt/vmware/toolchain/cayman_esx_toolchain_gcc6-978272870a6446e021754e84f5d75e0c6c53a9b7/usr/bin/aarch64-vmk-linux-gnu-gcc -march="armv8-a" --sysroot="/opt/vmware/toolchain/cayman_esx_glibc_2_17-8ef7e61952433909958dd15ee027ca4e03896da7/sysroot-aarch64" -fwrapv -pipe -fno-strict-aliasing -Wno-unused-but-set-variable -fno-working-directory -g -O2 -ffreestanding -nostdinc -fno-common -funwind-tables -fasynchronous-unwind-tables -Wall -Werror -Wno-error="inline" -Wformat-nonliteral -Wstrict-prototypes -fno-strict-aliasing -freg-struct-return -falign-jumps="1" -falign-functions="4" -falign-loops="1" -march="armv8-a" -mgeneral-regs-only -DVMK_ARM_BRINGUP -finline-limit="2000" -fomit-frame-pointer -Wlogical-op -Wno-pointer-sign -Wno-strict-prototypes -Wno-enum-compare -Wno-declaration-after-statement -std="gnu90" -DNO_FLOATING_POINT -DUSE_32_BIT -DVMKERNEL -DVMKERNEL_MODULE -DVMK_DEVKIT_HAS_API_VMKAPI_BASE -DVMK_DEVKIT_HAS_API_VMKAPI_ENS -DVMK_DEVKIT_HAS_API_VMKAPI_GPU -DVMK_DEVKIT_HAS_API_VMKAPI_ISCSI -DVMK_DEVKIT_HAS_API_VMKAPI_NET -DVMK_DEVKIT_HAS_API_VMKAPI_NPIV -DVMK_DEVKIT_HAS_API_VMKAPI_NVME_DRV -DVMK_DEVKIT_HAS_API_VMKAPI_RDMA -DVMK_DEVKIT_HAS_API_VMKAPI_SCSI -DVMK_DEVKIT_HAS_API_VMKAPI_SOCKETS -DVMK_DEVKIT_HAS_NATIVE_DDK -DVMK_DEVKIT_USES_BINARY_COMPATIBLE_APIS -DVMK_DEVKIT_USES_PUBLIC_APIS -DVMX86_RELEASE -DVMX86_SERVER -D_GLIBCXX_USE_CXX11_ABI="0" -D__KERNEL__ -D__KMDK__ -Ipartners/samples/public/thgpio -Ipartners/samples/public/thgpio/.build/build/version -Ipartners/samples/public/thgpio/.build/build/HEADERS/vmkapi-current-all-public-bincomp/generic/release -isystem /opt/vmware/toolchain/cayman_esx_toolchain_gcc6-978272870a6446e021754e84f5d75e0c6c53a9b7/usr/lib/gcc/aarch64-vmk-linux-gnu/6.4.0/include -E partners/samples/public/thgpio/gpio_os.c -U__TIME__ -U__DATE__ 22 | WD: /opt/vmware/nativeddkarm64-7.0.1-40650718/src 23 | BD: /opt/vmware/nativeddkarm64-7.0.1-40650718/src/partners/samples/public/thgpio/.build 24 | edc13860af4638ff15e6eefe7a05860e partners/samples/public/thgpio/.build/build/vmkdriver-thgpio/release/vmkernel64-arm64/SUBDIRS/opt/vmware/nativeddkarm64-7.0.1-40650718/src/partners/samples/public/thgpio/gpio_os.i 25 | /opt/vmware/toolchain/cayman_esx_toolchain_gcc6-978272870a6446e021754e84f5d75e0c6c53a9b7/usr/bin/aarch64-vmk-linux-gnu-gcc -march="armv8-a" --sysroot="/opt/vmware/toolchain/cayman_esx_glibc_2_17-8ef7e61952433909958dd15ee027ca4e03896da7/sysroot-aarch64" -fwrapv -pipe -fno-strict-aliasing -Wno-unused-but-set-variable -fno-working-directory -g -O2 -ffreestanding -nostdinc -fno-common -funwind-tables -fasynchronous-unwind-tables -Wall -Werror -Wno-error="inline" -Wformat-nonliteral -Wstrict-prototypes -fno-strict-aliasing -freg-struct-return -falign-jumps="1" -falign-functions="4" -falign-loops="1" -march="armv8-a" -mgeneral-regs-only -DVMK_ARM_BRINGUP -finline-limit="2000" -fomit-frame-pointer -Wlogical-op -Wno-pointer-sign -Wno-strict-prototypes -Wno-enum-compare -Wno-declaration-after-statement -std="gnu90" -DNO_FLOATING_POINT -DUSE_32_BIT -DVMKERNEL -DVMKERNEL_MODULE -DVMK_DEVKIT_HAS_API_VMKAPI_BASE -DVMK_DEVKIT_HAS_API_VMKAPI_ENS -DVMK_DEVKIT_HAS_API_VMKAPI_GPU -DVMK_DEVKIT_HAS_API_VMKAPI_ISCSI -DVMK_DEVKIT_HAS_API_VMKAPI_NET -DVMK_DEVKIT_HAS_API_VMKAPI_NPIV -DVMK_DEVKIT_HAS_API_VMKAPI_NVME_DRV -DVMK_DEVKIT_HAS_API_VMKAPI_RDMA -DVMK_DEVKIT_HAS_API_VMKAPI_SCSI -DVMK_DEVKIT_HAS_API_VMKAPI_SOCKETS -DVMK_DEVKIT_HAS_NATIVE_DDK -DVMK_DEVKIT_USES_BINARY_COMPATIBLE_APIS -DVMK_DEVKIT_USES_PUBLIC_APIS -DVMX86_RELEASE -DVMX86_SERVER -D_GLIBCXX_USE_CXX11_ABI="0" -D__KERNEL__ -D__KMDK__ -Ipartners/samples/public/thgpio -Ipartners/samples/public/thgpio/.build/build/version -Ipartners/samples/public/thgpio/.build/build/HEADERS/vmkapi-current-all-public-bincomp/generic/release -isystem /opt/vmware/toolchain/cayman_esx_toolchain_gcc6-978272870a6446e021754e84f5d75e0c6c53a9b7/usr/lib/gcc/aarch64-vmk-linux-gnu/6.4.0/include -E partners/samples/public/thgpio/gpio_drv.c -U__TIME__ -U__DATE__ 26 | WD: /opt/vmware/nativeddkarm64-7.0.1-40650718/src 27 | BD: /opt/vmware/nativeddkarm64-7.0.1-40650718/src/partners/samples/public/thgpio/.build 28 | 2add78a669e05aa7bf19599bebf7f652 partners/samples/public/thgpio/.build/build/vmkdriver-thgpio/release/vmkernel64-arm64/SUBDIRS/opt/vmware/nativeddkarm64-7.0.1-40650718/src/partners/samples/public/thgpio/gpio_drv.i 29 | /opt/vmware/toolchain/cayman_esx_toolchain_gcc6-978272870a6446e021754e84f5d75e0c6c53a9b7/usr/bin/aarch64-vmk-linux-gnu-gcc -march="armv8-a" --sysroot="/opt/vmware/toolchain/cayman_esx_glibc_2_17-8ef7e61952433909958dd15ee027ca4e03896da7/sysroot-aarch64" -fwrapv -pipe -fno-strict-aliasing -Wno-unused-but-set-variable -fno-working-directory -g -O2 -ffreestanding -nostdinc -fno-common -funwind-tables -fasynchronous-unwind-tables -Wall -Werror -Wno-error="inline" -Wformat-nonliteral -Wstrict-prototypes -fno-strict-aliasing -freg-struct-return -falign-jumps="1" -falign-functions="4" -falign-loops="1" -march="armv8-a" -mgeneral-regs-only -DVMK_ARM_BRINGUP -finline-limit="2000" -fomit-frame-pointer -Wlogical-op -Wno-pointer-sign -Wno-strict-prototypes -Wno-enum-compare -Wno-declaration-after-statement -std="gnu90" -DNO_FLOATING_POINT -DUSE_32_BIT -DVMKERNEL -DVMKERNEL_MODULE -DVMK_DEVKIT_HAS_API_VMKAPI_BASE -DVMK_DEVKIT_HAS_API_VMKAPI_ENS -DVMK_DEVKIT_HAS_API_VMKAPI_GPU -DVMK_DEVKIT_HAS_API_VMKAPI_ISCSI -DVMK_DEVKIT_HAS_API_VMKAPI_NET -DVMK_DEVKIT_HAS_API_VMKAPI_NPIV -DVMK_DEVKIT_HAS_API_VMKAPI_NVME_DRV -DVMK_DEVKIT_HAS_API_VMKAPI_RDMA -DVMK_DEVKIT_HAS_API_VMKAPI_SCSI -DVMK_DEVKIT_HAS_API_VMKAPI_SOCKETS -DVMK_DEVKIT_HAS_NATIVE_DDK -DVMK_DEVKIT_USES_BINARY_COMPATIBLE_APIS -DVMK_DEVKIT_USES_PUBLIC_APIS -DVMX86_RELEASE -DVMX86_SERVER -D_GLIBCXX_USE_CXX11_ABI="0" -D__KERNEL__ -D__KMDK__ -Ipartners/samples/public/thgpio -Ipartners/samples/public/thgpio/.build/build/version -Ipartners/samples/public/thgpio/.build/build/HEADERS/vmkapi-current-all-public-bincomp/generic/release -isystem /opt/vmware/toolchain/cayman_esx_toolchain_gcc6-978272870a6446e021754e84f5d75e0c6c53a9b7/usr/lib/gcc/aarch64-vmk-linux-gnu/6.4.0/include -E partners/samples/public/thgpio/gpio_charDev.c -U__TIME__ -U__DATE__ 30 | WD: /opt/vmware/nativeddkarm64-7.0.1-40650718/src 31 | BD: /opt/vmware/nativeddkarm64-7.0.1-40650718/src/partners/samples/public/thgpio/.build 32 | 2d618e20acbff6a763d513f934b42628 partners/samples/public/thgpio/.build/build/vmkdriver-thgpio/release/vmkernel64-arm64/SUBDIRS/opt/vmware/nativeddkarm64-7.0.1-40650718/src/partners/samples/public/thgpio/gpio_charDev.i 33 | /opt/vmware/toolchain/cayman_llvm-e51314a6e421afc0fc8856057af8b195bb9805f3/lin64/usr/bin+glibc212/ld.lld -r -o partners/samples/public/thgpio/.build/build/vmkdriver-thgpio/release/vmkernel64-arm64/thgpio.sv --whole-archive partners/samples/public/thgpio/.build/build/vmkdriver-thgpio/release/vmkernel64-arm64/thgpio 34 | WD: /opt/vmware/nativeddkarm64-7.0.1-40650718/src 35 | BD: /opt/vmware/nativeddkarm64-7.0.1-40650718/src/partners/samples/public/thgpio/.build 36 | 1e4f08ebc9b7a2ec4bf0755e5c5f913b partners/samples/public/thgpio/.build/build/vmkdriver-thgpio/release/vmkernel64-arm64/thgpio 37 | 564261baf4b168246307fa34142d2e4f partners/samples/public/thgpio/.build/build/vmkdriver-thgpio/release/vmkernel64-arm64/signed/thgpio 38 | -------------------------------------------------------------------------------- /build/thgpio: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebel1/thgpio/81084b3f965f3eefa0bbed7e676e53d37ab0bef6/build/thgpio -------------------------------------------------------------------------------- /build/thgpio-nostrip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebel1/thgpio/81084b3f965f3eefa0bbed7e676e53d37ab0bef6/build/thgpio-nostrip -------------------------------------------------------------------------------- /build/thgpio.map: -------------------------------------------------------------------------------- 1 | regtype=native,bus=acpi,id=BCMGPIO,driver=thgpio 2 | -------------------------------------------------------------------------------- /build/vib/thgpio-0.0.1-1OEM.701.1.0.40650718.aarch64.vib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebel1/thgpio/81084b3f965f3eefa0bbed7e676e53d37ab0bef6/build/vib/thgpio-0.0.1-1OEM.701.1.0.40650718.aarch64.vib -------------------------------------------------------------------------------- /build/vib/thgpio-0.1.0-1OEM.701.1.0.40650718.aarch64.vib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebel1/thgpio/81084b3f965f3eefa0bbed7e676e53d37ab0bef6/build/vib/thgpio-0.1.0-1OEM.701.1.0.40650718.aarch64.vib -------------------------------------------------------------------------------- /gpio.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | * Native ESXi driver for the RPi's GPIO interface. 3 | * 4 | * Tom Hebel, 2020 5 | \******************************************************************************/ 6 | 7 | /* 8 | * gpio.h -- 9 | */ 10 | 11 | #ifndef GPIO_H 12 | #define GPIO_H 13 | 14 | #define VMKAPI_ONLY 15 | #include "vmkapi.h" 16 | 17 | /***********************************************************************/ 18 | 19 | #define GPIO_DEBUG 20 | 21 | #define GPIO_DRIVER_NAME "thgpio" 22 | 23 | // Probably overkill 24 | #define GPIO_HEAP_INITIAL_SIZE (1024 * 1024) 25 | #define GPIO_HEAP_MAX_SIZE (2 * 1024 * 1024) 26 | 27 | #define GPIO_MIN(a, b) (a > b ? b : a) 28 | #define GPIO_MAX(a, b) (a > b ? a : b) 29 | 30 | #define GPIO_INT_MAX ((vmk_uint32)~0) 31 | 32 | #define GPIO_LOG_ERR 1 33 | #define GPIO_LOG_WARN 2 34 | #define GPIO_LOG_NOTE 3 35 | #define GPIO_LOG_INIT 4 36 | #define GPIO_LOG_DISC 5 37 | #define GPIO_LOG_INFO 6 38 | #define GPIO_LOG_FUNC 7 39 | #define GPIO_LOG_TRACEIO 8 40 | 41 | /***********************************************************************/ 42 | 43 | #endif /* GPIO_H */ -------------------------------------------------------------------------------- /gpio_charDev.c: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | * Native ESXi driver for the RPi's GPIO interface. 3 | * 4 | * Tom Hebel, 2020 5 | \******************************************************************************/ 6 | 7 | /* 8 | * gpio_charDev.c -- 9 | */ 10 | 11 | #include "gpio_charDev.h" 12 | 13 | /***********************************************************************/ 14 | 15 | static vmk_CharDevOps gpio_charDevFileOps = { 16 | .open = gpio_charDevOpen, 17 | .close = gpio_charDevClose, 18 | .ioctl = gpio_charDevIoctl, 19 | .poll = gpio_charDevPoll, 20 | .read = gpio_charDevRead, 21 | .write = gpio_charDevWrite, 22 | }; 23 | 24 | static vmk_CharDevRegOps gpio_CharDevOps = { 25 | .associate = gpio_charDevAssoc, 26 | .disassociate = gpio_charDevDisassoc, 27 | .fileOps = &gpio_charDevFileOps, 28 | }; 29 | 30 | /* 31 | * Dev ops for the new vmk_Device associated with the char dev 32 | */ 33 | static vmk_DeviceOps gpio_CharVmkDevOps = { 34 | .removeDevice = gpio_charVmkDevRemove, 35 | }; 36 | 37 | /* 38 | * Call backs that glue the char dev to the GPIO driver 39 | */ 40 | static gpio_CharDevCallbacks_t *gpio_CharDevCBs; 41 | 42 | /***********************************************************************/ 43 | 44 | /* 45 | * Char dev has its own pointer to gpio_os's driver struct, so we don't 46 | * have a global shared by several files. 47 | */ 48 | static gpio_Driver_t *gpio_Driver; 49 | 50 | static vmk_TimerQueue gpio_charDevTimerQueue; 51 | 52 | /* 53 | *********************************************************************** 54 | * gpio_charDevRegister -- 55 | * 56 | * Register a character device with the vmkernel chardev layer. 57 | * 58 | * Results: 59 | * VMK_OK on success, error code otherwise 60 | * 61 | * Side Effects: 62 | * None. 63 | *********************************************************************** 64 | */ 65 | VMK_ReturnStatus 66 | gpio_charDevRegister(gpio_Driver_t *gpioDriver, 67 | vmk_BusType logicalBusType, 68 | vmk_Device parentDevice, 69 | gpio_CharDev_t *charDev, 70 | vmk_uint32 logicalPort, 71 | gpio_CharDevCallbacks_t *devCBs) 72 | { 73 | VMK_ReturnStatus status = VMK_OK; 74 | vmk_Device newDevice; 75 | vmk_DeviceID devID; 76 | vmk_AddrCookie registeringDriverData; 77 | vmk_AddrCookie registrationData; 78 | vmk_DeviceProps deviceProps; 79 | vmk_AddrCookie charDevPriv; 80 | 81 | /* Make available globally */ 82 | gpio_Driver = gpioDriver; 83 | 84 | devID.busType = logicalBusType; 85 | status = vmk_LogicalCreateBusAddress(gpioDriver->driverHandle, 86 | parentDevice, logicalPort, 87 | &devID.busAddress, 88 | &devID.busAddressLen); 89 | if (status != VMK_OK) { 90 | goto logical_bus_failed; 91 | } 92 | 93 | /* 94 | * As per vmkapi_char.h it can only be graphics or a test device 95 | */ 96 | devID.busIdentifier = VMK_CHARDEV_IDENTIFIER_GRAPHICS; 97 | devID.busIdentifierLen = vmk_Strnlen(devID.busIdentifier, VMK_MISC_NAME_MAX); 98 | 99 | charDevPriv.ptr = charDev; 100 | 101 | /* 102 | * Set up char dev registration 103 | */ 104 | 105 | charDev->regData.moduleID = gpioDriver->moduleID; 106 | charDev->regData.deviceOps = &gpio_CharDevOps; 107 | charDev->regData.devicePrivate = charDevPriv; 108 | 109 | registrationData.ptr = &charDev->regData; 110 | registeringDriverData.ptr = charDev; 111 | 112 | deviceProps.registeringDriver = gpioDriver->driverHandle; 113 | deviceProps.deviceID = &devID; 114 | deviceProps.deviceOps = &gpio_CharVmkDevOps; 115 | deviceProps.registeringDriverData = registeringDriverData; 116 | deviceProps.registrationData = registrationData; 117 | 118 | status = vmk_DeviceRegister(&deviceProps, parentDevice, &newDevice); 119 | if (status != VMK_OK) { 120 | vmk_Warning(gpio_Driver->logger, 121 | "failed to register device: %s", 122 | vmk_StatusToString(status)); 123 | goto register_device_failed; 124 | } 125 | 126 | status = vmk_LogicalFreeBusAddress(gpioDriver->driverHandle, 127 | devID.busAddress); 128 | if (status != VMK_OK) { 129 | vmk_Warning(gpio_Driver->logger, 130 | "failed to free logical bus: %s", 131 | vmk_StatusToString(status)); 132 | goto free_bus_failed; 133 | } 134 | 135 | /* 136 | * Set CBs 137 | */ 138 | gpio_CharDevCBs = devCBs; 139 | 140 | return VMK_OK; 141 | 142 | free_bus_failed: 143 | vmk_LogicalFreeBusAddress(gpioDriver->driverHandle, 144 | devID.busAddress); 145 | register_device_failed: 146 | logical_bus_failed: 147 | return status; 148 | } 149 | 150 | /* 151 | *********************************************************************** 152 | * gpio_charVmkDevRemove -- 153 | * 154 | * Remove character device. 155 | * 156 | * Results: 157 | * VMK_OK on success, error code otherwise 158 | * 159 | * Side Effects: 160 | * None. 161 | *********************************************************************** 162 | */ 163 | VMK_ReturnStatus 164 | gpio_charVmkDevRemove(vmk_Device logicalDev) 165 | { 166 | VMK_ReturnStatus status = VMK_OK; 167 | 168 | status = vmk_DeviceUnregister(logicalDev); 169 | if (status != VMK_OK) 170 | { 171 | vmk_Warning(gpio_Driver->logger, 172 | "failed to unregister device: %s", 173 | vmk_StatusToString(status)); 174 | } 175 | 176 | return status; 177 | } 178 | 179 | /* 180 | *********************************************************************** 181 | * gpio_charDevAssoc -- 182 | * 183 | * Associate a char device with a device. 184 | * 185 | * Results: 186 | * VMK_OK on success, error code otherwise 187 | * 188 | * Side Effects: 189 | * None. 190 | *********************************************************************** 191 | */ 192 | VMK_ReturnStatus 193 | gpio_charDevAssoc(vmk_AddrCookie charDevPriv, 194 | vmk_CharDevHandle charDevHandle) 195 | { 196 | VMK_ReturnStatus status = VMK_OK; 197 | vmk_Name charDevAlias; 198 | 199 | status = vmk_CharDeviceGetAlias(charDevHandle, &charDevAlias); 200 | if (status != VMK_OK) { 201 | vmk_Warning(gpio_Driver->logger, 202 | "failed to obtain logical device alias: %s", 203 | vmk_StatusToString(status)); 204 | goto get_alias_failed; 205 | } 206 | 207 | #ifdef GPIO_DEBUG 208 | { 209 | vmk_Log(gpio_Driver->logger, 210 | "obtained logical device alias %s", 211 | charDevAlias.string); 212 | } 213 | #endif /* GPIO_DEBUG */ 214 | 215 | return VMK_OK; 216 | 217 | get_alias_failed: 218 | return status; 219 | } 220 | 221 | /* 222 | *********************************************************************** 223 | * gpio_charDevDisassoc -- 224 | * 225 | * Disassociate a char device with a device. 226 | * 227 | * Results: 228 | * VMK_OK on success, error code otherwise 229 | * 230 | * Side Effects: 231 | * None. 232 | *********************************************************************** 233 | */ 234 | VMK_ReturnStatus 235 | gpio_charDevDisassoc(vmk_AddrCookie charDevPriv) 236 | { 237 | VMK_ReturnStatus status = VMK_OK; 238 | 239 | return status; 240 | } 241 | 242 | /* 243 | *********************************************************************** 244 | * gpio_charDevOpen -- 245 | * 246 | * Opens a file. 247 | * 248 | * Results: 249 | * VMK_OK on success, error code otherwise 250 | * 251 | * Side Effects: 252 | * None. 253 | *********************************************************************** 254 | */ 255 | VMK_ReturnStatus 256 | gpio_charDevOpen(vmk_CharDevFdAttr *attr) 257 | { 258 | VMK_ReturnStatus status = VMK_OK; 259 | gpio_CharFileData_t *fileData; 260 | vmk_SpinlockCreateProps lockProps; 261 | 262 | /* 263 | * Init private file data 264 | */ 265 | 266 | fileData = vmk_HeapAlloc(gpio_Driver->heapID, sizeof(*fileData)); 267 | if (fileData == NULL) { 268 | status = VMK_NO_MEMORY; 269 | vmk_Warning(gpio_Driver->logger, 270 | "failed to create file private data: %s", 271 | vmk_StatusToString(status)); 272 | goto file_priv_alloc_failed; 273 | } 274 | vmk_Memset(fileData, 0, sizeof(*fileData)); 275 | 276 | /* 277 | * Init lock 278 | */ 279 | 280 | lockProps.moduleID = gpio_Driver->moduleID; 281 | lockProps.heapID = gpio_Driver->heapID; 282 | status = vmk_NameInitialize(&lockProps.name, GPIO_DRIVER_NAME); 283 | if (status != VMK_OK) { 284 | vmk_Warning(gpio_Driver->logger, 285 | "failed to init lock name: %s", 286 | vmk_StatusToString(status)); 287 | goto lock_init_failed; 288 | } 289 | 290 | lockProps.type = VMK_SPINLOCK; 291 | lockProps.domain = VMK_LOCKDOMAIN_INVALID; 292 | lockProps.rank = VMK_SPINLOCK_UNRANKED; 293 | status = vmk_SpinlockCreate(&lockProps, &fileData->lock); 294 | if (status != VMK_OK) { 295 | vmk_Warning(gpio_Driver->logger, 296 | "failed to create spinlock: %s", 297 | vmk_StatusToString(status)); 298 | goto lock_init_failed; 299 | } 300 | 301 | /* 302 | * We don't use this buffer, so set it to NULL 303 | */ 304 | fileData->data = NULL; 305 | 306 | /* 307 | * Prep file for I/O 308 | */ 309 | fileData->timerPending = VMK_FALSE; 310 | fileData->deathPending = VMK_FALSE; 311 | fileData->pollMask = VMKAPI_POLL_WRITE; 312 | fileData->timeoutUS = GPIO_CHARDEV_POLL_TIMEOUT_US; 313 | attr->clientInstanceData.ptr = fileData; 314 | 315 | #ifdef GPIO_DEBUG 316 | { 317 | vmk_Log(gpio_Driver->logger, 318 | "opened file; priv %p lock %p", 319 | fileData, 320 | fileData->lock); 321 | } 322 | #endif /* GPIO_DEBUG */ 323 | 324 | /* Call CB */ 325 | status = gpio_CharDevCBs->open(attr); 326 | 327 | return status; 328 | 329 | lock_init_failed: 330 | vmk_HeapFree(gpio_Driver->heapID, fileData); 331 | 332 | file_priv_alloc_failed: 333 | return status; 334 | } 335 | 336 | /* 337 | *********************************************************************** 338 | * gpio_charDevClose -- 339 | * 340 | * Closes a file. 341 | * 342 | * Results: 343 | * VMK_OK on success, error code otherwise 344 | * 345 | * Side Effects: 346 | * None. 347 | *********************************************************************** 348 | */ 349 | VMK_ReturnStatus 350 | gpio_charDevClose(vmk_CharDevFdAttr *attr) 351 | { 352 | VMK_ReturnStatus status = VMK_OK; 353 | gpio_CharFileData_t *fileData = attr->clientInstanceData.ptr; 354 | 355 | if (fileData == NULL) { 356 | status = VMK_BAD_PARAM; 357 | vmk_Warning(gpio_Driver->logger, "file data null"); 358 | goto file_data_null; 359 | } 360 | 361 | vmk_SpinlockLock(fileData->lock); 362 | 363 | fileData->deathPending = VMK_TRUE; 364 | 365 | if (fileData->timerPending == VMK_FALSE) { 366 | vmk_SpinlockUnlock(fileData->lock); 367 | gpio_charDevFileDestroy(fileData); 368 | } 369 | 370 | /* Call CB */ 371 | status = gpio_CharDevCBs->close(attr); 372 | 373 | file_data_null: 374 | return status; 375 | } 376 | 377 | /* 378 | *********************************************************************** 379 | * gpio_charDevIoctl -- 380 | * 381 | * GPIO chardev-specific I/O ops. Used for programming reads to input 382 | * pins. 383 | * 384 | * Results: 385 | * VMK_OK on success, error code otherwise 386 | * 387 | * Side Effects: 388 | * None. 389 | *********************************************************************** 390 | */ 391 | VMK_ReturnStatus 392 | gpio_charDevIoctl(vmk_CharDevFdAttr *attr, 393 | unsigned int cmd, 394 | vmk_uintptr_t userData, 395 | vmk_IoctlCallerSize callerSize, 396 | vmk_int32 *result) 397 | { 398 | VMK_ReturnStatus status = VMK_OK; 399 | gpio_IoctlCookie_t ioctlData; 400 | gpio_CharFileData_t *fileData = attr->clientInstanceData.ptr; 401 | 402 | if (fileData == NULL) { 403 | status = VMK_BAD_PARAM; 404 | vmk_Warning(gpio_Driver->logger, "file data null"); 405 | goto file_data_null; 406 | } 407 | 408 | /* Copy ioctl data from UW */ 409 | status = vmk_CopyFromUser((vmk_VA)&ioctlData, 410 | (vmk_VA)userData, 411 | sizeof(ioctlData)); 412 | if (status != VMK_OK) { 413 | vmk_Warning(gpio_Driver->logger, 414 | "unable to copy ioctl data from UW ptr %p: %s", 415 | userData, 416 | vmk_StatusToString(status)); 417 | goto ioctl_uw2vmk_failed; 418 | } 419 | 420 | #ifdef GPIO_DEBUG 421 | { 422 | vmk_Log(gpio_Driver->logger, 423 | "executing ioctl cmd %d with data %p", 424 | cmd, 425 | userData); 426 | } 427 | #endif 428 | 429 | /* Call CB */ 430 | vmk_SpinlockLock(fileData->lock); 431 | status = gpio_CharDevCBs->ioctl(cmd, &ioctlData); 432 | vmk_SpinlockUnlock(fileData->lock); 433 | if (status != VMK_OK) { 434 | vmk_Warning(gpio_Driver->logger, 435 | "ioctl cmd %d with data %p failed: %s", 436 | cmd, 437 | userData, 438 | vmk_StatusToString(status)); 439 | goto ioctl_cmd_failed; 440 | } 441 | 442 | /* Copy iotl data back to UW */ 443 | status = vmk_CopyToUser((vmk_VA)userData, 444 | (vmk_VA)&ioctlData, 445 | sizeof(ioctlData)); 446 | if (status != VMK_OK) { 447 | vmk_Warning(gpio_Driver->logger, 448 | "unable to copy ioctl data back to UW: %s", 449 | vmk_StatusToString(status)); 450 | goto ioctl_vmk2uw_failed; 451 | } 452 | 453 | return VMK_OK; 454 | 455 | ioctl_vmk2uw_failed: 456 | ioctl_cmd_failed: 457 | ioctl_uw2vmk_failed: 458 | file_data_null: 459 | return status; 460 | } 461 | 462 | /* 463 | *********************************************************************** 464 | * gpio_charDevRead -- 465 | * 466 | * Reads from a file. Not supported for now, as we only allow writing 467 | * commands of sorts to the chardev file. 468 | * 469 | * Results: 470 | * VMK_OK on success, error code otherwise 471 | * 472 | * Side Effects: 473 | * None. 474 | *********************************************************************** 475 | */ 476 | VMK_ReturnStatus 477 | gpio_charDevRead(vmk_CharDevFdAttr *attr, 478 | char *buffer, 479 | vmk_ByteCount nbytes, 480 | vmk_loff_t *ppos, 481 | vmk_ByteCountSigned *nread) 482 | { 483 | return gpio_charDevIO(attr, buffer, nbytes, ppos, nread, VMK_FALSE); 484 | } 485 | 486 | /* 487 | *********************************************************************** 488 | * gpio_charDevWrite -- 489 | * 490 | * Writes to a file. 491 | * 492 | * Results: 493 | * VMK_OK on success, error code otherwise 494 | * 495 | * Side Effects: 496 | * None. 497 | *********************************************************************** 498 | */ 499 | VMK_ReturnStatus 500 | gpio_charDevWrite(vmk_CharDevFdAttr *attr, 501 | char *buffer, 502 | vmk_ByteCount nbytes, 503 | vmk_loff_t *ppos, 504 | vmk_ByteCountSigned *nwritten) 505 | { 506 | return gpio_charDevIO(attr, buffer, nbytes, ppos, nwritten, VMK_TRUE); 507 | } 508 | 509 | /* 510 | *********************************************************************** 511 | * gpio_charDevIO -- 512 | * 513 | * Read/write to file. 514 | * 515 | * Results: 516 | * VMK_OK on success, error code otherwise 517 | * 518 | * Side Effects: 519 | * None. 520 | *********************************************************************** 521 | */ 522 | VMK_ReturnStatus 523 | gpio_charDevIO(vmk_CharDevFdAttr *attr, 524 | char *buffer, 525 | vmk_ByteCount nbytes, 526 | vmk_loff_t *ppos, 527 | vmk_ByteCountSigned *ndone, 528 | vmk_Bool isWrite) 529 | { 530 | VMK_ReturnStatus status = VMK_OK; 531 | gpio_CharFileData_t *fileData; 532 | vmk_ByteCount curByte; 533 | vmk_ByteCountSigned doneBytes = 0; 534 | vmk_loff_t offset; 535 | char *localBuffer; 536 | 537 | if (attr == NULL || ppos == NULL || ndone == NULL) { 538 | status = VMK_BAD_PARAM; 539 | vmk_Warning(gpio_Driver->logger, 540 | "invalid write command received;" 541 | " attr %p ppos %p ndone %p", 542 | attr, ppos, ndone); 543 | goto invalid_params; 544 | } 545 | 546 | #ifdef GPIO_DEBUG 547 | { 548 | vmk_Log(gpio_Driver->logger, 549 | "nbytes %lu offset %ld buffer %p", 550 | nbytes, 551 | *ppos, 552 | buffer); 553 | } 554 | #endif /* GPIO_DEBUG */ 555 | 556 | localBuffer = vmk_HeapAlloc(gpio_Driver->heapID, GPIO_CHARDEV_BUFFER_SIZE); 557 | if (localBuffer == NULL) { 558 | status = VMK_NO_MEMORY; 559 | vmk_Warning(gpio_Driver->logger, 560 | "unable to allocate local buffer: %s", 561 | vmk_StatusToString(status)); 562 | goto localBuffer_alloc_failed; 563 | } 564 | 565 | offset = *ppos; 566 | fileData = (gpio_CharFileData_t *)attr->clientInstanceData.ptr; 567 | 568 | /* 569 | * Perform I/O and copy data to/from user space 570 | */ 571 | if (isWrite) { /* Write */ 572 | for (curByte = 0; curByte < nbytes; ++curByte) { 573 | status = vmk_CopyFromUser( 574 | (vmk_VA)&localBuffer[curByte % GPIO_CHARDEV_BUFFER_SIZE], 575 | (vmk_VA)&buffer[curByte % GPIO_CHARDEV_BUFFER_SIZE], 576 | sizeof(char) 577 | ); 578 | 579 | if (status != VMK_OK) { 580 | break; 581 | } 582 | ++doneBytes; 583 | } 584 | vmk_SpinlockLock(fileData->lock); 585 | status = gpio_CharDevCBs->write(localBuffer, doneBytes, ppos, &doneBytes); 586 | vmk_SpinlockUnlock(fileData->lock); 587 | } 588 | else { /* Read */ 589 | status = gpio_CharDevCBs->read(localBuffer, nbytes, ppos, &doneBytes); 590 | for (curByte = 0; curByte < doneBytes; ++curByte) { 591 | status = vmk_CopyToUser( 592 | (vmk_VA)&buffer[curByte % GPIO_CHARDEV_BUFFER_SIZE], 593 | (vmk_VA)&localBuffer[curByte % GPIO_CHARDEV_BUFFER_SIZE], 594 | sizeof(char) 595 | ); 596 | if (status != VMK_OK) { 597 | break; 598 | } 599 | } 600 | doneBytes = curByte + 1; 601 | } 602 | 603 | vmk_HeapFree(gpio_Driver->heapID, localBuffer); 604 | 605 | *ndone = doneBytes; 606 | 607 | if (status != VMK_OK) { 608 | vmk_Warning(gpio_Driver->logger, 609 | "I/O failed to file %p: %s", 610 | vmk_StatusToString(status)); 611 | goto io_failed; 612 | } 613 | 614 | return VMK_OK; 615 | 616 | io_failed: 617 | localBuffer_alloc_failed: 618 | invalid_params: 619 | return status; 620 | } 621 | 622 | /* 623 | *********************************************************************** 624 | * gpio_charDevPoll -- 625 | * 626 | * Polls a file. 627 | * 628 | * Results: 629 | * VMK_OK on success, error code otherwise 630 | * 631 | * Side Effects: 632 | * None. 633 | *********************************************************************** 634 | */ 635 | VMK_ReturnStatus 636 | gpio_charDevPoll(vmk_CharDevFdAttr *attr, 637 | vmk_PollContext pollCtx, 638 | vmk_uint32 *pollMask) 639 | { 640 | VMK_ReturnStatus status = VMK_OK; 641 | gpio_CharFileData_t *fileData = attr->clientInstanceData.ptr; 642 | vmk_TimerCookie tmrCookie; 643 | 644 | if (fileData == NULL) { 645 | status = VMK_BAD_PARAM; 646 | vmk_Warning(gpio_Driver->logger, "file data null"); 647 | goto file_data_null; 648 | } 649 | 650 | vmk_SpinlockLock(fileData->lock); 651 | 652 | if (fileData->pollMask == VMK_FALSE 653 | && fileData->timerPending == VMK_FALSE 654 | && fileData->deathPending == VMK_FALSE) { 655 | tmrCookie.ptr = fileData; 656 | status = vmk_TimerSchedule(gpio_charDevTimerQueue, 657 | gpio_charDevTimerCB, 658 | tmrCookie, 659 | GPIO_CHARDEV_POLL_TIMEOUT_US, 660 | VMK_TIMER_DEFAULT_TOLERANCE, 661 | VMK_TIMER_ATTR_NONE, 662 | VMK_LOCKDOMAIN_INVALID, 663 | VMK_SPINLOCK_UNRANKED, 664 | &fileData->timer); 665 | if (status == VMK_OK) { 666 | fileData->timerPending = VMK_TRUE; 667 | } 668 | else { 669 | vmk_Warning(gpio_Driver->logger, 670 | "failed to create poll timer: %s", 671 | vmk_StatusToString(status)); 672 | goto create_poll_timer_failed; 673 | } 674 | } 675 | 676 | vmk_CharDevSetPollContext(pollCtx, (void *)fileData); 677 | *pollMask = fileData->pollMask; 678 | 679 | vmk_SpinlockUnlock(fileData->lock); 680 | 681 | return VMK_OK; 682 | 683 | create_poll_timer_failed: 684 | vmk_SpinlockUnlock(fileData->lock); 685 | 686 | file_data_null: 687 | return status; 688 | } 689 | 690 | /* 691 | *********************************************************************** 692 | * gpio_charDevTimerCB -- 693 | * 694 | * Callback for char device poll timer. 695 | * 696 | * Results: 697 | * VMK_OK on success, error code otherwise 698 | * 699 | * Side Effects: 700 | * None. 701 | *********************************************************************** 702 | */ 703 | void 704 | gpio_charDevTimerCB(vmk_TimerCookie data) 705 | { 706 | gpio_CharFileData_t *fileData = (gpio_CharFileData_t *)data.ptr; 707 | 708 | if (fileData == NULL) { 709 | vmk_Warning(gpio_Driver->logger, "file data null"); 710 | goto file_data_null; 711 | } 712 | 713 | vmk_SpinlockLock(fileData->lock); 714 | 715 | fileData->timerPending = VMK_FALSE; 716 | fileData->pollMask = VMKAPI_POLL_WRITE; 717 | 718 | if (fileData->deathPending == VMK_FALSE) { 719 | /* Wake up pollers in UW */ 720 | vmk_CharDevWakePollers(fileData); 721 | } 722 | else { 723 | vmk_SpinlockUnlock(fileData->lock); 724 | gpio_charDevFileDestroy(fileData); 725 | goto death_pending; 726 | } 727 | 728 | vmk_SpinlockUnlock(fileData->lock); 729 | 730 | death_pending: 731 | file_data_null: 732 | return; 733 | } 734 | 735 | /* 736 | *********************************************************************** 737 | * gpio_charDevFileDestroy -- 738 | * 739 | * Destroy a file. 740 | * 741 | * Results: 742 | * VMK_OK on success, error code otherwise 743 | * 744 | * Side Effects: 745 | * None. 746 | *********************************************************************** 747 | */ 748 | void 749 | gpio_charDevFileDestroy(gpio_CharFileData_t *fileData) 750 | { 751 | #ifdef GPIO_DEBUG 752 | { 753 | vmk_Log(gpio_Driver->logger, 754 | "destroying file %p", 755 | fileData); 756 | } 757 | #endif /* GPIO_DEBUG */ 758 | 759 | vmk_SpinlockDestroy(fileData->lock); 760 | vmk_HeapFree(gpio_Driver->heapID, fileData); 761 | } -------------------------------------------------------------------------------- /gpio_charDev.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | * Native ESXi driver for the RPi's GPIO interface. 3 | * 4 | * Tom Hebel, 2020 5 | \******************************************************************************/ 6 | 7 | /* 8 | * gpio_charDev.h -- 9 | */ 10 | 11 | #ifndef GPIO_CHARDEV_H 12 | #define GPIO_CHARDEV_H 13 | 14 | #include "gpio.h" 15 | #include "gpio_types.h" 16 | #include "gpio_drv.h" 17 | 18 | /***********************************************************************/ 19 | 20 | #define GPIO_CHARDEV_BUFFER_SIZE 4096 /* Probably overkill */ 21 | #define GPIO_CHARDEV_POLL_TIMEOUT_US 1000000 22 | 23 | /***********************************************************************/ 24 | 25 | /* 26 | * Callbacks for gluing the char dev driver to the GPIO driver 27 | */ 28 | typedef VMK_ReturnStatus (*gpio_CharDevOpenCB_t)(vmk_CharDevFdAttr *attr); 29 | typedef VMK_ReturnStatus (*gpio_CharDevCloseCB_t)(vmk_CharDevFdAttr *attr); 30 | 31 | typedef VMK_ReturnStatus (*gpio_CharDevIoctlCB_t)(unsigned int cmd, 32 | gpio_IoctlCookie_t *ioctlData); 33 | typedef VMK_ReturnStatus (*gpio_CharDevReadCB_t)(char *buffer, 34 | vmk_ByteCount nbytes, 35 | vmk_loff_t *ppos, 36 | vmk_ByteCountSigned *nread); 37 | typedef VMK_ReturnStatus (*gpio_CharDevWriteCB_t)(char *buffer, 38 | vmk_ByteCount nbytes, 39 | vmk_loff_t *ppos, 40 | vmk_ByteCountSigned *nread); 41 | typedef struct gpio_CharDevCallbacks_t { 42 | gpio_CharDevOpenCB_t open; 43 | gpio_CharDevCloseCB_t close; 44 | gpio_CharDevIoctlCB_t ioctl; 45 | gpio_CharDevReadCB_t read; 46 | gpio_CharDevWriteCB_t write; 47 | } gpio_CharDevCallbacks_t; 48 | 49 | typedef struct gpio_CharDev_t { 50 | vmk_Device vmkDevice; 51 | vmk_CharDevRegData regData; 52 | gpio_CharDevCallbacks_t devCBs; 53 | } gpio_CharDev_t; 54 | 55 | typedef struct gpio_CharFileData_t { 56 | vmk_Lock lock; 57 | char *data; 58 | vmk_Bool timerPending; 59 | vmk_Bool deathPending; 60 | vmk_Timer timer; 61 | vmk_int32 timeoutUS; 62 | vmk_uint32 pollMask; 63 | } gpio_CharFileData_t; 64 | 65 | /***********************************************************************/ 66 | 67 | VMK_ReturnStatus gpio_charDevRegister(gpio_Driver_t *gpioDriver, 68 | vmk_BusType logicalBusType, 69 | vmk_Device parentDevice, 70 | gpio_CharDev_t *charDev, 71 | vmk_uint32 logicalPort, 72 | gpio_CharDevCallbacks_t *devCBs); 73 | 74 | VMK_ReturnStatus gpio_charVmkDevRemove(vmk_Device logicalDev); 75 | 76 | VMK_ReturnStatus gpio_charDevAssoc(vmk_AddrCookie charDevPriv, 77 | vmk_CharDevHandle charDevHandle); 78 | 79 | VMK_ReturnStatus gpio_charDevDisassoc(vmk_AddrCookie charDevPriv); 80 | 81 | VMK_ReturnStatus gpio_charDevOpen(vmk_CharDevFdAttr *attr); 82 | 83 | VMK_ReturnStatus gpio_charDevClose(vmk_CharDevFdAttr *attr); 84 | 85 | VMK_ReturnStatus gpio_charDevIoctl(vmk_CharDevFdAttr *attr, 86 | vmk_uint32 cmd, 87 | vmk_uintptr_t userData, 88 | vmk_IoctlCallerSize callerSize, 89 | vmk_int32 *result); 90 | 91 | VMK_ReturnStatus gpio_charDevRead(vmk_CharDevFdAttr *attr, 92 | char *buffer, 93 | vmk_ByteCount nbytes, 94 | vmk_loff_t *ppos, 95 | vmk_ByteCountSigned *nread); 96 | 97 | VMK_ReturnStatus gpio_charDevWrite(vmk_CharDevFdAttr *attr, 98 | char *buffer, 99 | vmk_ByteCount nbytes, 100 | vmk_loff_t *ppos, 101 | vmk_ByteCountSigned *nwritten); 102 | 103 | VMK_ReturnStatus gpio_charDevIO(vmk_CharDevFdAttr *attr, 104 | char *buffer, 105 | vmk_ByteCount nbytes, 106 | vmk_loff_t *ppos, 107 | vmk_ByteCountSigned *ndone, 108 | vmk_Bool isWrite); 109 | 110 | VMK_ReturnStatus gpio_charDevPoll(vmk_CharDevFdAttr *attr, 111 | vmk_PollContext pollCtx, 112 | vmk_uint32 *pollMask); 113 | 114 | void gpio_charDevTimerCB(vmk_TimerCookie data); 115 | 116 | void gpio_charDevFileDestroy(gpio_CharFileData_t *fileData); 117 | 118 | /***********************************************************************/ 119 | 120 | #endif /* GPIO_CHARDEV_H */ -------------------------------------------------------------------------------- /gpio_drv.c: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | * Native ESXi driver for the RPi's GPIO interface. 3 | * 4 | * Tom Hebel, 2020 5 | \******************************************************************************/ 6 | 7 | /* 8 | * gpio_drv.c -- 9 | * 10 | * Implementation of gpio hardware interface. 11 | * 12 | * TODO: 13 | * - fix up interrupts to work as described here: 14 | * https://www.raspberrypi.org/documentation/hardware/raspberrypi/gpio/README.md 15 | */ 16 | 17 | #include "gpio_drv.h" 18 | 19 | /***********************************************************************/ 20 | 21 | static gpio_Driver_t *gpio_Driver; 22 | static gpio_Device_t *gpio_Device; 23 | 24 | /* 25 | *********************************************************************** 26 | * gpio_drvInit -- 27 | * 28 | * Initializes the GPIO driver. 29 | * 30 | * Results: 31 | * VMK_OK on success, error code otherwise 32 | * 33 | * Side Effects: 34 | * None. 35 | *********************************************************************** 36 | */ 37 | VMK_ReturnStatus 38 | gpio_drvInit(gpio_Driver_t *driver, 39 | gpio_Device_t *adapter) 40 | { 41 | VMK_ReturnStatus status = VMK_OK; 42 | 43 | gpio_Driver = driver; 44 | gpio_Device = adapter; 45 | 46 | return status; 47 | } 48 | 49 | /* 50 | *********************************************************************** 51 | * gpio_mmioPoll -- 52 | * 53 | * Polls a GPIO register and returns once the maked bits have changed. 54 | * 55 | * Results: 56 | * VMK_OK on success, error code otherwise 57 | * 58 | * Side Effects: 59 | * None. 60 | *********************************************************************** 61 | */ 62 | VMK_ReturnStatus 63 | gpio_mmioPoll(vmk_uint32 offset, // IN 64 | vmk_uint32 mask, // IN 65 | vmk_uint32 *data) // OUT 66 | { 67 | VMK_ReturnStatus status = VMK_OK; 68 | vmk_uint32 prevData, curData; 69 | vmk_uint32 prevMasked, curMasked; 70 | vmk_TimerCycles monoCounterFreq = vmk_GetMonotonicCounterFrequency(); 71 | unsigned long startTime, curTime; 72 | 73 | #ifdef GPIO_DEBUG 74 | { 75 | vmk_Log(gpio_Driver->logger, 76 | "polling GPIO MMIO offset 0x%x with mask 0x%x", 77 | offset, 78 | mask); 79 | } 80 | #endif /* GPIO_DEBUG */ 81 | 82 | status = vmk_MappedResourceRead32(&gpio_Device->mmioMappedAddr, 83 | offset, 84 | &prevData); 85 | if (status != VMK_OK) { 86 | goto mmio_read_failed; 87 | } 88 | 89 | /* Mask on only the bits we want to poll */ 90 | prevMasked = prevData & mask; 91 | 92 | /* 93 | * Loop until the read value changes or we hit the timeout 94 | */ 95 | startTime = vmk_TimerMonotonicCounter; 96 | do { 97 | status = vmk_MappedResourceRead32(&gpio_Device->mmioMappedAddr, 98 | offset, 99 | &curData); 100 | if (status != VMK_OK) { 101 | goto mmio_read_failed; 102 | } 103 | 104 | curMasked = curData & mask; 105 | 106 | curTime = vmk_TimerMonotonicCounter; 107 | if ((curTime - startTime) / monoCounterFreq >= GPIO_POLL_TIMEOUT_SEC) { 108 | goto poll_timed_out; 109 | } 110 | } while (curMasked == prevMasked); 111 | 112 | *data = curData; 113 | 114 | return VMK_OK; 115 | 116 | poll_timed_out: 117 | return VMK_TIMEOUT; 118 | 119 | mmio_read_failed: 120 | vmk_Warning(gpio_Driver->logger, 121 | "read from GPIO MMIO offset 0x%x failed: %s", 122 | offset, 123 | vmk_StatusToString(status)); 124 | return status; 125 | } 126 | 127 | /* 128 | *********************************************************************** 129 | * gpio_mmioDirectRead -- 130 | * 131 | * Read directly from the GPIO MMIO mapped area. 132 | * 133 | * Results: 134 | * VMK_OK on success, error code otherwise 135 | * 136 | * Side Effects: 137 | * None. 138 | *********************************************************************** 139 | */ 140 | VMK_INLINE 141 | VMK_ReturnStatus 142 | gpio_mmioDirectRead(vmk_uint32 offset, 143 | vmk_uint32 *value) 144 | { 145 | VMK_ReturnStatus status = VMK_OK; 146 | 147 | #ifdef GPIO_DEBUG 148 | { 149 | vmk_Log(gpio_Driver->logger, 150 | "reading value from GPIO MMIO offset 0x%x", 151 | offset); 152 | } 153 | #endif /* GPIO_DEBUG */ 154 | 155 | status = vmk_MappedResourceRead32(&gpio_Device->mmioMappedAddr, 156 | offset, 157 | value); 158 | 159 | return status; 160 | } 161 | 162 | /* 163 | *********************************************************************** 164 | * gpio_mmioDirectWrite -- 165 | * 166 | * Write directly to the GPIO MMIO mapped area. 167 | * 168 | * Results: 169 | * VMK_OK on success, error code otherwise 170 | * 171 | * Side Effects: 172 | * None. 173 | *********************************************************************** 174 | */ 175 | VMK_INLINE 176 | VMK_ReturnStatus 177 | gpio_mmioDirectWrite(vmk_uint32 offset, 178 | vmk_uint32 value) 179 | { 180 | VMK_ReturnStatus status = VMK_OK; 181 | 182 | #ifdef GPIO_DEBUG 183 | { 184 | vmk_Log(gpio_Driver->logger, 185 | "writing value 0x%x to GPIO MMIO offset 0x%x", 186 | value, 187 | offset); 188 | } 189 | #endif /* GPIO_DEBUG */ 190 | 191 | status = vmk_MappedResourceWrite32(&gpio_Device->mmioMappedAddr, 192 | offset, 193 | value); 194 | 195 | return status; 196 | } 197 | 198 | /* 199 | *********************************************************************** 200 | * gpio_mmioOpenCB -- 201 | * 202 | * Callback used by char dev driver when the /dev/vmgfx32 file is 203 | * opened. 204 | * 205 | * Results: 206 | * VMK_OK on success, error code otherwise 207 | * 208 | * Side Effects: 209 | * None. 210 | *********************************************************************** 211 | */ 212 | VMK_ReturnStatus 213 | gpio_mmioOpenCB(vmk_CharDevFdAttr *attr) 214 | { 215 | VMK_ReturnStatus status = VMK_OK; 216 | 217 | return status; 218 | } 219 | 220 | /* 221 | *********************************************************************** 222 | * gpio_mmioCloseCB -- 223 | * 224 | * Callback used by char dev driver when the /dev/vmgfx32 file is 225 | * closed. 226 | * 227 | * Results: 228 | * VMK_OK on success, error code otherwise 229 | * 230 | * Side Effects: 231 | * None. 232 | *********************************************************************** 233 | */ 234 | VMK_ReturnStatus 235 | gpio_mmioCloseCB(vmk_CharDevFdAttr *attr) 236 | { 237 | VMK_ReturnStatus status = VMK_OK; 238 | 239 | return status; 240 | } 241 | 242 | /* 243 | *********************************************************************** 244 | * gpio_mmioIoctlCB -- 245 | * 246 | * Callback used by char dev driver for I/O control. I/O control is 247 | * used to allow to perform alternate reads/writes that aren't 248 | * supported by the corresponding syscalls. For example, for polling 249 | * a pin value until it changes. 250 | * 251 | * Results: 252 | * VMK_OK on success, error code otherwise 253 | * 254 | * Side Effects: 255 | * None. 256 | *********************************************************************** 257 | */ 258 | VMK_ReturnStatus 259 | gpio_mmioIoctlCB(unsigned int cmd, // IN 260 | gpio_IoctlCookie_t *data) // IN/OUT 261 | { 262 | VMK_ReturnStatus status = VMK_OK; 263 | gpio_IoctlData_t ioctlData; 264 | vmk_uint32 outData; 265 | 266 | vmk_Memcpy(&ioctlData, data, sizeof(ioctlData)); 267 | 268 | /* Sanity checks */ 269 | if (ioctlData.offset % GPIO_REG_SIZE != 0 270 | || ioctlData.offset > GPIO_MMIO_SIZE 271 | || cmd <= GPIO_IOCTL_INVALID 272 | || cmd > GPIO_IOCTL_MAX) { 273 | status = VMK_BAD_PARAM; 274 | vmk_Warning(gpio_Driver->logger, 275 | "invalid ioctl: cmd %d data %p" 276 | " (0x%x, 0x%x)", 277 | cmd, 278 | *data, 279 | ioctlData.offset, 280 | ioctlData.mask); 281 | goto ioctl_invalid; 282 | } 283 | 284 | /* Run ioctl command */ 285 | switch (cmd) { 286 | case GPIO_IOCTL_POLL: 287 | status = gpio_mmioPoll(ioctlData.offset, ioctlData.mask, &outData); 288 | break; 289 | } 290 | if (status != VMK_OK) { 291 | goto ioctl_cmd_failed; 292 | } 293 | 294 | /* Prep data for output */ 295 | ioctlData.data = outData; 296 | vmk_Memcpy(data, &ioctlData, sizeof(ioctlData)); 297 | 298 | return VMK_OK; 299 | 300 | ioctl_cmd_failed: 301 | ioctl_invalid: 302 | return status; 303 | } 304 | 305 | /* 306 | *********************************************************************** 307 | * gpio_mmioReadCB -- 308 | * 309 | * Callback used by char dev driver when the /dev/vmgfx32 file is 310 | * read. 311 | * 312 | * Results: 313 | * VMK_OK on success, error code otherwise 314 | * 315 | * Side Effects: 316 | * None. 317 | *********************************************************************** 318 | */ 319 | VMK_ReturnStatus 320 | gpio_mmioReadCB(char *buffer, 321 | vmk_ByteCount nbytes, 322 | vmk_loff_t *ppos, 323 | vmk_ByteCountSigned *nread) 324 | { 325 | VMK_ReturnStatus status = VMK_OK; 326 | vmk_loff_t offset = *ppos; 327 | vmk_uint32 value = 0; 328 | 329 | /* 330 | * Offset sanity checks 331 | */ 332 | if (offset % GPIO_REG_SIZE != 0 333 | || offset > GPIO_MMIO_SIZE - GPIO_REG_SIZE) { 334 | status = VMK_FAILURE; 335 | vmk_Warning(gpio_Driver->logger, 336 | "attempted read from invalid MMIO offset 0x%x", 337 | offset); 338 | goto invalid_mmio_offset; 339 | } 340 | 341 | /* 342 | * Read from GPIO MMIO space 343 | */ 344 | status = gpio_mmioDirectRead(offset, &value); 345 | if (status != VMK_OK) { 346 | vmk_Warning(gpio_Driver->logger, 347 | "failed to read from GPIO MMIO region: %s", 348 | vmk_StatusToString(status)); 349 | *nread = 0; 350 | goto mmio_read_failed; 351 | } 352 | 353 | /* Write register value to UW buffer */ 354 | *(vmk_uint32 *)buffer = value; 355 | 356 | #ifdef GPIO_DEBUG 357 | { 358 | vmk_Log(gpio_Driver->logger, 359 | "read 0x%x from offset 0x%x", 360 | value, 361 | offset); 362 | } 363 | #endif /* GPIO_DEBUG */ 364 | 365 | /* Each read is the size of a register */ 366 | *nread = GPIO_REG_SIZE; 367 | 368 | return VMK_OK; 369 | 370 | mmio_read_failed: 371 | invalid_mmio_offset: 372 | return status; 373 | } 374 | 375 | /* 376 | *********************************************************************** 377 | * gpio_mmioWrite -- 378 | * 379 | * Callback used by char dev driver when the /dev/vmgfx32 file is 380 | * written to. We can write without locking, since locking is handled 381 | * by the char dev driver. 382 | * 383 | * Results: 384 | * VMK_OK on success, error code otherwise 385 | * 386 | * Side Effects: 387 | * None. 388 | *********************************************************************** 389 | */ 390 | VMK_ReturnStatus 391 | gpio_mmioWriteCB(char *buffer, 392 | vmk_ByteCount nbytes, 393 | vmk_loff_t *ppos, 394 | vmk_ByteCountSigned *nwritten) 395 | { 396 | VMK_ReturnStatus status = VMK_OK; 397 | vmk_loff_t offset = *ppos; 398 | long value; 399 | 400 | /* 401 | * Offset sanity checks 402 | */ 403 | if (offset % GPIO_REG_SIZE != 0 404 | || offset + nbytes > GPIO_MMIO_SIZE) { 405 | status = VMK_FAILURE; 406 | vmk_Warning(gpio_Driver->logger, 407 | "attempted write to invalid MMIO offset 0x%x", 408 | offset); 409 | goto invalid_mmio_offset; 410 | } 411 | 412 | value = *(vmk_uint32*)buffer; 413 | status = gpio_mmioDirectWrite(offset, (vmk_uint32)value); 414 | if (status != VMK_OK) { 415 | vmk_Warning(gpio_Driver->logger, 416 | "failed to write to GPIO MMIO region: %s", 417 | vmk_StatusToString(status)); 418 | *nwritten = 0; 419 | goto mmio_write_failed; 420 | } 421 | 422 | /* Each write is the size of a register */ 423 | *nwritten = GPIO_REG_SIZE; 424 | 425 | mmio_write_failed: 426 | invalid_mmio_offset: 427 | return status; 428 | } -------------------------------------------------------------------------------- /gpio_drv.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | * Native ESXi driver for the RPi's GPIO interface. 3 | * 4 | * Tom Hebel, 2020 5 | \******************************************************************************/ 6 | 7 | /* 8 | * gpio_drv.h -- 9 | * 10 | * Definition for gpio device layer interface 11 | */ 12 | 13 | #ifndef GPIO_DRV_H 14 | #define GPIO_DRV_H 15 | 16 | #include "gpio.h" 17 | #include "gpio_types.h" 18 | 19 | /***********************************************************************/ 20 | 21 | /* 22 | * As documented in: 23 | * https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2711/rpi_DATA_2711_1p0.pdf 24 | */ 25 | #define GPIO_MMIO_SIZE 0xf4 26 | 27 | /* Each register is 4 bytes in size */ 28 | #define GPIO_REG_SIZE 4 29 | 30 | #define GPIO_NUM_REGS (GPIO_MMIO_SIZE / GPIO_REG_SIZE) 31 | 32 | #define GPIO_POLL_TIMEOUT_SEC 1 33 | 34 | /***********************************************************************/ 35 | 36 | /* 37 | * Packing the struct to avoid alignment issues when passing between UW and 38 | * driver. Must be exactly 64 bits in size! 39 | */ 40 | #pragma pack(push, 1) 41 | typedef struct gpio_IoctlData_t { 42 | vmk_uint16 offset; 43 | union { 44 | vmk_uint32 mask; 45 | vmk_uint32 data; 46 | }; 47 | /* We might use this for something at some point */ 48 | vmk_uint16 __padding; 49 | } gpio_IoctlData_t; 50 | #pragma pack(pop) 51 | 52 | /* 53 | * An opaque version of gpio_IoctlData_t, to be used outside of gpio_drv.c. 54 | */ 55 | typedef vmk_uint64 gpio_IoctlCookie_t; 56 | 57 | typedef enum gpio_IoctlCommands_t { 58 | GPIO_IOCTL_INVALID = 0, 59 | /* Poll a GPIO register until masked bits change */ 60 | GPIO_IOCTL_POLL = 1, 61 | /* Used for sanity */ 62 | GPIO_IOCTL_MAX = GPIO_IOCTL_POLL, 63 | } gpio_IoctlCommands_t; 64 | 65 | /***********************************************************************/ 66 | 67 | VMK_ReturnStatus gpio_drvInit(gpio_Driver_t *driver, 68 | gpio_Device_t *adapter); 69 | 70 | VMK_ReturnStatus gpio_mmioPoll(vmk_uint32 offset, 71 | vmk_uint32 mask, 72 | vmk_uint32 *data); 73 | 74 | VMK_ReturnStatus gpio_mmioDirectRead(vmk_uint32 offset, 75 | vmk_uint32 *value); 76 | 77 | VMK_ReturnStatus gpio_mmioDirectWrite(vmk_uint32 offset, 78 | vmk_uint32 value); 79 | 80 | VMK_ReturnStatus gpio_mmioOpenCB(vmk_CharDevFdAttr *attr); 81 | 82 | VMK_ReturnStatus gpio_mmioCloseCB(vmk_CharDevFdAttr *attr); 83 | 84 | VMK_ReturnStatus gpio_mmioIoctlCB(unsigned int cmd, 85 | gpio_IoctlCookie_t *data); 86 | 87 | VMK_ReturnStatus gpio_mmioReadCB(char *buffer, 88 | vmk_ByteCount nbytes, 89 | vmk_loff_t *ppos, 90 | vmk_ByteCountSigned *nread); 91 | 92 | VMK_ReturnStatus gpio_mmioWriteCB(char *buffer, 93 | vmk_ByteCount nbytes, 94 | vmk_loff_t *ppos, 95 | vmk_ByteCountSigned *nwritten); 96 | 97 | /***********************************************************************/ 98 | 99 | #endif /* GPIO_DRV_H */ -------------------------------------------------------------------------------- /gpio_os.c: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | * Native ESXi driver for the RPi's GPIO interface. 3 | * 4 | * Tom Hebel, 2020 5 | \******************************************************************************/ 6 | 7 | /* 8 | * gpio_os.c -- 9 | * 10 | * Module initialization and other OS stuff. 11 | */ 12 | 13 | #include "gpio.h" 14 | #include "gpio_types.h" 15 | #include "gpio_drv.h" 16 | #include "gpio_charDev.h" 17 | 18 | /***********************************************************************/ 19 | 20 | static gpio_Driver_t gpio_Driver; 21 | static gpio_Device_t gpio_Device; 22 | 23 | /* For chardev */ 24 | static gpio_CharDev_t gpio_CharDev; 25 | static vmk_BusType gpio_logicalBusType; 26 | static vmk_uint32 gpio_logicalPort = 0; 27 | 28 | VMK_ReturnStatus gpio_attachDevice(vmk_Device device); 29 | VMK_ReturnStatus gpio_scanDevice(vmk_Device device); 30 | VMK_ReturnStatus gpio_detachDevice(vmk_Device device); 31 | VMK_ReturnStatus gpio_quiesceDevice(vmk_Device device); 32 | VMK_ReturnStatus gpio_startDevice(vmk_Device device); 33 | void gpio_forgetDevice(vmk_Device device); 34 | 35 | static vmk_DriverOps gpio_DriverOps = { 36 | .attachDevice = gpio_attachDevice, 37 | .scanDevice = gpio_scanDevice, 38 | .detachDevice = gpio_detachDevice, 39 | .quiesceDevice = gpio_quiesceDevice, 40 | .startDevice = gpio_startDevice, 41 | .forgetDevice = gpio_forgetDevice, 42 | }; 43 | 44 | /* 45 | * Callbacks gluing the chardev driver to the GPIO driver "back-end". 46 | */ 47 | static gpio_CharDevCallbacks_t gpio_CharDevCBs = { 48 | .open = gpio_mmioOpenCB, 49 | .close = gpio_mmioCloseCB, 50 | .ioctl = gpio_mmioIoctlCB, 51 | .read = gpio_mmioReadCB, 52 | .write = gpio_mmioWriteCB, 53 | }; 54 | 55 | /* 56 | *********************************************************************** 57 | * init_module -- 58 | * 59 | * Entry point for driver. 60 | * 61 | * Results: 62 | * VMK_OK on success, error code otherwise 63 | * 64 | * Side Effects: 65 | * None 66 | *********************************************************************** 67 | */ 68 | VMK_ReturnStatus 69 | init_module(void) 70 | { 71 | VMK_ReturnStatus status; 72 | vmk_HeapID heapID; 73 | vmk_HeapCreateProps heapProps; 74 | vmk_DriverProps driverProps; 75 | vmk_LogProperties logProps; 76 | vmk_Name logicalBusName; /* For the chardev */ 77 | 78 | status = vmk_NameInitialize(&gpio_Driver.driverName, GPIO_DRIVER_NAME); 79 | VMK_ASSERT(status == VMK_OK); 80 | 81 | gpio_Driver.moduleID = vmk_ModuleCurrentID; 82 | 83 | gpio_Device.initialized = VMK_FALSE; 84 | 85 | /* 86 | * Create heap 87 | */ 88 | 89 | heapProps.type = VMK_HEAP_TYPE_SIMPLE; 90 | status = vmk_NameInitialize(&heapProps.name, GPIO_DRIVER_NAME); 91 | VMK_ASSERT(status == VMK_OK); 92 | heapProps.module = gpio_Driver.moduleID; 93 | heapProps.initial = GPIO_HEAP_INITIAL_SIZE; 94 | heapProps.creationTimeoutMS = VMK_TIMEOUT_NONBLOCKING; 95 | heapProps.max = GPIO_HEAP_MAX_SIZE; 96 | 97 | status = vmk_HeapCreate(&heapProps, &heapID); 98 | if (status != VMK_OK) { 99 | vmk_WarningMessage("vmk_HeapCreate failed: %s", 100 | vmk_StatusToString(status)); 101 | goto heap_create_failed; 102 | } 103 | 104 | vmk_ModuleSetHeapID(gpio_Driver.moduleID, heapID); 105 | VMK_ASSERT(vmk_ModuleGetHeapID(gpio_Driver.moduleID) == heapID); 106 | gpio_Driver.heapID = heapID; 107 | 108 | /* 109 | * Set up logger 110 | */ 111 | 112 | status = vmk_NameInitialize(&logProps.name, GPIO_DRIVER_NAME); 113 | VMK_ASSERT(status == VMK_OK); 114 | logProps.module = gpio_Driver.moduleID; 115 | logProps.heap = gpio_Driver.heapID; 116 | logProps.defaultLevel = 0; 117 | logProps.throttle = NULL; 118 | 119 | status = vmk_LogRegister(&logProps, &gpio_Driver.logger); 120 | if (status != VMK_OK) { 121 | vmk_WarningMessage("failed to register logger: %s", 122 | vmk_StatusToString(status)); 123 | goto logger_reg_failed; 124 | } 125 | vmk_LogSetCurrentLogLevel(gpio_Driver.logger, GPIO_LOG_INIT); 126 | 127 | /* 128 | * Init logical bus 129 | */ 130 | 131 | status = vmk_NameInitialize(&logicalBusName, VMK_LOGICAL_BUS_NAME); 132 | status = vmk_BusTypeFind(&logicalBusName, &gpio_logicalBusType); 133 | if (status != VMK_OK) { 134 | vmk_Warning(gpio_Driver.logger, 135 | "vmk_BusTypeFind for logical bus failed: %s", 136 | vmk_StatusToString(status)); 137 | goto logical_bus_failed; 138 | } 139 | 140 | /* 141 | * Register driver 142 | */ 143 | 144 | vmk_Memset(&driverProps, 0, sizeof(vmk_DriverProps)); 145 | status = vmk_NameInitialize(&driverProps.name, GPIO_DRIVER_NAME); 146 | VMK_ASSERT(status == VMK_OK); 147 | driverProps.moduleID = gpio_Driver.moduleID; 148 | driverProps.ops = &gpio_DriverOps; 149 | 150 | status = vmk_DriverRegister(&driverProps, 151 | &(gpio_Driver.driverHandle)); 152 | if (status == VMK_OK) { 153 | vmk_Log(gpio_Driver.logger, 154 | "vmk_DriverRegister successful", 155 | GPIO_DRIVER_NAME, 156 | __FUNCTION__); 157 | } 158 | else { 159 | vmk_Warning(gpio_Driver.logger, 160 | "vmk_DriverRegister failed: %s", 161 | vmk_StatusToString(status)); 162 | goto driver_register_failed; 163 | } 164 | 165 | /* 166 | * Init GPIO driver 167 | */ 168 | gpio_drvInit(&gpio_Driver, &gpio_Device); 169 | 170 | return VMK_OK; 171 | 172 | driver_register_failed: 173 | logical_bus_failed: 174 | vmk_LogUnregister(gpio_Driver.logger); 175 | 176 | logger_reg_failed: 177 | vmk_HeapDestroy(gpio_Driver.heapID); 178 | 179 | heap_create_failed: 180 | vmk_Warning(gpio_Driver.logger, 181 | "%s: %s failed", 182 | GPIO_DRIVER_NAME, 183 | __FUNCTION__); 184 | 185 | return status; 186 | } 187 | 188 | /* 189 | *********************************************************************** 190 | * cleanup_module -- 191 | * 192 | * Cleans up the module. 193 | * 194 | * Results: 195 | * None. 196 | * 197 | * Side Effects: 198 | * None 199 | *********************************************************************** 200 | */ 201 | void cleanup_module(void) 202 | { 203 | vmk_BusTypeRelease(gpio_logicalBusType); 204 | vmk_HeapDestroy(gpio_Driver.heapID); 205 | vmk_ACPIUnmapIOResource(gpio_Driver.moduleID, gpio_Device.acpiDevice, 0); 206 | vmk_DriverUnregister(gpio_Driver.driverHandle); 207 | } 208 | 209 | /* 210 | *********************************************************************** 211 | * gpio_attachDevice -- 212 | * 213 | * Driver callback that attaches gpio logical device to the driver. 214 | * 215 | * Results: 216 | * VMK_OK on success, error code otherwise 217 | * 218 | * Side Effects: 219 | * None 220 | *********************************************************************** 221 | */ 222 | VMK_ReturnStatus 223 | gpio_attachDevice(vmk_Device device) 224 | { 225 | VMK_ReturnStatus status = VMK_OK; 226 | vmk_ACPIDevice acpiDev; 227 | vmk_ACPIInfo acpiInfo; 228 | vmk_MappedResourceAddress mappedAddr; 229 | gpio_Device_t *adapter = &gpio_Device; 230 | 231 | /* 232 | * Since there is only one gpio device on the system board, we're making 233 | * sure we're not initializing more. 234 | */ 235 | if (adapter->initialized != VMK_FALSE) { 236 | vmk_Warning(gpio_Driver.logger, 237 | "gpio device %p already initialized as %p", 238 | device, 239 | adapter->vmkDevice); 240 | status = VMK_EXISTS; 241 | goto device_already_exists; 242 | } 243 | 244 | /* 245 | * Get vmk acpi dev 246 | */ 247 | status = vmk_DeviceGetRegistrationData(device, (vmk_AddrCookie *)&acpiDev); 248 | if (status != VMK_OK) { 249 | vmk_Warning(gpio_Driver.logger, 250 | "failed to get acpi dev for vmk dev %p: %s", 251 | device, 252 | vmk_StatusToString(status)); 253 | goto get_acpi_dev_failed; 254 | } 255 | 256 | /* 257 | * Find out if this is an acpi dev 258 | */ 259 | status = vmk_ACPIQueryInfo(acpiDev, &acpiInfo); 260 | if (status != VMK_OK) { 261 | goto not_an_acpi_dev; 262 | } 263 | 264 | vmk_Log(gpio_Driver.logger, 265 | "retrieved acpi dev %p for vmk dev %p", 266 | acpiDev, 267 | device); 268 | 269 | /* 270 | * Map io resources 271 | */ 272 | 273 | status = vmk_ACPIMapIOResource(gpio_Driver.moduleID, 274 | acpiDev, 275 | 0, 276 | &mappedAddr); 277 | if (status != VMK_OK) { 278 | vmk_Warning(gpio_Driver.logger, 279 | "unable to acquire io resources" 280 | " for device %p: %s", 281 | device, 282 | vmk_StatusToString(status)); 283 | goto iores_map_failed; 284 | } 285 | else if (mappedAddr.type != VMK_IORESOURCE_MEM) { 286 | vmk_Warning(gpio_Driver.logger, 287 | "mapped io space %p of type %d, expecting %d", 288 | mappedAddr, 289 | mappedAddr.type, 290 | VMK_IORESOURCE_MEM); 291 | goto iores_map_failed; 292 | } 293 | 294 | vmk_Log(gpio_Driver.logger, 295 | "mapped io space at %p of length %d for device %p", 296 | (void*)mappedAddr.address.vaddr, 297 | mappedAddr.len, 298 | device); 299 | 300 | /* 301 | * Init adapter 302 | */ 303 | 304 | vmk_Memset(adapter, 0, sizeof(*adapter)); 305 | adapter->vmkDevice = device; 306 | adapter->acpiDevice = acpiDev; 307 | adapter->acpiInfo = acpiInfo; 308 | vmk_AtomicWrite64(&adapter->refCount, 0); 309 | adapter->wakeWorld = VMK_INVALID_WORLD_ID; 310 | vmk_Memcpy(&adapter->mmioMappedAddr, 311 | &mappedAddr, 312 | sizeof(adapter->mmioMappedAddr)); 313 | adapter->mmioBase = (void *)mappedAddr.address.vaddr; 314 | adapter->mmioLen = mappedAddr.len; 315 | 316 | /* Currently unused */ 317 | vmk_AtomicWrite64(&adapter->intCount, 0); 318 | vmk_AtomicWrite64(&adapter->intState, 0); 319 | 320 | adapter->initialized = VMK_TRUE; 321 | 322 | /* 323 | * Attach device 324 | */ 325 | status = vmk_DeviceSetAttachedDriverData(adapter->vmkDevice, adapter); 326 | if (status != VMK_OK) { 327 | vmk_Warning(gpio_Driver.logger, 328 | "failed to attach device %p: %s", 329 | adapter->vmkDevice, 330 | vmk_StatusToString(status)); 331 | goto device_attach_failed; 332 | } 333 | 334 | return status; 335 | 336 | device_attach_failed: 337 | iores_map_failed: 338 | not_an_acpi_dev: 339 | get_acpi_dev_failed: 340 | device_already_exists: 341 | vmk_Warning(gpio_Driver.logger, 342 | "no device attached", 343 | GPIO_DRIVER_NAME, 344 | __FUNCTION__); 345 | return status; 346 | } 347 | 348 | /* 349 | *********************************************************************** 350 | * gpio_scanDevice -- 351 | * 352 | * Register char dev. 353 | * 354 | * Results: 355 | * VMK_OK on success, error code otherwise 356 | * 357 | * Side Effects: 358 | * None 359 | *********************************************************************** 360 | */ 361 | VMK_ReturnStatus 362 | gpio_scanDevice(vmk_Device device) 363 | { 364 | VMK_ReturnStatus status = VMK_OK; 365 | gpio_CharDev_t *charDev = &gpio_CharDev; 366 | 367 | /* 368 | * Register char device 369 | */ 370 | status = gpio_charDevRegister(&gpio_Driver, 371 | gpio_logicalBusType, 372 | device, 373 | charDev, 374 | gpio_logicalPort, 375 | &gpio_CharDevCBs); 376 | 377 | return status; 378 | } 379 | 380 | /* 381 | *********************************************************************** 382 | * gpio_detachDevice -- 383 | * 384 | * Detaches the device from the driver. 385 | * 386 | * Results: 387 | * VMK_OK on success, error code otherwise 388 | * 389 | * Side Effects: 390 | * None 391 | *********************************************************************** 392 | */ 393 | VMK_ReturnStatus 394 | gpio_detachDevice(vmk_Device device) 395 | { 396 | VMK_ReturnStatus status = VMK_OK; 397 | 398 | return status; 399 | } 400 | 401 | /* 402 | *********************************************************************** 403 | * gpio_quiesceDevice -- 404 | * 405 | * Do nothing. 406 | * 407 | * Results: 408 | * VMK_OK on success, error code otherwise 409 | * 410 | * Side Effects: 411 | * None 412 | *********************************************************************** 413 | */ 414 | VMK_ReturnStatus 415 | gpio_quiesceDevice(vmk_Device device) 416 | { 417 | VMK_ReturnStatus status = VMK_OK; 418 | 419 | return status; 420 | } 421 | 422 | /* 423 | *********************************************************************** 424 | * gpio_startDevice -- 425 | * 426 | * Start the GPIO device. 427 | * 428 | * Results: 429 | * VMK_OK on success, error code otherwise 430 | * 431 | * Side Effects: 432 | * None 433 | *********************************************************************** 434 | */ 435 | VMK_ReturnStatus 436 | gpio_startDevice(vmk_Device device) 437 | { 438 | VMK_ReturnStatus status = VMK_OK; 439 | 440 | return status; 441 | } 442 | 443 | /* 444 | *********************************************************************** 445 | * gpio_forgetDevice -- 446 | * 447 | * Do nothing. 448 | * 449 | * Results: 450 | * None. 451 | * 452 | * Side Effects: 453 | * None 454 | *********************************************************************** 455 | */ 456 | void 457 | gpio_forgetDevice(vmk_Device device) 458 | { 459 | return; 460 | } -------------------------------------------------------------------------------- /gpio_types.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | * Native ESXi driver for the RPi's GPIO interface. 3 | * 4 | * Tom Hebel, 2020 5 | \******************************************************************************/ 6 | 7 | /* 8 | * gpio_types.h -- 9 | * 10 | * Type definitions 11 | */ 12 | 13 | #ifndef GPIO_TYPES_H 14 | #define GPIO_TYPES_H 15 | 16 | /***********************************************************************/ 17 | 18 | typedef struct gpio_Driver_t { 19 | vmk_Name driverName; 20 | vmk_ModuleID moduleID; 21 | vmk_HeapID heapID; 22 | vmk_Driver driverHandle; 23 | vmk_IOResource resHandle; 24 | vmk_LogComponent logger; 25 | } gpio_Driver_t; 26 | 27 | /* 28 | * The gpio adapter. It should only ever be allocated once since there is only 29 | * one gpio interface on a Raspberry Pi. 30 | */ 31 | typedef struct gpio_Device_t { 32 | /* Object */ 33 | vmk_Bool initialized; 34 | vmk_atomic64 refCount; 35 | /* Device */ 36 | vmk_Device vmkDevice; 37 | vmk_ACPIDevice acpiDevice; 38 | vmk_ACPIInfo acpiInfo; 39 | /* Interrupts */ 40 | vmk_IntrCookie intrCookie; 41 | vmk_atomic64 intCount; 42 | vmk_atomic64 intState; 43 | vmk_WorldID wakeWorld; 44 | /* MMIO */ 45 | vmk_MappedResourceAddress mmioMappedAddr; 46 | char *mmioBase; 47 | vmk_ByteCount mmioLen; 48 | } gpio_Device_t; 49 | 50 | /* 51 | * Struct holding information about a world to launch while starting the GPIO 52 | * device in gpio_startDevice(). 53 | */ 54 | typedef struct gpio_StartUpWorld_t { 55 | char name[32]; 56 | vmk_WorldID worldID; 57 | vmk_WorldStartFunc startFunc; 58 | } gpio_StartUpWorld_t; 59 | 60 | /***********************************************************************/ 61 | 62 | #endif /* GPIO_TYPES_H */ -------------------------------------------------------------------------------- /pyUtil/gpioLib/__init__.py: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # UW library interfacing with the GPIO MMIO area. 3 | # 4 | # Tom Hebel, 2020 5 | ################################################################################ 6 | 7 | import math 8 | import struct 9 | import fcntl 10 | 11 | ######################################################################### 12 | 13 | GPIO_DEVICE_PATH = '/dev/vmgfx32' 14 | GPIO_REG_SIZE = 4 15 | GPIO_MAX_PIN = 57 16 | GPIO_MMIO_SIZE = 248 17 | GPIO_INT_MAX = pow(2, 32) - 1 18 | GPIO_PIN_MASK = 31 19 | GPIO_RPI_BYTE_ORDER = 'little' 20 | GPIO_IOCTL_COOKIE_SIZE = 8 21 | 22 | ######################################################################### 23 | 24 | # 25 | # GPIO ioctl commands 26 | # 27 | GPIO_IOCTL_POLL = 1 28 | 29 | ######################################################################### 30 | 31 | # 32 | # GPIO function selectors 33 | # 34 | gpioFuncSelectors = { 35 | 'IN': 0b000, 36 | 'OUT': 0b001, 37 | 'F0': 0b100, 38 | 'F1': 0b101, 39 | 'F2': 0b110, 40 | 'F3': 0b111, 41 | 'F4': 0b011, 42 | 'F5': 0b010, 43 | } 44 | gpioPinToSelReg= [ 45 | 'GPFSEL0', 'GPFSEL1', 'GPFSEL2', 'GPFSEL3', 'GPFSEL4', 'GPFSEL5', 46 | ] 47 | 48 | # 49 | # Pull-up/down states 50 | # 51 | gpioPUDs = { 52 | 'OFF': 0b00, 53 | 'DN': 0b10, 54 | 'UP': 0b01, 55 | } 56 | gpioPinToPudReg = [ 57 | 'GPIO_PUP_PDN_CNTRL_REG0', 58 | 'GPIO_PUP_PDN_CNTRL_REG1', 59 | 'GPIO_PUP_PDN_CNTRL_REG2', 60 | 'GPIO_PUP_PDN_CNTRL_REG3', 61 | ] 62 | 63 | # 64 | # GPIO registers 65 | # 66 | gpioRegisters = { 67 | # 68 | # Function selector registers which control the operation of the 54 gpio pins. 69 | # 70 | 'GPFSEL0': 0x00, # gpio function select 0 71 | 'GPFSEL1': 0x04, # gpio function select 1 72 | 'GPFSEL2': 0x08, # gpio function select 2 73 | 'GPFSEL3': 0x0c, # gpio function select 3 74 | 'GPFSEL4': 0x10, # gpio function select 4 75 | 'GPFSEL5': 0x14, # gpio function select 5 76 | 77 | # 78 | # Used to set a specific gpio pin. Setting to 0 has no effect. 79 | # 80 | 'GPSET0': 0x1c, # gpio pin output set 0 81 | 'GPSET1': 0x20, # gpio pin output set 1 82 | 83 | # 84 | # Used to clear gpio pin. Setting to 0 has no effect. 85 | # 86 | 'GPCLR0': 0x28, # gpio pin output clear 0 87 | 'GPCLR1': 0x2c, # gpio pin output clear 1 88 | 89 | # 90 | # Return value of a pin. 91 | # 92 | 'GPLEV0': 0x34, # gpio pin level 0 93 | 'GPLEV1': 0x38, # gpio pin level 1 94 | 95 | # 96 | # Used to retrieve signal edge detection results. 97 | # 98 | 'GPEDS0': 0x40, # gpio pin event detect status 0 99 | 'GPEDS1': 0x44, # gpio pin event detect status 1 100 | 'GPREN0': 0x4c, # gpio pin rising edge detect enable 0 101 | 'GPREN1': 0x50, # gpio pin rising edge detect enable 1 102 | 'GPFEN0': 0x58, # gpio pin falling edge detect enable 0 103 | 'GPFEN1': 0x5c, # gpio pin falling edge detect enable 1 104 | 'GPHEN0': 0x64, # gpio pin high detect enable 0 105 | 'GPHEN1': 0x68, # gpio pin high detect enable 1 106 | 'GPLEN0': 0x70, # gpio pin low detect enable 0 107 | 'GPLEN1': 0x74, # gpio pin low detect enable 1 108 | 'GPAREN0': 0x7c, # gpio pin async rising edge detect 0 109 | 'GPAREN1': 0x80, # gpio pin async rising edge detect 1 110 | 'GPAFEN0': 0x88, # gpio pin async falling edge detect 0 111 | 'GPAFEN1': 0x8c, # gpio ping async falling edge detect 1 112 | 113 | # 114 | # Pull-up/down config. 115 | # 116 | 'GPIO_PUP_PDN_CNTRL_REG0': 0xe4, 117 | 'GPIO_PUP_PDN_CNTRL_REG1': 0xe8, 118 | 'GPIO_PUP_PDN_CNTRL_REG2': 0xec, 119 | 'GPIO_PUP_PDN_CNTRL_REG3': 0xf0, 120 | } 121 | 122 | ######################################################################### 123 | # GPIO -- 124 | # 125 | # GPIO char dev interface class. 126 | ######################################################################### 127 | class GPIO: 128 | gpioDev = open(GPIO_DEVICE_PATH, 'r+b') 129 | def __del__(self): 130 | self.gpioDev.close() 131 | 132 | ######################################################################### 133 | # pollReg -- 134 | # 135 | # Polls a GPIO register until the masked bits have changed. Then, 136 | # returns the registers current value. 137 | ######################################################################### 138 | def pollReg(self, reg, mask): 139 | try: 140 | # 141 | # The GPIO driver's ioctl struct looks like this: 142 | # 143 | # +--------------------+ 144 | # | uint16_t offset | 145 | # +--------------------+ 146 | # | uint32_t mask/data | 147 | # +--------------------+ 148 | # | uint16_t padding | 149 | # +--------------------+ 150 | # 151 | ioctlData = bytearray(struct.pack('> shift 280 | return list(gpioPUDs.keys())[list(gpioPUDs.values()).index(key)] 281 | 282 | ######################################################################### 283 | # setPull -- 284 | # 285 | # Set pull-up/down state of a pin. 286 | ######################################################################### 287 | def setPull(self, pin, pull): 288 | pud = gpioPUDs[pull] 289 | shift = (pin % 16) * 2 290 | reg = gpioPinToPudReg[int(math.floor(pin / 16))] 291 | oldPud = self.readReg(reg) 292 | newPud = oldPud | (0b11 << shift) 293 | newPud ^= (0b11 << shift) 294 | newPud |= (pud << shift) 295 | self.writeReg(reg, newPud) -------------------------------------------------------------------------------- /pyUtil/gpio_fanShim.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | ################################################################################ 3 | # Script to test the Pimoroni FanShim LED. 4 | # 5 | # Tom Hebel, 2020 6 | ################################################################################ 7 | 8 | import sys 9 | import time 10 | import argparse 11 | from gpioLib import * 12 | 13 | ######################################################################### 14 | 15 | GPIO_FANSHIM_PIN_CLOCK = 14 16 | GPIO_FANSHIM_PIN_DATA = 15 17 | GPIO_FANSHIM_PIN_BUTTON = 17 18 | GPIO_FANSHIM_PIN_FAN = 18 19 | 20 | ######################################################################### 21 | 22 | class FanShim: 23 | gpio = GPIO() 24 | 25 | ######################################################################### 26 | # toggleFan -- 27 | # 28 | # Turn on/off the FanShim fan. 29 | ######################################################################### 30 | def toggleFan(self): 31 | self.gpio.funcSel(GPIO_FANSHIM_PIN_FAN, 'OUT') 32 | val = self.gpio.levPin(GPIO_FANSHIM_PIN_FAN) 33 | if val: 34 | self.gpio.clrPin(GPIO_FANSHIM_PIN_FAN) 35 | else: 36 | self.gpio.setPin(GPIO_FANSHIM_PIN_FAN) 37 | 38 | ######################################################################### 39 | # setLED -- 40 | # 41 | # Turn on/off the FanShim LED. 42 | ######################################################################### 43 | def setLED(self, red, green, blue): 44 | buf = [0, 0, 0, 0, 45 | 255, blue, green, red, 46 | ~0, ~0, ~0, ~0] 47 | self.gpio.funcSel(GPIO_FANSHIM_PIN_CLOCK, 'OUT') 48 | self.gpio.funcSel(GPIO_FANSHIM_PIN_DATA, 'OUT') 49 | self.gpio.clrPin(GPIO_FANSHIM_PIN_CLOCK) 50 | self.gpio.clrPin(GPIO_FANSHIM_PIN_DATA) 51 | for i in range(len(buf)): 52 | byte = buf[i] 53 | for j in range(8): 54 | bit = (byte & 0x80) > 0 55 | if bit: 56 | self.gpio.setPin(GPIO_FANSHIM_PIN_DATA) 57 | else: 58 | self.gpio.clrPin(GPIO_FANSHIM_PIN_DATA) 59 | self.gpio.setPin(GPIO_FANSHIM_PIN_CLOCK) 60 | time.sleep(0) 61 | self.gpio.clrPin(GPIO_FANSHIM_PIN_CLOCK) 62 | time.sleep(0) 63 | byte <<= 1 64 | 65 | ######################################################################### 66 | # flashLED -- 67 | # 68 | # Flash the FanShim LED. 69 | ######################################################################### 70 | def flashLED(self, red, green, blue, durationSec, intervalMs): 71 | bufOn = [0, 0, 0, 0, 72 | 255, blue, green, red, 73 | ~0, ~0, ~0, ~0] 74 | bufOff = [0, 0, 0, 0, 75 | 255, 0, 0, 0, 76 | ~0, ~0, ~0, ~0] 77 | curBuf = bufOff 78 | intervalUs = intervalMs * 1000 79 | self.gpio.funcSel(GPIO_FANSHIM_PIN_CLOCK, 'OUT') 80 | self.gpio.funcSel(GPIO_FANSHIM_PIN_DATA, 'OUT') 81 | self.gpio.clrPin(GPIO_FANSHIM_PIN_CLOCK) 82 | self.gpio.clrPin(GPIO_FANSHIM_PIN_DATA) 83 | startTime = time.monotonic() 84 | while True: 85 | curTime = time.monotonic() 86 | if curTime - startTime > durationSec: 87 | return 88 | if curBuf == bufOff: 89 | curBuf = bufOn 90 | else: 91 | curBuf = bufOff 92 | for i in range(len(curBuf)): 93 | byte = curBuf[i] 94 | for j in range(8): 95 | bit = (byte & 0x80) > 0 96 | if bit: 97 | self.gpio.setPin(GPIO_FANSHIM_PIN_DATA) 98 | else: 99 | self.gpio.clrPin(GPIO_FANSHIM_PIN_DATA) 100 | self.gpio.setPin(GPIO_FANSHIM_PIN_CLOCK) 101 | time.sleep(0) 102 | self.gpio.clrPin(GPIO_FANSHIM_PIN_CLOCK) 103 | time.sleep(0) 104 | byte <<= 1 105 | time.sleep(intervalMs / 1000) 106 | 107 | ######################################################################### 108 | # pulseLED -- 109 | # 110 | # Pulse the FanShim LED. 111 | ######################################################################### 112 | def pulseLED(self, col, durationSec, periodMs): 113 | buf = [0, 0, 0, 0, 114 | 255, 0, 0, 0, 115 | ~0, ~0, ~0, ~0] 116 | curCol = [0, 0, 0] 117 | intervalSec = (periodMs / 1000) / 256 118 | self.gpio.funcSel(GPIO_FANSHIM_PIN_CLOCK, 'OUT') 119 | self.gpio.funcSel(GPIO_FANSHIM_PIN_DATA, 'OUT') 120 | self.gpio.clrPin(GPIO_FANSHIM_PIN_CLOCK) 121 | self.gpio.clrPin(GPIO_FANSHIM_PIN_DATA) 122 | sign = 1 123 | startTime = time.monotonic() 124 | while True: 125 | curTime = time.monotonic() 126 | if curTime - startTime > durationSec: 127 | return 128 | for i in range(len(buf)): 129 | byte = buf[i] 130 | for j in range(8): 131 | bit = (byte & 0x80) > 0 132 | if bit: 133 | self.gpio.setPin(GPIO_FANSHIM_PIN_DATA) 134 | else: 135 | self.gpio.clrPin(GPIO_FANSHIM_PIN_DATA) 136 | self.gpio.setPin(GPIO_FANSHIM_PIN_CLOCK) 137 | time.sleep(0) 138 | self.gpio.clrPin(GPIO_FANSHIM_PIN_CLOCK) 139 | time.sleep(0) 140 | byte <<= 1 141 | if curCol[col] == 255: 142 | sign = -1 143 | elif curCol[col] == 0: 144 | sign = 1 145 | curCol[col] += sign 146 | buf[5] = curCol[0] 147 | buf[6] = curCol[1] 148 | buf[7] = curCol[2] 149 | time.sleep(0) 150 | 151 | ######################################################################### 152 | # gradientLED -- 153 | # 154 | # Smoothly transition from blue to green to red to blue. 155 | ######################################################################### 156 | def gradientLED(self, durationSec, periodMs): 157 | buf = [0, 0, 0, 0, 158 | 255, 0, 0, 0, 159 | ~0, ~0, ~0, ~0] 160 | intervalSec = (periodMs / 1000) / 256 161 | col = [255, 0, 0] 162 | self.gpio.funcSel(GPIO_FANSHIM_PIN_CLOCK, 'OUT') 163 | self.gpio.funcSel(GPIO_FANSHIM_PIN_DATA, 'OUT') 164 | self.gpio.clrPin(GPIO_FANSHIM_PIN_CLOCK) 165 | self.gpio.clrPin(GPIO_FANSHIM_PIN_DATA) 166 | k = 1 167 | startTime = time.monotonic() 168 | while True: 169 | curTime = time.monotonic() 170 | if curTime - startTime > durationSec: 171 | return 172 | for i in range(len(buf)): 173 | byte = buf[i] 174 | for j in range(8): 175 | bit = (byte & 0x80) > 0 176 | if bit: 177 | self.gpio.setPin(GPIO_FANSHIM_PIN_DATA) 178 | else: 179 | self.gpio.clrPin(GPIO_FANSHIM_PIN_DATA) 180 | self.gpio.setPin(GPIO_FANSHIM_PIN_CLOCK) 181 | time.sleep(0) 182 | self.gpio.clrPin(GPIO_FANSHIM_PIN_CLOCK) 183 | time.sleep(0) 184 | byte <<= 1 185 | if col[k] == 255: 186 | k = (k + 1) % 3 187 | if k == 0: 188 | col[2] -= 1 189 | col[0] += 1 190 | elif k == 1: 191 | col[0] -= 1 192 | col[1] += 1 193 | elif k == 2: 194 | col[1] -= 1 195 | col[2] += 1 196 | buf[5] = col[0] 197 | buf[6] = col[1] 198 | buf[7] = col[2] 199 | time.sleep(0) 200 | 201 | ######################################################################### 202 | # printBtnChng -- 203 | # 204 | # Prints changes to the button state (pressed or released). 205 | ######################################################################### 206 | def printBtnChng(self): 207 | self.gpio.funcSel(GPIO_FANSHIM_PIN_BUTTON, 'IN') 208 | self.gpio.setPull(GPIO_FANSHIM_PIN_BUTTON, 'UP') 209 | time.sleep(0.1) 210 | prev = self.gpio.levPin(GPIO_FANSHIM_PIN_BUTTON) 211 | while True: 212 | cur = self.gpio.pollPin(GPIO_FANSHIM_PIN_BUTTON, prev) 213 | if cur != prev: 214 | # Don't use an if/elif block here; Python doesn't handle changes 215 | # to the button state properly then. 216 | sys.stdout.write('Button pressed\n' if cur < prev \ 217 | else 'Button released\n') 218 | sys.stdout.flush() 219 | prev = cur 220 | time.sleep(0) 221 | 222 | ######################################################################### 223 | # main -- 224 | # 225 | # Main entry point. 226 | ######################################################################### 227 | def main(argv): 228 | fanShim = FanShim() 229 | progDesc = ('Utility for controlling the Pimoroni FanShim.' 230 | '\n\nCommands:' 231 | '\nfan' 232 | '\nbutton' 233 | '\nset\t ' 234 | '\nflash\t ' 235 | '\npulse\t<[red|green|blue]>' 236 | '\ngradient' 237 | '\n\nAll colors 0-255.') 238 | parser = argparse.ArgumentParser(prog='gpio', 239 | description=progDesc, 240 | formatter_class=argparse.RawTextHelpFormatter) 241 | parser.add_argument('command', help='interact with the FanShim accessory') 242 | parser.add_argument('args', 243 | nargs='*', 244 | help='arguments for the command') 245 | args = parser.parse_args() 246 | if args.command == 'fan': 247 | fanShim.toggleFan() 248 | elif args.command == 'set': 249 | try: 250 | red = int(args.args[0]) 251 | green = int(args.args[1]) 252 | blue = int(args.args[2]) 253 | except Exception as e: 254 | print('Invalid color: {}'.format(str(e))) 255 | exit(1) 256 | fanShim.setLED(red, green, blue) 257 | elif args.command == 'flash': 258 | try: 259 | red = int(args.args[0]) 260 | green = int(args.args[1]) 261 | blue = int(args.args[2]) 262 | except Exception as e: 263 | print('Invalid color: {}'.format(str(e))) 264 | exit(1) 265 | fanShim.flashLED(red, green, blue, 5, 500) 266 | fanShim.setLED(0, 0, 0) 267 | elif args.command == 'pulse': 268 | arg = args.args[0] 269 | cols = ['blue', 'green', 'red'] 270 | if arg not in cols: 271 | print('Invalid color: {}'.format(col)) 272 | print('Must be one of: red, green, blue') 273 | exit(1) 274 | col = cols.index(arg) 275 | fanShim.pulseLED(col, 5, 500) 276 | fanShim.setLED(0, 0, 0) 277 | elif args.command == 'gradient': 278 | fanShim.gradientLED(8, 2000) 279 | fanShim.setLED(0, 0, 0) 280 | elif args.command == 'button': 281 | fanShim.printBtnChng() 282 | 283 | if __name__ == '__main__': 284 | main(sys.argv) -------------------------------------------------------------------------------- /pyUtil/gpio_util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | ################################################################################ 3 | # UW interface to the GPIO MMIO area. 4 | # 5 | # Tom Hebel, 2020 6 | ################################################################################ 7 | 8 | import sys 9 | import argparse 10 | import time 11 | from gpioLib import * 12 | 13 | ######################################################################### 14 | 15 | class GPIOCommands: 16 | gpio = GPIO() 17 | 18 | ######################################################################### 19 | # infoCmd -- 20 | # 21 | # Get information about the GPIO interface, such as the MMiO area size. 22 | ######################################################################### 23 | def infoCmd(self, args): 24 | print('\nAll registers are 4 bytes long.') 25 | print('All pins are either set (1) or cleared (0).') 26 | print(('\nRegisters:\n' 27 | + '\n'.join([x for x in gpioRegisters.keys()]))) 28 | print(('\nFunction Selectors:\n' 29 | + '\n'.join([x for x in gpioFuncSelectors.keys()]))) 30 | print(('\nPull-up/down States:\n' 31 | + '\n'.join([x for x in gpioPUDs.keys()]))) 32 | 33 | ######################################################################### 34 | # readRegCmd -- 35 | # 36 | # Command func for reading register value. 37 | ######################################################################### 38 | def readRegCmd(self, subParsers, argv): 39 | parser = subParsers.add_parser('gpio read', 40 | help=('Read the value of a GPIO' 41 | ' register')) 42 | parser.add_argument('register', 43 | nargs=1, 44 | type=str, 45 | help='the GPIO register to read') 46 | args = parser.parse_args(argv) 47 | reg = str(args.register[0]) 48 | if reg not in gpioRegisters.keys(): 49 | print('Invalid register: {}'.format(reg)) 50 | exit(1) 51 | print(self.gpio.readReg(reg)) 52 | 53 | ######################################################################### 54 | # writeRegCmd -- 55 | # 56 | # Command func for writing register value. 57 | ######################################################################### 58 | def writeRegCmd(self, subParsers, argv): 59 | parser = subParsers.add_parser('gpio write', 60 | help=('Write 4 bytes to a GPIO' 61 | ' register')) 62 | parser.add_argument('register', 63 | nargs=1, 64 | type=str, 65 | help='the GPIO register to write to') 66 | parser.add_argument('value', 67 | nargs=1, 68 | type=int, 69 | help='the 4-byte value to write') 70 | args = parser.parse_args(argv) 71 | reg = str(args.register[0]) 72 | val = int(args.value[0]) 73 | if val > GPIO_INT_MAX: 74 | print('Value doesn\'t fit into 4 bytes: {}'.format(val)) 75 | exit(1) 76 | if reg not in gpioRegisters.keys(): 77 | print('Invalid register: {}'.format(reg)) 78 | exit(1) 79 | self.gpio.writeReg(reg, val) 80 | 81 | ######################################################################### 82 | # funcSelCmd -- 83 | # 84 | # Command func for setting the function of a GPIO pin. 85 | ######################################################################### 86 | def funcSelCmd(self, subParsers, argv): 87 | parser = subParsers.add_parser('gpio fsel', 88 | help=('sets the function of a GPIO' 89 | ' pin')) 90 | parser.add_argument('pin', 91 | nargs=1, 92 | type=int, 93 | help='the GPIO pin') 94 | parser.add_argument('func', 95 | nargs=1, 96 | type=str, 97 | help='the function to select') 98 | args = parser.parse_args(argv) 99 | pin = int(args.pin[0]) 100 | func = str(args.func[0]) 101 | if pin > GPIO_MAX_PIN: 102 | print('Invalid pin: {}'.format(pin)) 103 | exit(1) 104 | if func not in gpioFuncSelectors.keys(): 105 | print('Invalid function: {}'.format(func)) 106 | exit(1) 107 | self.gpio.funcSel(pin, func) 108 | 109 | ######################################################################### 110 | # setPinCmd -- 111 | # 112 | # Command func for setting a GPIO pin to 1. 113 | ######################################################################### 114 | def setPinCmd(self, subParsers, argv): 115 | parser = subParsers.add_parser('gpio set', 116 | help=('sets a GPIO pin to 1')) 117 | parser.add_argument('pin', 118 | nargs=1, 119 | type=int, 120 | help='the GPIO pin') 121 | args = parser.parse_args(argv) 122 | pin = int(args.pin[0]) 123 | if pin > GPIO_MAX_PIN: 124 | print('Invalid pin: {}'.format(pin)) 125 | exit(1) 126 | self.gpio.setPin(pin) 127 | 128 | ######################################################################### 129 | # clrPinCmd -- 130 | # 131 | # Command func for clearing a GPIO pin. 132 | ######################################################################### 133 | def clrPinCmd(self, subParsers, argv): 134 | parser = subParsers.add_parser('clear', 135 | help=('clears a GPIO pin')) 136 | parser.add_argument('pin', 137 | nargs=1, 138 | type=int, 139 | help='the GPIO pin') 140 | args = parser.parse_args(argv) 141 | pin = int(args.pin[0]) 142 | if pin > GPIO_MAX_PIN: 143 | print('Invalid pin: {}'.format(pin)) 144 | exit(1) 145 | self.gpio.clrPin(pin) 146 | 147 | ######################################################################### 148 | # levPinCmd -- 149 | # 150 | # Command func for getting the level of a GPIO pin. 151 | ######################################################################### 152 | def levPinCmd(self, subParsers, argv): 153 | parser = subParsers.add_parser('level', 154 | help=('gets the level of a GPIO' 155 | ' pin')) 156 | parser.add_argument('pin', 157 | nargs=1, 158 | type=int, 159 | help='the GPIO pin') 160 | args = parser.parse_args(argv) 161 | pin = int(args.pin[0]) 162 | if pin > GPIO_MAX_PIN: 163 | print('Invalid pin: {}'.format(pin)) 164 | exit(1) 165 | print(self.gpio.levPin(pin)) 166 | 167 | ######################################################################### 168 | # pollPinCmd -- 169 | # 170 | # Command func for getting the level of a GPIO pin. 171 | ######################################################################### 172 | def pollPinCmd(self, subParsers, argv): 173 | parser = subParsers.add_parser('poll', 174 | help=('polls the level of a GPIO' 175 | ' pin until it changes')) 176 | parser.add_argument('pin', 177 | nargs=1, 178 | type=int, 179 | help='the GPIO pin') 180 | args = parser.parse_args(argv) 181 | pin = int(args.pin[0]) 182 | if pin > GPIO_MAX_PIN: 183 | print('Invalid pin: {}'.format(pin)) 184 | exit(1) 185 | prev = self.gpio.levPin(pin) 186 | while True: 187 | cur = self.gpio.pollPin(pin, prev) 188 | if cur != prev: 189 | print(cur) 190 | break 191 | prev = cur 192 | time.sleep(0) 193 | 194 | ######################################################################### 195 | # getPullCmd -- 196 | # 197 | # Command func for getting the pull of a GPIO pin. 198 | ######################################################################### 199 | def getPullCmd(self, subParsers, argv): 200 | parser = subParsers.add_parser('getpull', 201 | help=('gets the pull of a GPIO' 202 | ' pin')) 203 | parser.add_argument('pin', 204 | nargs=1, 205 | type=int, 206 | help='the GPIO pin') 207 | args = parser.parse_args(argv) 208 | pin = int(args.pin[0]) 209 | if pin > GPIO_MAX_PIN: 210 | print('Invalid pin: {}'.format(pin)) 211 | exit(1) 212 | print(self.gpio.getPull(pin)) 213 | 214 | ######################################################################### 215 | # setPullCmd -- 216 | # 217 | # Command func for setting the pull of a GPIO pin. 218 | ######################################################################### 219 | def setPullCmd(self, subParsers, argv): 220 | parser = subParsers.add_parser('setpull', 221 | help=('sets the pull of a' 222 | 'GPIO pin')) 223 | parser.add_argument('pin', 224 | nargs=1, 225 | type=int, 226 | help='the GPIO pin') 227 | parser.add_argument('pull', 228 | nargs=1, 229 | type=str, 230 | help='the pull to apply (UP, DN, OFF)') 231 | args = parser.parse_args(argv) 232 | pin = int(args.pin[0]) 233 | pull = str(args.pull[0]) 234 | if pin > GPIO_MAX_PIN: 235 | print('Invalid pin: {}'.format(pin)) 236 | exit(1) 237 | if pull not in gpioPUDs.keys(): 238 | print('Invalid pull value: {}'.format(pull)) 239 | exit(1) 240 | self.gpio.setPull(pin, pull) 241 | 242 | ######################################################################### 243 | # main -- 244 | # 245 | # Main entry point. 246 | ######################################################################### 247 | def main(argv): 248 | cmdsObj = GPIOCommands() 249 | commands = { 250 | 'info': cmdsObj.infoCmd, 251 | 'read': cmdsObj.readRegCmd, 252 | 'write': cmdsObj.writeRegCmd, 253 | 'fsel': cmdsObj.funcSelCmd, 254 | 'set': cmdsObj.setPinCmd, 255 | 'clear': cmdsObj.clrPinCmd, 256 | 'level': cmdsObj.levPinCmd, 257 | 'poll': cmdsObj.pollPinCmd, 258 | 'getpull': cmdsObj.getPullCmd, 259 | 'setpull': cmdsObj.setPullCmd, 260 | } 261 | progDesc = ('Wrapper for the GPIO device driver.' 262 | '\nFor more information, check out the documenation for the' 263 | ' RPi 4B\'s system on a chip, BCM2711:' 264 | '\nhttps://www.raspberrypi.org/documentation/hardware/' 265 | 'raspberrypi/bcm2711/rpi_DATA_2711_1p0.pdf' 266 | '\n\nRun `./self.gpio.py info` to get information about the GPIO' 267 | ' interface.' 268 | '\n\nAll registers are 4 bytes long.' 269 | '\n\nCommands:' 270 | '\nread\t' 271 | '\nwrite\t ' 272 | '\nfsel\t ' 273 | '\nset\t' 274 | '\nclear\t' 275 | '\nlevel\t' 276 | '\npoll\t ' 277 | '\ngetpull\t' 278 | '\nsetpull\t ') 279 | parser = argparse.ArgumentParser(prog='gpio', 280 | description=progDesc, 281 | formatter_class=argparse.RawTextHelpFormatter) 282 | parser.add_argument('command', help='interact with the GPIO interface') 283 | parser.add_argument('args', 284 | nargs='*', 285 | help='arguments for the command') 286 | args = parser.parse_args() 287 | if args.command not in commands.keys(): 288 | print('Invalid command: {}'.format(args.command)) 289 | exit(1) 290 | subParsers = parser.add_subparsers() 291 | # Call the command func 292 | commands[args.command](subParsers, args.args) 293 | 294 | if __name__ == '__main__': 295 | main(sys.argv) --------------------------------------------------------------------------------