pax_global_header00006660000000000000000000000064146656272560014535gustar00rootroot0000000000000052 comment=d4f8fb543a892ba65bfc608511bf582c0425aa81 raspi-utils-20240903/000077500000000000000000000000001466562725600142565ustar00rootroot00000000000000raspi-utils-20240903/.gitignore000066400000000000000000000010711466562725600162450ustar00rootroot00000000000000# # NOTE! Don't add files that are generated in specific # subdirectories here. Add them in the ".gitignore" file # in that subdirectory instead. # # NOTE! Please use 'git ls-files -i --exclude-standard' # command after changing this file, to see if there are # any tracked files which get ignored after the change. # # Normal rules (sorted alphabetically) # .* *.a *.bin *.bz2 *.c.[012]*.* *.dtb* *.dtb.S *.dwo *.elf *.exe *.gcno *.gz *.i *.ko *.ll *.lst *.lz4 *.lzma *.lzo *.mod.c *.o *.o.* *.order *.orig *.patch *.s *.so *.so.dbg *.su *.symtypes *.tar *.xz *~ \#*# raspi-utils-20240903/CMakeLists.txt000066400000000000000000000005471466562725600170240ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.1...3.27) project(utils) # List of subsidiary CMakeLists add_subdirectory(dtmerge) add_subdirectory(eeptools) add_subdirectory(otpset) add_subdirectory(overlaycheck) add_subdirectory(ovmerge) add_subdirectory(pinctrl) add_subdirectory(raspinfo) add_subdirectory(vcgencmd) add_subdirectory(vclog) add_subdirectory(vcmailbox) raspi-utils-20240903/LICENCE000066400000000000000000000027651466562725600152550ustar00rootroot00000000000000Copyright (c) 2023, Raspberry Pi Ltd. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. raspi-utils-20240903/README.md000066400000000000000000000027131466562725600155400ustar00rootroot00000000000000# utils A collection of scripts and simple applications * [dtmerge](dtmerge/) - A tool for applying compiled DT overlays (`*.dtbo`) to base Device Tree files (`*.dtb`). Also includes the `dtoverlay` and `dtparam` utilities. * [eeptools](eeptools/) - Tools for creating and managing EEPROMs for HAT+ and HAT board. * [otpset](otpset/) - A short script to help with reading and setting the customer OTP bits. * [overlaycheck](overlaycheck/) - A tool for validating the overlay files and README in a kernel source tree. * [ovmerge](ovmerge/) - A tool for merging DT overlay source files (`*-overlay.dts`), flattening and sorting `.dts` files for easy comparison, displaying the include tree, etc. * [pinctrl](pinctrl/) - A more powerful replacement for raspi-gpio, a tool for displaying and modifying the GPIO and pin muxing state of a system, bypassing the kernel. * [raspinfo](raspinfo/) - A short script to dump information about the Pi. Intended for the submission of bug reports. * [vclog](vclog/) - A tool to get VideoCore 'assert' or 'msg' logs with optional -f to wait for new logs to arrive. **Build Instructions** Install the prerequisites with "sudo apt install cmake device-tree-compiler libfdt-dev" - you need at least version 3.10 of cmake. Run the following commands to build and install everything, or see the README files in the subdirectories to just build utilities individually: - *cmake .* - *make* - *sudo make install* raspi-utils-20240903/dtmerge/000077500000000000000000000000001466562725600157055ustar00rootroot00000000000000raspi-utils-20240903/dtmerge/CMakeLists.txt000066400000000000000000000017421466562725600204510ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.10...3.27) include(GNUInstallDirs) #set project name project(dtmerge) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror") if (CMAKE_COMPILER_IS_GNUCC) add_definitions (-ffunction-sections) endif () add_library (dtovl ${STATIC} dtoverlay.c) target_link_libraries(dtovl fdt) add_executable(dtmerge dtmerge.c) target_link_libraries(dtmerge dtovl) install(TARGETS dtmerge RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES dtmerge.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) add_executable(dtoverlay dtoverlay_main.c utils.c) target_link_libraries(dtoverlay dtovl) install(TARGETS dtoverlay RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES dtoverlay.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) add_custom_command(TARGET dtoverlay POST_BUILD COMMAND ln;-sf;dtoverlay;dtparam) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/dtparam DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES dtparam.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) raspi-utils-20240903/dtmerge/README.md000066400000000000000000000057521466562725600171750ustar00rootroot00000000000000 # dtmerge, dtoverlay and dtparam dtmerge is a tool for applying pre-compiled overlays (`*.dtbo` files) to a base Device Tree file (`*.dtb`). dtoverlay is a tool for applying pre-compiled overlays to a live system. dtparam is a tool for applying paramaters of the base Device Tree of a live system. **Build Instructions** Install the prerequisites with "sudo apt install cmake libfdt-dev" - you need at least version 3.10 of cmake. Run the following commands here, or in the top-level directory to build and install all the utilities: - *cmake .* - *make* - *sudo make install* **Usage** ``` Usage: dtmerge [ - [param=value] ... to apply a parameter to the base dtb (like dtparam) dtmerge [ [param=value] ... to apply an overlay with parameters (like dtoverlay) where is any of: -d Enable debug output -h Show this help message ``` ``` Usage: dtoverlay [=...] Add an overlay (with parameters) dtoverlay -D Dry-run (prepare overlay, but don't apply - save it as dry-run.dtbo) dtoverlay -r [] Remove an overlay (by name, index or the last) dtoverlay -R [] Remove from an overlay (by name, index or all) dtoverlay -l List active overlays/params dtoverlay -a List all overlays (marking the active) dtoverlay -h Show this usage message dtoverlay -h Display help on an overlay dtoverlay -h .. Or its parameters where is the name of an overlay or 'dtparam' for dtparams Options applicable to most variants: -d Specify an alternate location for the overlays (defaults to /boot/overlays or /flash/overlays) -p Force a compatible string for the platform -v Verbose operation Adding or removing overlays and parameters requires root privileges. ``` ``` Usage: dtparam Display help on all parameters dtparam =... Add an overlay (with parameters) dtparam -D Dry-run (prepare overlay, but don't apply - save it as dry-run.dtbo) dtparam -r [] Remove an overlay (by index, or the last) dtparam -R [] Remove from an overlay (by index, or all) dtparam -l List active overlays/dtparams dtparam -a List all overlays/dtparams (marking the active) dtparam -h Show this usage message dtparam -h ... Display help on the listed parameters Options applicable to most variants: -d Specify an alternate location for the overlays (defaults to /boot/overlays or /flash/overlays) -p Force a compatible string for the platform -v Verbose operation Adding or removing overlays and parameters requires root privileges. ``` raspi-utils-20240903/dtmerge/dtmerge.1000066400000000000000000000027161466562725600174240ustar00rootroot00000000000000.TH DTMERGE 1 . .SH NAME dtmerge \- merge an overlay or parameters into a base device-tree . . .SH SYNOPSIS .SY dtmerge .OP \-d .I base-dtb .I merged-dtb .I overlay-dtb .RI [ param=val \|.\|.\|.] .YS . .SY dtmerge .B \-h .YS . . .SH DESCRIPTION .B dtmerge is a command line utility for merging overlays and/or device-tree parameters with a base device-tree. See .B [DTREE] for more information. . .PP The base device-tree (in dtb format) is always specified as the first (non-option) argument. The second argument is the output filename, which will also be in dtb format. The third argument provides the name of the overlay to merge into the base tree. If this is "-" then no overlay is used and the utility will simply customize the base tree with any parameters given. . . .SH OPTIONS . .TP .BR \-d Show debug output during operation. . .TP .BR \-h Displays help on the application. . . .SH EXAMPLES . .TP .B dtmerge /boot/bcm2711-rpi-4-b.dtb out.dtb - spi=on i2c=on Produce a device-tree for the Raspberry Pi 4 in "out.dtb" which has the SPI and I2C interfaces activated. . .TP .B dtmerge /boot/bcm2710-rpi-3-b-plus.dtb out.dtb /boot/overlays/gpio-shutdown.dtbo Produce a device-tree for the Raspberry Pi 3+ in "out.dtb" which includes the GPIO shutdown overlay (with all parameters set to their default). . . .SH SEE ALSO .BR dtoverlay (1), .BR dtparam (1), .B [DTREE] . . .SH REFERENCES .TP .B [DTREE] https://www.raspberrypi.com/documentation/computers/configuration.html#part5.2 raspi-utils-20240903/dtmerge/dtmerge.c000066400000000000000000000141001466562725600174740ustar00rootroot00000000000000/* Copyright (c) 2016 Raspberry Pi (Trading) Ltd. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include "dtoverlay.h" static void usage(void) { printf("Usage:\n"); printf(" dtmerge [ - [param=value] ...\n"); printf(" to apply a parameter to the base dtb (like dtparam)\n"); printf(" dtmerge [ [param=value] ...\n"); printf(" to apply an overlay with parameters (like dtoverlay)\n"); printf(" where is any of:\n"); printf(" -d Enable debug output\n"); printf(" -h Show this help message\n"); exit(1); } int main(int argc, char **argv) { const char *base_file; const char *merged_file; const char *overlay_file; const char *compatible; char *overlay_dir; char *p; DTBLOB_T *base_dtb; DTBLOB_T *overlay_dtb; int err = 0; int argn = 1; int max_dtb_size = 200000; int compatible_len; while ((argn < argc) && (argv[argn][0] == '-')) { const char *arg = argv[argn++]; if ((strcmp(arg, "-d") == 0) || (strcmp(arg, "--debug") == 0)) dtoverlay_enable_debug(1); else if ((strcmp(arg, "-h") == 0) || (strcmp(arg, "--help") == 0)) usage(); else { printf("* Unknown option '%s'\n", arg); usage(); } } if (argc < (argn + 3)) { usage(); } base_file = argv[argn++]; merged_file = argv[argn++]; overlay_file = argv[argn++]; base_dtb = dtoverlay_load_dtb(base_file, max_dtb_size); if (!base_dtb) { printf("* failed to load '%s'\n", base_file); return -1; } if (strnlen(overlay_file, DTOVERLAY_MAX_PATH) == DTOVERLAY_MAX_PATH) { printf("* overlay filename too long\n"); return -1; } overlay_dir = strdup(overlay_file); p = strrchr(overlay_dir, '/'); if (p) *p = 0; else overlay_dir = "."; compatible = dtoverlay_get_property(base_dtb, dtoverlay_find_node(base_dtb, "/", 1), "compatible", &compatible_len); dtoverlay_init_map(overlay_dir, compatible, compatible_len); if (!dtoverlay_get_alias(base_dtb, "i2c")) { err = dtoverlay_set_synonym(base_dtb, "i2c_arm", "i2c0"); err = dtoverlay_set_synonym(base_dtb, "i2c_vc", "i2c1"); err = dtoverlay_set_synonym(base_dtb, "i2c_baudrate", "i2c0_baudrate"); err = dtoverlay_set_synonym(base_dtb, "i2c_arm_baudrate", "i2c0_baudrate"); err = dtoverlay_set_synonym(base_dtb, "i2c_vc_baudrate", "i2c1_baudrate"); }; if (strcmp(overlay_file, "-") == 0) { overlay_dtb = base_dtb; } else { char new_file[DTOVERLAY_MAX_PATH]; char *overlay_name; const char *new_name; char *p; int len; strcpy(new_file, overlay_file); overlay_name = strrchr(new_file, '/'); if (overlay_name) overlay_name++; else overlay_name = new_file; p = strrchr(overlay_name, '.'); if (p) *p = 0; new_name = dtoverlay_remap_overlay(overlay_name); if (new_name) { if (strcmp(overlay_name, new_name)) dtoverlay_debug("mapped overlay '%s' to '%s'", overlay_name, new_name); len = strlen(new_name); memmove(overlay_name, new_name, len); strcpy(overlay_name + len, ".dtbo"); overlay_dtb = dtoverlay_load_dtb(new_file, max_dtb_size); if (overlay_dtb) err = dtoverlay_fixup_overlay(base_dtb, overlay_dtb); else err = -1; } else { overlay_dtb = NULL; err = -2; } } while (!err && (argn < argc)) { char *param_name = argv[argn++]; char *param_value = param_name + strcspn(param_name, "="); const void *override_data; int data_len; if (*param_value == '=') { *(param_value++) = '\0'; } else { /* This isn't a well-formed parameter assignment, but it can be treated as an assignment of true. */ param_value = "true"; } override_data = dtoverlay_find_override(overlay_dtb, param_name, &data_len); if (override_data) { err = dtoverlay_apply_override(overlay_dtb, param_name, override_data, data_len, param_value); } else { override_data = dtoverlay_find_override(base_dtb, param_name, &data_len); if (override_data) { err = dtoverlay_apply_override(base_dtb, param_name, override_data, data_len, param_value); } else { printf("* unknown param '%s'\n", param_name); err = data_len; } } } if (!err && (overlay_dtb != base_dtb)) { err = dtoverlay_merge_overlay(base_dtb, overlay_dtb); dtoverlay_free_dtb(overlay_dtb); } if (!err) { dtoverlay_pack_dtb(base_dtb); err = dtoverlay_save_dtb(base_dtb, merged_file); } dtoverlay_free_dtb(base_dtb); return err; } raspi-utils-20240903/dtmerge/dtoverlay.1000066400000000000000000000066571466562725600200160ustar00rootroot00000000000000.TH DTOVERLAY 1 . .SH NAME dtoverlay \- load a device-tree overlay . . .SH SYNOPSIS .SY dtoverlay .OP \-d dir .OP \-p string .OP \-v .OP \-D .OP \-r .OP \-R .OP \-l .OP \-a .RI [ overlay " [" param=val \|.\|.\|.]] .YS . .SY dtoverlay .B \-h .RI [ overlay " [" param ]] .YS . . .SH DESCRIPTION .B dtoverlay is a command line utility for manipulating the system's runtime device-tree by adding or removing overlays. It can also customize the base device-tree or overlays by setting parameters within them. See .B [DTREE] for more information. . .PP By default, without the .B -r or .B -R options, the application adds the specified overlay. Manipulation of active device-tree overlays usually requires root privileges. . . .SH OPTIONS . .TP .BR \-a Lists all defined device-tree overlays, placing a "*" next to those that are currently loaded. . .TP .BR \-d " \fIdir\fR" Specifies an alternate location path from which to load overlays. The utility defaults to "/boot/overlays" or "/flash/overlays". . .TP .BR \-D Dry-run; prepares the specified overlay but doesn't apply it. The resulting prepared overlay is saved as "dry-run.dtbo". . .TP .BR \-h [\fIoverlay\fR] [\fIparam\fR] If given without .I overlay or .I param displays help on the application overall. If .I overlay is specified, prints help on that overlay. Finally, if .I param is also specified, prints help on that parameter of the overlay. To query parameters on the base overlay, specify "dtparam" as the .IR overlay . . .TP .BR \-l Lists all currently loaded device-tree overlays, in the order they were loaded. This also specifies the index of each overlay. . .TP .BR \-p " \fIstring\fR" Override the platform's "compatible" string, normally read from "/proc/device-tree/compatible". This is used with the overlay map to determine which platform-specific overlay to read (if necessary). . .TP .BR \-r Remove overlay. With this option, the utility will remove the specified overlay. If no overlay is given, the last overlay loaded will be removed. Overlays can be specified by name or by index. . .TP .BR \-R Remove the specified overlay, and all subsequent overlays that were loaded after it. If no overlay is given, all overlays will be removed. Overlays can be specified by name or by index. . .TP .BR \-v Verbose operation; the utility will produce more output. . . .SH EXAMPLES . .TP .B sudo dtoverlay w1-gpio Load the w1-gpio overlay (to enable Dallas 1-Wire on GPIO4). Note that root privileges are usually required for manipulating the device-tree overlays (hence, sudo in the example). . .TP .B dtoverlay -l Show the loaded overlays. . .TP .B sudo dtoverlay -r Remove the last loaded overlay. . .TP .B sudo dtoverlay gpio-shutdown gpio_pin=7 active_low=0 gpio_pull=down Load the gpio-shutdown overlay specifying that it should pull GPIO7 down and monitor it for a rising edge to initiate shutdown. . .TP .B dtoverlay -h gpio-shutdown Display help for the gpio-shutdown overlay . .TP .B sudo dtoverlay -r gpio-shutdown Remove the gpio-shutdown overlay, wherever it is in the load order. . . .SH HOOKS .B dtoverlay attempts to call two executables (shell scripts by default) during its operation: .B dtoverlay-pre prior to making device-tree modifications, and .B dtoverlay-post afterwards. Each executable is optional and will be ignored if not present. . . .SH SEE ALSO .BR dtparam (1), .BR dtmerge (1), .B [DTREE] . . .SH REFERENCES .TP .B [DTREE] https://www.raspberrypi.com/documentation/computers/configuration.html#part3.5.1 raspi-utils-20240903/dtmerge/dtoverlay.2000066400000000000000000000066571466562725600200170ustar00rootroot00000000000000.TH DTOVERLAY 1 . .SH NAME dtoverlay \- load a device-tree overlay . . .SH SYNOPSIS .SY dtoverlay .OP \-d dir .OP \-p string .OP \-v .OP \-D .OP \-r .OP \-R .OP \-l .OP \-a .RI [ overlay " [" param=val \|.\|.\|.]] .YS . .SY dtoverlay .B \-h .RI [ overlay " [" param ]] .YS . . .SH DESCRIPTION .B dtoverlay is a command line utility for manipulating the system's runtime device-tree by adding or removing overlays. It can also customize the base device-tree or overlays by setting parameters within them. See .B [DTREE] for more information. . .PP By default, without the .B -r or .B -R options, the application adds the specified overlay. Manipulation of active device-tree overlays usually requires root privileges. . . .SH OPTIONS . .TP .BR \-a Lists all defined device-tree overlays, placing a "*" next to those that are currently loaded. . .TP .BR \-d " \fIdir\fR" Specifies an alternate location path from which to load overlays. The utility defaults to "/boot/overlays" or "/flash/overlays". . .TP .BR \-D Dry-run; prepares the specified overlay but doesn't apply it. The resulting prepared overlay is saved as "dry-run.dtbo". . .TP .BR \-h [\fIoverlay\fR] [\fIparam\fR] If given without .I overlay or .I param displays help on the application overall. If .I overlay is specified, prints help on that overlay. Finally, if .I param is also specified, prints help on that parameter of the overlay. To query parameters on the base overlay, specify "dtparam" as the .IR overlay . . .TP .BR \-l Lists all currently loaded device-tree overlays, in the order they were loaded. This also specifies the index of each overlay. . .TP .BR \-p " \fIstring\fR" Override the platform's "compatible" string, normally read from "/proc/device-tree/compatible". This is used with the overlay map to determine which platform-specific overlay to read (if necessary). . .TP .BR \-r Remove overlay. With this option, the utility will remove the specified overlay. If no overlay is given, the last overlay loaded will be removed. Overlays can be specified by name or by index. . .TP .BR \-R Remove the specified overlay, and all subsequent overlays that were loaded after it. If no overlay is given, all overlays will be removed. Overlays can be specified by name or by index. . .TP .BR \-v Verbose operation; the utility will produce more output. . . .SH EXAMPLES . .TP .B sudo dtoverlay w1-gpio Load the w1-gpio overlay (to enable Dallas 1-Wire on GPIO4). Note that root privileges are usually required for manipulating the device-tree overlays (hence, sudo in the example). . .TP .B dtoverlay -l Show the loaded overlays. . .TP .B sudo dtoverlay -r Remove the last loaded overlay. . .TP .B sudo dtoverlay gpio-shutdown gpio_pin=7 active_low=0 gpio_pull=down Load the gpio-shutdown overlay specifying that it should pull GPIO7 down and monitor it for a rising edge to initiate shutdown. . .TP .B dtoverlay -h gpio-shutdown Display help for the gpio-shutdown overlay . .TP .B sudo dtoverlay -r gpio-shutdown Remove the gpio-shutdown overlay, wherever it is in the load order. . . .SH HOOKS .B dtoverlay attempts to call two executables (shell scripts by default) during its operation: .B dtoverlay-pre prior to making device-tree modifications, and .B dtoverlay-post afterwards. Each executable is optional and will be ignored if not present. . . .SH SEE ALSO .BR dtparam (1), .BR dtmerge (1), .B [DTREE] . . .SH REFERENCES .TP .B [DTREE] https://www.raspberrypi.com/documentation/computers/configuration.html#part3.5.1 raspi-utils-20240903/dtmerge/dtoverlay.c000066400000000000000000002764521466562725600201020ustar00rootroot00000000000000/* Copyright (c) 2016-2023 Raspberry Pi Ltd. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include "dtoverlay.h" #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) #define UNUSED(x) (void)(x) typedef enum { FIXUP_ABSOLUTE, FIXUP_RELATIVE } fixup_type_t; #define DTOVERRIDE_END 0 #define DTOVERRIDE_INTEGER 1 #define DTOVERRIDE_BOOLEAN 2 #define DTOVERRIDE_BOOLEAN_INV 3 #define DTOVERRIDE_STRING 4 #define DTOVERRIDE_OVERLAY 5 #define DTOVERRIDE_BYTE_STRING 6 static int dtoverlay_extract_override(const char *override_name, char *override_value, int value_size, int *phandle_ptr, const char **datap, const char *dataendp, const char **namep, int *namelenp, int *offp, int *sizep); static const char *dtoverlay_lookup_key(const char *lookup_string, const char *data_end, const char *key, char *buf, int buf_len); static int dtoverlay_set_node_name(DTBLOB_T *dtb, int node_off, const char *name); static void dtoverlay_stdio_logging(dtoverlay_logging_type_t type, const char *fmt, va_list args); #define phandle_debug if (0) dtoverlay_debug static DTOVERLAY_LOGGING_FUNC *dtoverlay_logging_func = dtoverlay_stdio_logging; static int dtoverlay_debug_enabled = 0; static DTBLOB_T *overlay_map; static const char *platform_name; static int platform_name_len; static void (*cell_changed_callback)(DTBLOB_T *, int, const char *, int, int); static void (*intra_fragment_merged_callback)(DTBLOB_T *, int, int); static const void *override_data_start; static const void *cell_source; static int strmemcmp(const char *mem, int mem_len, const char *str) { int ret = strncmp(mem, str, mem_len); if (ret == 0 && str[mem_len] != 0) ret = 1; return ret; } uint8_t dtoverlay_read_u8(const void *src, int off) { const unsigned char *p = src; return (p[off + 0] << 0); } uint16_t dtoverlay_read_u16(const void *src, int off) { const unsigned char *p = src; return (p[off + 0] << 8) | (p[off + 1] << 0); } uint32_t dtoverlay_read_u32(const void *src, int off) { const unsigned char *p = src; return (p[off + 0] << 24) | (p[off + 1] << 16) | (p[off + 2] << 8) | (p[off + 3] << 0); } uint64_t dtoverlay_read_u64(const void *src, int off) { const unsigned char *p = src; return ((uint64_t)p[off + 0] << 56) | ((uint64_t)p[off + 1] << 48) | ((uint64_t)p[off + 2] << 40) | ((uint64_t)p[off + 3] << 32) | (p[off + 4] << 24) | (p[off + 5] << 16) | (p[off + 6] << 8) | (p[off + 7] << 0); } void dtoverlay_write_u8(void *dst, int off, uint32_t val) { unsigned char *p = dst; p[off] = (val >> 0) & 0xff; } void dtoverlay_write_u16(void *dst, int off, uint32_t val) { unsigned char *p = dst; p[off + 0] = (val >> 8) & 0xff; p[off + 1] = (val >> 0) & 0xff; } void dtoverlay_write_u32(void *dst, int off, uint32_t val) { unsigned char *p = dst; p[off + 0] = (val >> 24) & 0xff; p[off + 1] = (val >> 16) & 0xff; p[off + 2] = (val >> 8) & 0xff; p[off + 3] = (val >> 0) & 0xff; } void dtoverlay_write_u64(void *dst, int off, uint64_t val) { unsigned char *p = dst; p[off + 0] = (val >> 56) & 0xff; p[off + 1] = (val >> 48) & 0xff; p[off + 2] = (val >> 40) & 0xff; p[off + 3] = (val >> 32) & 0xff; p[off + 4] = (val >> 24) & 0xff; p[off + 5] = (val >> 16) & 0xff; p[off + 6] = (val >> 8) & 0xff; p[off + 7] = (val >> 0) & 0xff; } // Returns the offset of the node indicated by the absolute path, creating // it and any intermediates as necessary, or a negative error code. int dtoverlay_create_node(DTBLOB_T *dtb, const char *node_path, int path_len) { const char *path_ptr; const char *path_end; int node_off = 0; if (!path_len) path_len = strlen(node_path); path_ptr = node_path; path_end = node_path + path_len; if ((path_len > 0) && (path_ptr[path_len - 1] == '/')) path_end--; while (path_ptr < path_end) { const char *path_next; int subnode_off; if (*path_ptr != '/') return -FDT_ERR_BADPATH; // find the next path separator (or the end of the string) path_ptr++; for (path_next = path_ptr; (path_next != path_end) && (*path_next != '/'); path_next++) continue; subnode_off = fdt_subnode_offset_namelen(dtb->fdt, node_off, path_ptr, path_next - path_ptr); if (subnode_off >= 0) node_off = subnode_off; else node_off = fdt_add_subnode_namelen(dtb->fdt, node_off, path_ptr, path_next - path_ptr); if (node_off < 0) break; path_ptr = path_next; } if ((node_off >= 0) && (path_ptr != path_end)) return -FDT_ERR_BADPATH; return node_off; } // Returns 0 on success, otherwise <0 error code int dtoverlay_delete_node(DTBLOB_T *dtb, const char *node_path, int path_len) { int node_off = 0; if (!path_len) path_len = strlen(node_path); dtoverlay_debug("delete_node(%.*s)", path_len, node_path); node_off = fdt_path_offset_namelen(dtb->fdt, node_path, path_len); if (node_off < 0) return node_off; return fdt_del_node(dtb->fdt, node_off); } // Returns the offset of the node indicated by the absolute path or a negative // error code. int dtoverlay_find_node(DTBLOB_T *dtb, const char *node_path, int path_len) { if (!path_len) path_len = strlen(node_path); return fdt_path_offset_namelen(dtb->fdt, node_path, path_len); } int dtoverlay_first_subnode(DTBLOB_T *dtb, int node_off) { return fdt_first_subnode(dtb->fdt, node_off); } int dtoverlay_next_subnode(DTBLOB_T *dtb, int subnode_off) { return fdt_next_subnode(dtb->fdt, subnode_off); } // Returns 0 on success, otherwise <0 error code int dtoverlay_set_node_properties(DTBLOB_T *dtb, const char *node_path, DTOVERLAY_PARAM_T *properties, unsigned int num_properties) { int err = 0; int node_off; node_off = fdt_path_offset(dtb->fdt, node_path); if (node_off < 0) node_off = dtoverlay_create_node(dtb, node_path, 0); if (node_off >= 0) { unsigned int i; for (i = 0; (i < num_properties) && (err == 0); i++) { DTOVERLAY_PARAM_T *p; p = properties + i; err = fdt_setprop(dtb->fdt, node_off, p->param, p->b, p->len); } } else err = node_off; return err; } struct dynstring { char *buf; int size; int len; }; static void dynstring_init(struct dynstring *ds) { ds->size = 0; ds->len = 0; ds->buf = NULL; } static int dynstring_init_size(struct dynstring *ds, int initial_size) { if (initial_size < 32) initial_size = 32; ds->size = 0; ds->len = 0; ds->buf = malloc(initial_size); if (!ds->buf) { dtoverlay_error(" out of memory"); return -FDT_ERR_NOSPACE; } ds->size = initial_size; return 0; } static int dynstring_set_size(struct dynstring *ds, int size) { if (size > ds->size) { size = (size * 5)/4; // Add a 25% headroom ds->buf = realloc(ds->buf, size); if (!ds->buf) { dtoverlay_error(" out of memory"); return -FDT_ERR_NOSPACE; } ds->size = size; } else if (size < 0) { return -FDT_ERR_BADVALUE; } return 0; } static int dynstring_dup(struct dynstring *ds, const char *src, int len) { int err = 0; if (!len) len = strlen(src); if (len < 0) return -FDT_ERR_BADVALUE; err = dynstring_set_size(ds, len + 1); if (!err) { memcpy(ds->buf, src, len + 1); ds->len = len; } return err; } static int dynstring_patch(struct dynstring *ds, int pos, int width, const char *src, int len) { int newlen = ds->len + (len - width); int err = dynstring_set_size(ds, newlen + 1); if (!err) { if (width != len) { // Move any data following the patch memmove(ds->buf + pos + len, ds->buf + pos + width, ds->len + 1 - (pos + width)); ds->len = newlen; } memcpy(ds->buf + pos, src, len); } return err; } static int dynstring_grow(struct dynstring *ds) { return dynstring_set_size(ds, (3*ds->size)/2); } static void dynstring_free(struct dynstring *ds) { free(ds->buf); dynstring_init(ds); } static int dtoverlay_set_node_name(DTBLOB_T *dtb, int node_off, const char *name) { struct dynstring path_buf; struct dynstring prop_buf; char *old_path; const char *old_name; const char *fixup_nodes[] = { "/__fixups__", "/__local_fixups__", // For old-style dtbos "/__symbols__" // Just in case the kernel cares }; int old_name_len; int old_path_len; // All of it int dir_len; // Excluding the node name, but with the trailling slash int name_len; int offset; unsigned int fixup_idx; int err = 0; // Fixups and local-fixups both use node names, so this // function must be patch them up when a node is renamed // unless the fixups have already been applied. // Calculating a node's name is expensive, so only do it if // necessary. Since renaming a node can move things around, // don't use node_off afterwards. err = dynstring_init_size(&path_buf, 100); if (err) return err; if (!dtb->fixups_applied) { while (1) { err = fdt_get_path(dtb->fdt, node_off, path_buf.buf, path_buf.size); if (!err) break; if (err != -FDT_ERR_NOSPACE) return err; dynstring_grow(&path_buf); } } old_path = path_buf.buf; err = fdt_set_name(dtb->fdt, node_off, name); if (err || dtb->fixups_applied) goto clean_up; // Find the node name in old_path old_name = strrchr(old_path, '/'); assert(old_name); if (!old_name) return -FDT_ERR_INTERNAL; old_name++; old_name_len = strlen(old_name); dir_len = old_name - old_path; old_path_len = dir_len + old_name_len; // Short-circuit the case where the name isn't changing if (strcmp(name, old_name) == 0) goto clean_up; name_len = strlen(name); // Search the fixups and symbols for the old path (including as // a parent) and replace with the new name dynstring_init(&prop_buf); for (fixup_idx = 0; fixup_idx < ARRAY_SIZE(fixup_nodes); fixup_idx++) { int prop_off; offset = fdt_path_offset(dtb->fdt, fixup_nodes[fixup_idx]); if (offset > 0) { // Iterate through the properties for (prop_off = fdt_first_property_offset(dtb->fdt, offset); (prop_off >= 0) && (err == 0); prop_off = fdt_next_property_offset(dtb->fdt, prop_off)) { const char *prop_name; const char *prop_val; int prop_len; int pos; int changed = 0; prop_val = fdt_getprop_by_offset(dtb->fdt, prop_off, &prop_name, &prop_len); err = dynstring_dup(&prop_buf, prop_val, prop_len); if (err) break; // Scan each property for matching paths pos = 0; while (pos < prop_len) { if ((pos + old_path_len < prop_len) && (memcmp(prop_buf.buf + pos, old_path, old_path_len) == 0) && ((prop_buf.buf[pos + old_path_len] == ':') || (prop_buf.buf[pos + old_path_len] == '/') || (prop_buf.buf[pos + old_path_len] == '\0'))) { // Patch the string, replacing old name with new err = dynstring_patch(&prop_buf, pos + dir_len, old_name_len, name, name_len); if (err) break; prop_len += name_len - old_name_len; changed = 1; } pos += strlen(prop_buf.buf + pos) + 1; } if (!err && changed) { // Caution - may change offsets, but only by shuffling everything // afterwards, i.e. the offset to this node or property does not // change. err = fdt_setprop(dtb->fdt, offset, prop_name, prop_buf.buf, prop_len); } } } } dynstring_free(&prop_buf); if (err) goto clean_up; // Then look for a "/__local_fixups__" node, and rename // that as well. offset = fdt_path_offset(dtb->fdt, "/__local_fixups__"); if (offset > 0) { const char *p, *end; p = old_path; end = old_path + old_path_len; while (p < end) { const char *q; while (*p == '/') { p++; if (p == end) break; } q = memchr(p, '/', end - p); if (! q) q = end; offset = fdt_subnode_offset_namelen(dtb->fdt, offset, p, q-p); if (offset < 0) break; p = q; } if (offset > 0) err = fdt_set_name(dtb->fdt, offset, name); } // __overrides__ don't need patching because nodes are identified // using phandles, which are unaffected by renaming and resizing nodes. clean_up: dynstring_free(&path_buf); return err; } // Returns 0 on success, otherwise <0 error code int dtoverlay_create_prop_fragment(DTBLOB_T *dtb, int idx, int target_phandle, const char *prop_name, const void *prop_data, int prop_len) { char fragment_name[20]; int frag_off, ovl_off; int ret; snprintf(fragment_name, sizeof(fragment_name), "fragment-%u", idx); frag_off = fdt_add_subnode(dtb->fdt, 0, fragment_name); if (frag_off < 0) return frag_off; ret = fdt_setprop_u32(dtb->fdt, frag_off, "target", target_phandle); if (ret < 0) return ret; ovl_off = fdt_add_subnode(dtb->fdt, frag_off, "__overlay__"); if (ovl_off < 0) return ovl_off; return fdt_setprop(dtb->fdt, ovl_off, prop_name, prop_data, prop_len); } // Returns 0 on success, otherwise <0 error code int dtoverlay_merge_fragment(DTBLOB_T *base_dtb, int target_off, const DTBLOB_T *overlay_dtb, int overlay_off, int depth) { int prop_off, subnode_off; int err = 0; if (dtoverlay_debug_enabled) { char base_path[DTOVERLAY_MAX_PATH]; char overlay_path[DTOVERLAY_MAX_PATH]; fdt_get_path(base_dtb->fdt, target_off, base_path, sizeof(base_path)); fdt_get_path(overlay_dtb->fdt, overlay_off, overlay_path, sizeof(overlay_path)); dtoverlay_debug("merge_fragment(%s,%s)", base_path, overlay_path); } // Merge each property of the node for (prop_off = fdt_first_property_offset(overlay_dtb->fdt, overlay_off); (prop_off >= 0) && (err == 0); prop_off = fdt_next_property_offset(overlay_dtb->fdt, prop_off)) { const char *prop_name; const void *prop_val; int prop_len; struct fdt_property *target_prop; int target_len; prop_val = fdt_getprop_by_offset(overlay_dtb->fdt, prop_off, &prop_name, &prop_len); /* Skip these system properties (only phandles in the first level) */ if ((strcmp(prop_name, "name") == 0) || ((depth == 0) && ((strcmp(prop_name, "phandle") == 0) || (strcmp(prop_name, "linux,phandle") == 0)))) continue; dtoverlay_debug(" +prop(%s)", prop_name); if ((strcmp(prop_name, "bootargs") == 0) && ((target_prop = fdt_get_property_w(base_dtb->fdt, target_off, prop_name, &target_len)) != NULL) && (target_len > 0) && *target_prop->data) { target_prop->data[target_len - 1] = ' '; err = fdt_appendprop(base_dtb->fdt, target_off, prop_name, prop_val, prop_len); } else { err = fdt_setprop(base_dtb->fdt, target_off, prop_name, prop_val, prop_len); } } // Merge each subnode of the node for (subnode_off = fdt_first_subnode(overlay_dtb->fdt, overlay_off); (subnode_off >= 0) && (err == 0); subnode_off = fdt_next_subnode(overlay_dtb->fdt, subnode_off)) { const char *subnode_name; int name_len; int subtarget_off; subnode_name = fdt_get_name(overlay_dtb->fdt, subnode_off, &name_len); subtarget_off = fdt_subnode_offset_namelen(base_dtb->fdt, target_off, subnode_name, name_len); if (subtarget_off < 0) subtarget_off = fdt_add_subnode_namelen(base_dtb->fdt, target_off, subnode_name, name_len); if (subtarget_off >= 0) { err = dtoverlay_merge_fragment(base_dtb, subtarget_off, overlay_dtb, subnode_off, depth + 1); } else { err = subtarget_off; } } dtoverlay_debug("merge_fragment() end"); return err; } static int dtoverlay_phandle_relocate(DTBLOB_T *dtb, int node_off, const char *prop_name, uint32_t phandle_increment) { int len; const fdt32_t *prop_val = fdt_getprop(dtb->fdt, node_off, prop_name, &len); int err = 0; // The absence of the property is not an error if (prop_val) { uint32_t phandle; if (len < 4) { dtoverlay_error("%s property too small", prop_name); return -FDT_ERR_BADSTRUCTURE; } phandle = fdt32_to_cpu(*prop_val) + phandle_increment; phandle_debug(" phandle_relocate %d->%d", fdt32_to_cpu(*prop_val), phandle); err = fdt_setprop_inplace_u32(dtb->fdt, node_off, prop_name, phandle); } return err; } // Returns 0 on success, or an FDT error code static int dtoverlay_apply_fixups(DTBLOB_T *dtb, const char *fixups_stringlist, int fixups_len, uint32_t phandle, fixup_type_t type) { // The fixups arrive as a sequence of NUL-terminated strings, of the form: // "path:property:offset" // Use an empty string as an end marker, since: // 1) all tags begin 0x00 0x00 0x00, // 2) all string properties must be followed by a tag, // 3) an empty string is not a valid fixup, and // 4) the code is simpler as a result. const char *fixup = fixups_stringlist; const char *end = fixup + fixups_len; while (fixup < end && fixup[0]) { const char *prop_name, *offset_str; char *offset_end; const void *prop_ptr; int prop_len; int node_off; unsigned long offset; uint32_t patch; prop_name = strchr(fixup, ':'); if (!prop_name) return -FDT_ERR_BADSTRUCTURE; prop_name++; offset_str = strchr(prop_name, ':'); if (!offset_str) return -FDT_ERR_BADSTRUCTURE; offset_str++; offset = strtoul(offset_str, &offset_end, 10); if ((offset_end == offset_str) || (offset_end[0] != 0)) return -FDT_ERR_BADSTRUCTURE; node_off = fdt_path_offset_namelen(dtb->fdt, fixup, prop_name - 1 - fixup); if (node_off < 0) return node_off; prop_ptr = fdt_getprop_namelen(dtb->fdt, node_off, prop_name, offset_str - 1 - prop_name, &prop_len); if (!prop_ptr) return prop_len; if ((int)offset > (prop_len - 4)) return -FDT_ERR_BADSTRUCTURE; // Now apply the patch. Yes, prop_ptr is a const void *, but the // alternative (copying the whole property, patching, then updating as // a whole) is ridiculous. if (type == FIXUP_RELATIVE) { patch = phandle + dtoverlay_read_u32(prop_ptr, offset); phandle_debug(" phandle fixup %d+%d->%d", phandle, patch - phandle, patch); } else { patch = phandle; phandle_debug(" phandle ref '%s'->%d", prop_name, patch); } dtoverlay_write_u32((void *)prop_ptr, offset, patch); fixup = offset_end + 1; } return 0; } // Returns 0 on success, or an FDT error code static int dtoverlay_apply_fixups_node(DTBLOB_T *dtb, int fix_off, int target_off, uint32_t phandle_offset) { // The fixups are arranged as a subtree mirroring the structure of the // overall tree. Walk this tree in order. Each property is an array of cells // containing offsets to patch within the corresponding node/property of // the target tree. int err = 0; int prop_off; int subfix_off; // Merge each property of the node for (prop_off = fdt_first_property_offset(dtb->fdt, fix_off); (prop_off >= 0) && (err == 0); prop_off = fdt_next_property_offset(dtb->fdt, prop_off)) { const char *prop_name; const void *prop_val; int prop_len; void *target_ptr; int target_len; int off; prop_val = fdt_getprop_by_offset(dtb->fdt, prop_off, &prop_name, &prop_len); if (!prop_val) return -FDT_ERR_INTERNAL; target_ptr = fdt_getprop_w(dtb->fdt, target_off, prop_name, &target_len); if (!target_ptr) return -FDT_ERR_BADSTRUCTURE; for (off = 0; (off + 4) <= prop_len; off += 4) { uint32_t patch; int patch_offset = dtoverlay_read_u32(prop_val, off); if ((patch_offset + 4) > target_len) return -FDT_ERR_BADSTRUCTURE; patch = phandle_offset + dtoverlay_read_u32(target_ptr, patch_offset); phandle_debug(" phandle fixup %d+%d->%d", phandle_offset, patch - phandle_offset, patch); dtoverlay_write_u32(target_ptr, patch_offset, patch); } } // Merge each subnode of the node for (subfix_off = fdt_first_subnode(dtb->fdt, fix_off); (subfix_off >= 0) && (err == 0); subfix_off = fdt_next_subnode(dtb->fdt, subfix_off)) { const char *subnode_name; int name_len; int subtarget_off; subnode_name = fdt_get_name(dtb->fdt, subfix_off, &name_len); subtarget_off = fdt_subnode_offset_namelen(dtb->fdt, target_off, subnode_name, name_len); if (subtarget_off >= 0) { err = dtoverlay_apply_fixups_node(dtb, subfix_off, subtarget_off, phandle_offset); } else { err = subtarget_off; } } return err; } // Returns 0 on success, or a negative FDT error. static int dtoverlay_resolve_phandles(DTBLOB_T *base_dtb, DTBLOB_T *overlay_dtb) { int local_fixups_off; int node_off; int err = 0; // First find and update the phandles in the overlay for (node_off = 0; node_off >= 0; node_off = fdt_next_node(overlay_dtb->fdt, node_off, NULL)) { dtoverlay_phandle_relocate(overlay_dtb, node_off, "phandle", base_dtb->max_phandle); dtoverlay_phandle_relocate(overlay_dtb, node_off, "linux,phandle", base_dtb->max_phandle); } local_fixups_off = fdt_path_offset(overlay_dtb->fdt, "/__local_fixups__"); if (local_fixups_off >= 0) { const char *fixups_stringlist; // Update the references to local phandles using the local fixups. // The property name is "fixup". // The value is a NUL-separated stringlist of descriptors of the form: // path:property:offset fixups_stringlist = fdt_getprop(overlay_dtb->fdt, local_fixups_off, "fixup", &err); if (fixups_stringlist) { // Relocate the overlay phandle references err = dtoverlay_apply_fixups(overlay_dtb, fixups_stringlist, err, base_dtb->max_phandle, FIXUP_RELATIVE); } else { err = dtoverlay_apply_fixups_node(overlay_dtb, local_fixups_off, 0, base_dtb->max_phandle); } if (err < 0) { dtoverlay_error("error applying local fixups"); return err; } } overlay_dtb->max_phandle += base_dtb->max_phandle; phandle_debug(" +overlay max phandle +%d -> %d", base_dtb->max_phandle, overlay_dtb->max_phandle); return err; } // Returns 0 on success, or an FDT error code static int dtoverlay_resolve_fixups(DTBLOB_T *base_dtb, DTBLOB_T *overlay_dtb) { int fixups_off; int err = 0; fixups_off = fdt_path_offset(overlay_dtb->fdt, "/__fixups__"); if (fixups_off >= 0) { int fixup_off, symbols_off = -1; fixup_off = fdt_first_property_offset(overlay_dtb->fdt, fixups_off); if (fixup_off >= 0) { // Find the symbols, which will be needed to resolve the fixups symbols_off = fdt_path_offset(base_dtb->fdt, "/__symbols__"); if (symbols_off < 0) { dtoverlay_error("no symbols found"); return -FDT_ERR_NOTFOUND; } } for (; fixup_off >= 0; fixup_off = fdt_next_property_offset(overlay_dtb->fdt, fixup_off)) { const char *fixups_stringlist, *symbol_name, *target_path; const char *ref_type; int target_off, fixups_len; uint32_t target_phandle; // The property name identifies a symbol (or alias) in the base. // The value is a comma-separated list of descriptors of the form: // path:property:offset fixups_stringlist = fdt_getprop_by_offset(overlay_dtb->fdt, fixup_off, &symbol_name, &err); if (!fixups_stringlist) { dtoverlay_error("__fixups__ are borked"); break; } fixups_len = err; // 1) Find the target node. if (symbol_name[0] == '/') { /* This is a new-style path reference */ target_path = symbol_name; ref_type = "path"; } else { target_path = fdt_getprop(base_dtb->fdt, symbols_off, symbol_name, &err); if (!target_path) { dtoverlay_error("can't find symbol '%s'", symbol_name); break; } ref_type = "symbol"; } target_off = fdt_path_offset(base_dtb->fdt, target_path); if (target_off < 0) { dtoverlay_error("%s '%s' is invalid", ref_type, symbol_name); err = target_off; break; } // 2) Ensure that the target node has a phandle. target_phandle = fdt_get_phandle(base_dtb->fdt, target_off); if (!target_phandle) { // It doesn't, so give it one fdt32_t temp; target_phandle = ++base_dtb->max_phandle; temp = cpu_to_fdt32(target_phandle); err = fdt_setprop(base_dtb->fdt, target_off, "phandle", &temp, 4); if (err != 0) { dtoverlay_error("failed to add a phandle"); break; } phandle_debug(" phandle '%s'->%d", target_path, target_phandle); // The symbols may have moved, so recalculate symbols_off = fdt_path_offset(base_dtb->fdt, "/__symbols__"); } // Now apply the valid target_phandle to the items in the fixup string err = dtoverlay_apply_fixups(overlay_dtb, fixups_stringlist, fixups_len, target_phandle, FIXUP_ABSOLUTE); if (err) { dtoverlay_error("failed to apply fixups"); break; } } } return err; } static int dtoverlay_get_target_offset(DTBLOB_T *base_dtb, DTBLOB_T *overlay_dtb, int frag_off) { const char *target_path; int target_off; int len; target_path = fdt_getprop(overlay_dtb->fdt, frag_off, "target-path", &len); if (target_path) { if (!base_dtb) return -FDT_ERR_NOTFOUND; if (len && (target_path[len - 1] == '\0')) len--; target_off = fdt_path_offset_namelen(base_dtb->fdt, target_path, len); if (target_off < 0) { dtoverlay_error("invalid target-path '%.*s'", len, target_path); return target_off; } } else { const void *target_prop; int phandle; target_prop = fdt_getprop(overlay_dtb->fdt, frag_off, "target", &len); if (!target_prop) { dtoverlay_error("no target or target-path"); return len; } if (len != 4) return -FDT_ERR_BADSTRUCTURE; phandle = fdt32_to_cpu(*(fdt32_t *)target_prop); if (!base_dtb) { if (phandle < 0 || (uint32_t)phandle > overlay_dtb->max_phandle) return -FDT_ERR_NOTFOUND; return fdt_node_offset_by_phandle(overlay_dtb->fdt, phandle); } target_off = fdt_node_offset_by_phandle(base_dtb->fdt, phandle); if (target_off < 0) { dtoverlay_error("invalid target (phandle %d)", phandle); return target_off; } } return target_off; } // Copy a node full of path strings (__symbols__, aliases) from an overlay to // the base dtb, rebasing any fragment-relative paths to make them relative // to the respective fragment target. Note that this should not be called for // intra-overlay fragments, and that overlay_dtb is not modified. static int dtoverlay_apply_overlay_paths(DTBLOB_T *base_dtb, int strings_off, DTBLOB_T *overlay_dtb, int frag_off, const char *type) { int sym_off; int err = 0; fdt_for_each_property_offset(sym_off, overlay_dtb->fdt, frag_off) { char target_path[DTOVERLAY_MAX_PATH]; const char *sym_name = NULL; const char *sym_path; const char *p; int sym_len; int sym_frag_off; int target_off; int target_path_len; int new_path_len; sym_path = fdt_getprop_by_offset(overlay_dtb->fdt, sym_off, &sym_name, &sym_len); if (!sym_path) break; /* Skip non-overlay symbols * Overlay symbol paths should be of the form: * //__overlay__/ * It doesn't actually matter what is. */ if (sym_path[0] != '/') goto copy_verbatim; p = strchr(sym_path + 1, '/'); if (!p || strncmp(p + 1, "__overlay__", 11) != 0 || (p[12] != '/' && p[12] != '\0')) goto copy_verbatim; /* Rebase the symbol path so that * /fragment@0/__overlay__/ * becomes * / */ /* Find the offset to the fragment */ sym_frag_off = dtoverlay_find_node(overlay_dtb, sym_path, p - sym_path); p += 12; /* p points to / */ /* Locate the path to the fragment target */ target_off = dtoverlay_get_target_offset(base_dtb, overlay_dtb, sym_frag_off); if (target_off < 0) return NON_FATAL(target_off); err = fdt_get_path(base_dtb->fdt, target_off, target_path, sizeof(target_path)); if (err) { dtoverlay_error("bad target path for %s", sym_path); break; } /* Append the fragment-relative path to the target path */ target_path_len = strlen(target_path); if (strcmp(target_path, "/") == 0) p++; // Avoid a '//' if the target is the root new_path_len = target_path_len + (sym_path + sym_len - p); if (new_path_len >= (int)sizeof(target_path)) { dtoverlay_error("exported symbol path too long for %s", sym_path); err = -FDT_ERR_NOSPACE; break; } strcpy(target_path + target_path_len, p); fdt_setprop(base_dtb->fdt, strings_off, sym_name, target_path, new_path_len); dtoverlay_debug("set %s '%s' path to '%s'", type, sym_name, target_path); continue; copy_verbatim: fdt_setprop(base_dtb->fdt, strings_off, sym_name, sym_path, sym_len); } return err; } // Returns 0 on success, -ve for fatal errors and +ve for non-fatal errors int dtoverlay_merge_overlay(DTBLOB_T *base_dtb, DTBLOB_T *overlay_dtb) { // Merge each fragment node int frag_off; int frag_idx; int err = 0; int overlay_size = fdt_totalsize(overlay_dtb->fdt); void *overlay_copy = NULL; dtoverlay_filter_symbols(overlay_dtb); for (frag_off = fdt_first_subnode(overlay_dtb->fdt, 0), frag_idx = 0; frag_off >= 0; frag_off = fdt_next_subnode(overlay_dtb->fdt, frag_off), frag_idx++) { const char *node_name; const char *frag_name; int target_off, overlay_off; DTBLOB_T clone_dtb; int idx; node_name = fdt_get_name(overlay_dtb->fdt, frag_off, NULL); if (strncmp(node_name, "fragment@", 9) != 0 && strncmp(node_name, "fragment-", 9) != 0) continue; frag_name = node_name + 9; // Find the target and overlay nodes overlay_off = fdt_subnode_offset(overlay_dtb->fdt, frag_off, "__overlay__"); if (overlay_off < 0) { if (fdt_subnode_offset(overlay_dtb->fdt, frag_off, "__dormant__") >= 0) dtoverlay_debug("fragment %s disabled", frag_name); else dtoverlay_error("no overlay in fragment %s", frag_name); continue; } target_off = dtoverlay_get_target_offset(NULL, overlay_dtb, frag_off); if (target_off < 0) continue; // Merge the fragment with the overlay // We can't just call dtoverlay_merge_fragment with the overlay_dtb // as source and destination because the source is not expected to // change. Instead, clone the overlay, apply the fragment, then switch. if (intra_fragment_merged_callback) (*intra_fragment_merged_callback)(overlay_dtb, overlay_off, target_off); if (!overlay_copy) { overlay_copy = malloc(overlay_size); if (!overlay_copy) { err = -FDT_ERR_NOSPACE; break; } } memcpy(overlay_copy, overlay_dtb->fdt, overlay_size); memcpy(&clone_dtb, overlay_dtb, sizeof(DTBLOB_T)); clone_dtb.fdt = overlay_copy; err = dtoverlay_merge_fragment(&clone_dtb, target_off, overlay_dtb, overlay_off, 0); if (err) break; // Swap the buffers { void *temp = overlay_dtb->fdt; overlay_dtb->fdt = overlay_copy; overlay_copy = temp; } // Disable this fragment (and resync with the changed overlay) for (frag_off = fdt_first_subnode(overlay_dtb->fdt, 0), idx = 0; idx < frag_idx; frag_off = fdt_next_subnode(overlay_dtb->fdt, frag_off), idx++) continue; overlay_off = fdt_subnode_offset(overlay_dtb->fdt, frag_off, "__overlay__"); if (overlay_off >= 0) dtoverlay_set_node_name(overlay_dtb, overlay_off, "__dormant__"); // As the new name is the same length, the offsets are still valid } if (overlay_copy) free(overlay_copy); if (err || !base_dtb) goto no_base_dtb; for (frag_off = fdt_first_subnode(overlay_dtb->fdt, 0), frag_idx = 0; frag_off >= 0; frag_off = fdt_next_subnode(overlay_dtb->fdt, frag_off), frag_idx++) { const char *node_name; const char *frag_name; int target_off, overlay_off; node_name = fdt_get_name(overlay_dtb->fdt, frag_off, NULL); if (strcmp(node_name, "__symbols__") == 0) { /* At this point, only exported symbols should remain */ int symbols_off = dtoverlay_find_node(base_dtb, "/__symbols__", 0); dtoverlay_apply_overlay_paths(base_dtb, symbols_off, overlay_dtb, frag_off, "label"); continue; } else if (strncmp(node_name, "fragment@", 9) != 0 && strncmp(node_name, "fragment-", 9) != 0) { continue; } frag_name = node_name + 9; // Find the target and overlay nodes overlay_off = fdt_subnode_offset(overlay_dtb->fdt, frag_off, "__overlay__"); if (overlay_off < 0) { if (fdt_subnode_offset(overlay_dtb->fdt, frag_off, "__dormant__") >= 0) dtoverlay_debug("fragment %s disabled", frag_name); else dtoverlay_error("no overlay in fragment %s", frag_name); continue; } target_off = dtoverlay_get_target_offset(base_dtb, overlay_dtb, frag_off); if (target_off < 0) { err = NON_FATAL(target_off); break; } // Now do the merge node_name = fdt_get_name(base_dtb->fdt, target_off, NULL); if (node_name && strcmp(node_name, "aliases") == 0) err = dtoverlay_apply_overlay_paths(base_dtb, target_off, overlay_dtb, overlay_off, "alias"); else err = dtoverlay_merge_fragment(base_dtb, target_off, overlay_dtb, overlay_off, 0); } if (err == 0) base_dtb->max_phandle = overlay_dtb->max_phandle; no_base_dtb: if (err) dtoverlay_error("merge failed"); return err; } // Returns 0 on success, -ve for fatal errors and +ve for non-fatal errors int dtoverlay_fixup_overlay(DTBLOB_T *base_dtb, DTBLOB_T *overlay_dtb) { int err; // To do: Check the "compatible" string? err = dtoverlay_resolve_fixups(base_dtb, overlay_dtb); if (err >= 0) err = dtoverlay_resolve_phandles(base_dtb, overlay_dtb); overlay_dtb->fixups_applied = 1; return NON_FATAL(err); } // Returns 0 on success, -ve for fatal errors and +ve for non-fatal errors int dtoverlay_merge_params(DTBLOB_T *dtb, const DTOVERLAY_PARAM_T *params, unsigned int num_params) { int err = 0; unsigned int i; for (i=0; (iparam; slash = strrchr(node_name, '/'); if (!slash) { err = NON_FATAL(FDT_ERR_BADPATH); break; } // Ensure that root properties ("/xxx") work if (slash == node_name) path_len = 1; else path_len = slash - node_name; // find node, create if it does not exist yet node_off = dtoverlay_create_node(dtb, node_name, path_len); if (node_off >= 0) { const char *prop_name = slash + 1; int prop_len; struct fdt_property *prop; if ((strcmp(prop_name, "bootargs") == 0) && ((prop = fdt_get_property_w(dtb->fdt, node_off, prop_name, &prop_len)) != NULL) && (prop_len > 0) && *prop->data) { prop->data[prop_len - 1] = ' '; err = fdt_appendprop(dtb->fdt, node_off, prop_name, p->b, p->len); } else err = fdt_setprop(dtb->fdt, node_off, prop_name, p->b, p->len); } else err = node_off; } return err; } int dtoverlay_filter_symbols(DTBLOB_T *dtb) { int symbols_off; int exports_off; struct str_item *exports = NULL; int prop_off; struct str_item { struct str_item *next; char str[1]; }; symbols_off = dtoverlay_find_node(dtb, "/__symbols__", 0); if (symbols_off < 0) return 0; exports_off = dtoverlay_find_node(dtb, "/__exports__", 0); if (exports_off < 0) { /* There are no exports, so keep all symbols private. */ fdt_del_node(dtb->fdt, symbols_off); return 0; } /* Internalise the names of the exported properties for speed * and to protect against the FDT contents moving. */ fdt_for_each_property_offset(prop_off, dtb->fdt, exports_off) { struct str_item *new_str; const char *name = NULL; fdt_getprop_by_offset(dtb->fdt, prop_off, &name, NULL); if (!name) break; new_str = malloc(sizeof(*new_str) + strlen(name)); if (!new_str) { /* Free all of the internalised exports */ while (exports) { struct str_item *str = exports; exports = str->next; free(str); } dtoverlay_error(" out of memory"); return -FDT_ERR_NOSPACE; } strcpy(new_str->str, name); new_str->next = exports; exports = new_str; } /* Iterate through the symbols, deleting any that aren't * exported. */ prop_off = fdt_first_property_offset(dtb->fdt, symbols_off); while (prop_off >= 0) { const char *name = NULL; struct str_item *str; (void)fdt_getprop_by_offset(dtb->fdt, prop_off, &name, NULL); if (!name) break; for (str = exports; str; str = str->next) { if (!strcmp(str->str, name)) break; } if (str) /* This symbol is exported */ prop_off = fdt_next_property_offset(dtb->fdt, prop_off); else fdt_delprop(dtb->fdt, symbols_off, name); } /* Free all of the internalised exports */ while (exports) { struct str_item *str = exports; exports = str->next; free(str); } return 0; } const char *dtoverlay_find_fixup(DTBLOB_T *dtb, const char *fixup_loc) { int fixups_off; fixups_off = fdt_path_offset(dtb->fdt, "/__fixups__"); if (fixups_off > 0) { int fixup_off; for (fixup_off = fdt_first_property_offset(dtb->fdt, fixups_off); fixup_off >= 0; fixup_off = fdt_next_property_offset(dtb->fdt, fixup_off)) { const char *fixups_stringlist; const char *symbol_name; int list_len; fixups_stringlist = fdt_getprop_by_offset(dtb->fdt, fixup_off, &symbol_name, &list_len); if (fdt_stringlist_contains(fixups_stringlist, list_len, fixup_loc) > 0) return symbol_name; } } return NULL; } int dtoverlay_add_fixup(DTBLOB_T *dtb, const char *symbol, const char *fixup_loc) { int loc_len = strlen(fixup_loc); int fixups_off; fixups_off = fdt_path_offset(dtb->fdt, "/__fixups__"); assert(fixups_off > 0); if (fixups_off < 0) return fixups_off; return fdt_appendprop(dtb->fdt, fixups_off, symbol, fixup_loc, loc_len + 1); } static const char *stringlist_find(const char *strlist, int listlen, const char *str, int len) { const char *p; while (listlen >= len) { if (memcmp(str, strlist, len + 1) == 0) return strlist; p = memchr(strlist, '\0', listlen); if (!p) return NULL; /* malformed strlist.. */ listlen -= (p-strlist) + 1; strlist = p + 1; } return NULL; } int dtoverlay_delete_fixup(DTBLOB_T *dtb, const char *fixup_loc) { int loc_len = strlen(fixup_loc); int fixups_off; fixups_off = fdt_path_offset(dtb->fdt, "/__fixups__"); if (fixups_off > 0) { int fixup_off; for (fixup_off = fdt_first_property_offset(dtb->fdt, fixups_off); fixup_off >= 0; fixup_off = fdt_next_property_offset(dtb->fdt, fixup_off)) { const char *fixups_stringlist; const char *symbol_name; const char *match; int list_len; fixups_stringlist = fdt_getprop_by_offset(dtb->fdt, fixup_off, &symbol_name, &list_len); match = stringlist_find(fixups_stringlist, list_len, fixup_loc, loc_len); if (match) { int match_len = loc_len + 1; if (match_len == list_len) { /* This was the only fixup - the symbol is no longer referenced */ return fdt_delprop(dtb->fdt, fixups_off, symbol_name); } else { char *buf = malloc(list_len - match_len); int match_off = match - fixups_stringlist; int after_match = list_len - (match_off + match_len); int err; if (match_off) memcpy(buf, fixups_stringlist, match_off); if (after_match) memcpy(buf + match_off, match + match_len, after_match); err = fdt_setprop(dtb->fdt, fixups_off, symbol_name, buf, list_len - match_len); free(buf); return err; } } } } return -FDT_ERR_NOTFOUND; } int dtoverlay_stringlist_replace(const char *src, int src_len, const char *src_prefix, int src_prefix_len, const char *dst_prefix, int dst_prefix_len, char *dst) { /* With a NULL dst, only returns the new length */ char *dst_p = dst; int replaced = 0; while (src_len) { const char *p; int copy_bytes; p = memchr(src, '\0', src_len); if (!p) return -1; /* malformed strlist.. */ if (src_prefix_len < src_len && memcmp(src, src_prefix, src_prefix_len) == 0) { if (dst) memcpy(dst_p, dst_prefix, dst_prefix_len); src_len -= src_prefix_len; src += src_prefix_len; dst_p += dst_prefix_len; replaced = 1; } copy_bytes = (p - src) + 1; if (dst) memcpy(dst_p, src, copy_bytes); dst_p += copy_bytes; src_len -= copy_bytes; src = p + 1; } if (!replaced) return -1; return dst_p - dst; } void dtoverlay_set_intra_fragment_merged_callback(void (*callback)(DTBLOB_T *, int, int)) { intra_fragment_merged_callback = callback; } void dtoverlay_set_cell_changed_callback(void (*callback)(DTBLOB_T *, int, const char *, int, int)) { cell_changed_callback = callback; } /* Returns a pointer to the override data and (through data_len) its length. On error, sets *data_len to be the error code. */ const char *dtoverlay_find_override(DTBLOB_T *dtb, const char *override_name, int *data_len) { int overrides_off; const char *data; int len; // Find the table of overrides overrides_off = fdt_path_offset(dtb->fdt, "/__overrides__"); if (overrides_off < 0) { dtoverlay_debug("/__overrides__ node not found"); *data_len = overrides_off; return NULL; } // Locate the property data = fdt_getprop(dtb->fdt, overrides_off, override_name, &len); *data_len = len; if (data) dtoverlay_debug("found override %s", override_name); else dtoverlay_debug("/__overrides__ has no %s property", override_name); return data; } static int hex_digit(char c) { if (c >= '0' && c <= '9') return c - '0'; else if (c >= 'A' && c <= 'F') return 10 + c - 'A'; else if (c >= 'a' && c <= 'f') return 10 + c - 'a'; else return -1; } static int hex_byte(const char *p) { int nib1, nib2; nib1 = hex_digit(p[0]); if (nib1 < 0) return -1; nib2 = hex_digit(p[1]); if (nib2 < 0) return -1; return (nib1 << 4) | nib2; } int dtoverlay_override_one_target(int override_type, const char *override_value, DTBLOB_T *dtb, int node_off, const char *prop_name, int target_phandle, int target_off, int target_size, void *callback_state) { UNUSED(target_phandle); UNUSED(callback_state); int err = 0; if (override_type == DTOVERRIDE_STRING) { char unescaped_value[256]; char *prop_val, *q; const char *p; int prop_len; p = override_value; q = unescaped_value; while (*p) { int c = *(p++); if (c == '\\') { c = *(p++); if (c >= '0' && c <= '7') { c -= '0'; if (*p >= '0' && *p <= '7') { c = (c << 3) + *(p++) - '0'; if (*p >= '0' && *p <= '7') { c = (c << 3) + *(p++) - '0'; if (c > 255) c = -1; } } } else if (c == 'a') c = '\x07'; else if (c == 'b') c = '\b'; else if (c == 'f') c = '\f'; else if (c == 'n') c = '\n'; else if (c == 'r') c = '\r'; else if (c == 't') c = '\t'; else if (c == 'v') c = '\v'; else if (c == 'x') c = hex_byte(p), p += 2; else if (c != '\\' && c != '\'' && c != '"') c = -1; if (c < 0) { dtoverlay_error("invalid escape in '%s'", override_value); return NON_FATAL(FDT_ERR_BADVALUE); } } *(q++) = (char)c; } *(q++) = '\0'; /* Append to or replace the property string */ if ((strcmp(prop_name, "bootargs") == 0) && ((prop_val = fdt_getprop_w(dtb->fdt, node_off, prop_name, &prop_len)) != NULL) && (prop_len > 0) && prop_val[0]) { prop_val[prop_len - 1] = ' '; err = fdt_appendprop(dtb->fdt, node_off, prop_name, unescaped_value, q - unescaped_value); } else if (strcmp(prop_name, "name") == 0) // "name" is a pseudo-property { err = dtoverlay_set_node_name(dtb, node_off, unescaped_value); } else err = fdt_setprop(dtb->fdt, node_off, prop_name, unescaped_value, q - unescaped_value); } else if (override_type == DTOVERRIDE_BYTE_STRING) { /* Replace the whole property with the byte string */ uint8_t bytes_buf[32]; // For efficiency/laziness, place a limit on the length const char *p = override_value; int byte_count = 0; while (*p) { int byteval; // whitespace and colons are legal separators if (*p == ':' || *p == ' ' || *p == '\t') { p++; continue; } byteval = hex_byte(p); if (byteval < 0) { dtoverlay_error("invalid bytestring at '%s'", p); return NON_FATAL(FDT_ERR_BADVALUE); } if (byte_count == sizeof(bytes_buf)) { dtoverlay_error("bytestring '%s' too long", override_value); return NON_FATAL(FDT_ERR_BADVALUE); } bytes_buf[byte_count++] = byteval; p += 2; } err = fdt_setprop(dtb->fdt, node_off, prop_name, bytes_buf, byte_count); } else if (override_type != DTOVERRIDE_END) { const char *p; char *end; char *prop_val; void *prop_buf = NULL; int prop_len; int new_prop_len; uint64_t override_int; uint32_t frag_num; /* Parse as an integer */ override_int = strtoull(override_value, &end, 0); if (end[0] != '\0') { if ((strcmp(override_value, "y") == 0) || (strcmp(override_value, "yes") == 0) || (strcmp(override_value, "on") == 0) || (strcmp(override_value, "true") == 0) || (strcmp(override_value, "down") == 0)) override_int = 1; else if ((strcmp(override_value, "n") == 0) || (strcmp(override_value, "no") == 0) || (strcmp(override_value, "off") == 0) || (strcmp(override_value, "false") == 0)) override_int = 0; else if (strcmp(override_value, "up") == 0) override_int = 2; else { dtoverlay_error("invalid override value '%s' - ignored", override_value); return NON_FATAL(FDT_ERR_INTERNAL); } } switch (override_type) { case DTOVERRIDE_INTEGER: /* Patch a word within the property */ prop_val = (void *)fdt_getprop(dtb->fdt, node_off, prop_name, &prop_len); new_prop_len = target_off + target_size; if (prop_len < new_prop_len) { /* This property either doesn't exist or isn't long enough. Create a buffer containing any existing property data with zero padding, which will later be patched and written back. */ prop_buf = calloc(1, new_prop_len); if (!prop_buf) { dtoverlay_error(" out of memory"); return NON_FATAL(FDT_ERR_NOSPACE); } if (prop_len > 0) memcpy(prop_buf, prop_val, prop_len); prop_val = prop_buf; } switch (target_size) { case 1: dtoverlay_write_u8(prop_val, target_off, (uint32_t)override_int); break; case 2: dtoverlay_write_u16(prop_val, target_off, (uint32_t)override_int); break; case 4: dtoverlay_write_u32(prop_val, target_off, (uint32_t)override_int); break; case 8: dtoverlay_write_u64(prop_val, target_off, override_int); break; default: break; } if (prop_buf) { /* Add/extend the property by setting it */ if (strcmp(prop_name, "reg") != 0) // Don't create or extend "reg" - it must be a pseudo-property err = fdt_setprop(dtb->fdt, node_off, prop_name, prop_buf, new_prop_len); free(prop_buf); } if (strcmp(prop_name, "reg") == 0 && target_off == 0) { const char *old_name = fdt_get_name(dtb->fdt, node_off, NULL); const char *atpos = strchr(old_name, '@'); if (atpos) { int name_len = (atpos - old_name); char *new_name = malloc(name_len + 1 + 16 + 1); if (!new_name) return -FDT_ERR_NOSPACE; sprintf(new_name, "%.*s@%x", name_len, old_name, (uint32_t)override_int); err = dtoverlay_set_node_name(dtb, node_off, new_name); free(new_name); } } break; case DTOVERRIDE_BOOLEAN: case DTOVERRIDE_BOOLEAN_INV: /* This is a boolean property (present->true, absent->false) */ if (override_int ^ (override_type == DTOVERRIDE_BOOLEAN_INV)) err = fdt_setprop(dtb->fdt, node_off, prop_name, NULL, 0); else { err = fdt_delprop(dtb->fdt, node_off, prop_name); if (err == -FDT_ERR_NOTFOUND) err = 0; } break; case DTOVERRIDE_OVERLAY: /* Apply the overlay-wide override. The supported options are ( is a fragment number): + Enable a fragment - Disable a fragment = Enable/disable the fragment according to the override value ! Disable/enable the fragment according to the inverse of the override value */ p = prop_name; while (*p && !err) { char type = *p; int frag_off; switch (type) { case '+': case '-': case '=': case '!': frag_num = strtoul(p + 1, &end, 0); if (end != p) { /* Change fragment@/__overlay__<->__dormant__ as necessary */ const char *states[2] = { "__dormant__", "__overlay__" }; char node_name[24]; int active = (type == '+') || ((type == '=') && (override_int != 0)) || ((type == '!') && (override_int == 0)); snprintf(node_name, sizeof(node_name), "/fragment@%u", frag_num); frag_off = fdt_path_offset(dtb->fdt, node_name); if (frag_off < 0) { snprintf(node_name, sizeof(node_name), "/fragment-%u", frag_num); frag_off = fdt_path_offset(dtb->fdt, node_name); } if (frag_off >= 0) { frag_off = fdt_subnode_offset(dtb->fdt, frag_off, states[!active]); if (frag_off >= 0) (void)dtoverlay_set_node_name(dtb, frag_off, states[active]); } else { dtoverlay_error(" fragment %u not found", frag_num); err = NON_FATAL(frag_off); } p = end; } else { dtoverlay_error(" invalid overlay override '%s'", prop_name); err = NON_FATAL(FDT_ERR_BADVALUE); } break; default: err = NON_FATAL(FDT_ERR_BADVALUE); break; } } break; } } if (!err && cell_changed_callback && cell_source && override_type == DTOVERRIDE_INTEGER && target_size == 4) (*cell_changed_callback)(dtb, node_off, prop_name, target_off, (int)(cell_source - override_data_start)); return err; } /* The problem is the split between inline string values and inline cell values passed to the callback. For strings properties the returned data is strings; no conversion from cells is required. The special handling for "status" is performed before the callback. For all other property types the returned values are binary/opaque data. Any string data should have been converted to binary data already in the framework. Translation: 1. The override value (the value assigned to the parameter) is always a string. 2. Strings are converted according to type of the parameter at the point of use. 3. A single override value can result in multiple different values being assigned to properties as the result of type conversions and set lookups. 4. Cell literals have a binary value. 5. Lookups convert strings to either a string or a cell literal. 6. Cell literals are primarily (only?) useful for label references, which are really just integers. There is nothing stopping them (or other integers) being converted to strings. 7. Therefore dtoverlay_extract_override always returns a string value, either the input override value, a literal, or the result of a lookup. */ // Returns 0 on success, -ve for fatal errors and +ve for non-fatal errors // After calling this, assume all node offsets are no longer valid int dtoverlay_foreach_override_target(DTBLOB_T *dtb, const char *override_name, const char *override_data, int data_len, const char *override_value, override_callback_t callback, void *callback_state) { int err = 0; int target_phandle = 0; char *data_buf, *data, *data_end; /* Short-circuit the degenerate case of an empty parameter, avoiding an apparent memory allocation failure. */ if (!data_len) return 0; /* Copy the override data in case it moves */ data_buf = malloc(data_len); if (!data_buf) { dtoverlay_error(" out of memory"); return NON_FATAL(FDT_ERR_NOSPACE); } memcpy(data_buf, override_data, data_len); data = data_buf; data_end = data + data_len; override_data_start = data_buf; while (err == 0) { const char *target_prop = NULL; static char prop_name[256]; static char target_value[256]; int name_len = 0; int target_off = 0; int target_size = 0; int override_type; int node_off = 0; strcpy(target_value, override_value); override_type = dtoverlay_extract_override(override_name, target_value, sizeof(target_value), &target_phandle, (const char **)&data, data_end, &target_prop, &name_len, &target_off, &target_size); if (override_type < 0) { err = override_type; break; } if (target_phandle != 0) { node_off = fdt_node_offset_by_phandle(dtb->fdt, target_phandle); if (node_off < 0) { dtoverlay_error(" phandle %d not found", target_phandle); err = NON_FATAL(node_off); break; } } /* Sadly there are no '_namelen' setprop variants, so copies are required */ if (target_prop) { memcpy(prop_name, target_prop, name_len); prop_name[name_len] = '\0'; } /* Pass DTOVERRIDE_END to the callback, in case it is interested */ err = callback(override_type, target_value, dtb, node_off, prop_name, target_phandle, target_off, target_size, callback_state); if (override_type == DTOVERRIDE_END) break; } free(data_buf); return err; } // Returns 0 on success, -ve for fatal errors and +ve for non-fatal errors int dtoverlay_apply_override(DTBLOB_T *dtb, const char *override_name, const char *override_data, int data_len, const char *override_value) { return dtoverlay_foreach_override_target(dtb, override_name, override_data, data_len, override_value, dtoverlay_override_one_target, NULL); } /* Returns an override type (DTOVERRIDE_INTEGER, DTOVERRIDE_BOOLEAN, DTOVERRIDE_STRING, DTOVERRIDE_OVERLAY), DTOVERRIDE_END (0) at the end, or an error code (< 0) */ static int dtoverlay_extract_override(const char *override_name, char *override_value, int value_size, int *phandle_ptr, const char **datap, const char *data_end, const char **namep, int *namelenp, int *offp, int *sizep) { const char *data; const char *prop_name, *override_end; int len, override_len, name_len, target_len, phandle; const char *offset_seps = ".;:#?![{="; const char *literal_value = NULL; char literal_type = '?'; int type; cell_source = NULL; data = *datap; len = data_end - data; if (len <= 0) { if (len < 0) return len; *phandle_ptr = 0; *namep = NULL; return DTOVERRIDE_END; } // Check for space for a phandle, a terminating NUL and at least one char if (len < ((int)sizeof(fdt32_t) + 1 + 1)) { dtoverlay_error(" override %s: data is truncated or mangled", override_name); return -FDT_ERR_BADSTRUCTURE; } phandle = dtoverlay_read_u32(data, 0); *phandle_ptr = phandle; data += sizeof(fdt32_t); len -= sizeof(fdt32_t); override_end = memchr(data, 0, len); if (!override_end) { dtoverlay_error(" override %s: string is not NUL-terminated", override_name); return -FDT_ERR_BADSTRUCTURE; } prop_name = data; override_len = override_end - prop_name; data += (override_len + 1); *datap = data; if (phandle <= 0) { if (phandle < 0) return -FDT_ERR_BADPHANDLE; /* This is an "overlay" override, signalled using <0> as the phandle. */ *namep = prop_name; *namelenp = override_len; return DTOVERRIDE_OVERLAY; } target_len = strcspn(prop_name, "={"); name_len = strcspn(prop_name, offset_seps); *namep = prop_name; *namelenp = name_len; if (target_len < override_len) { /* Literal assignment or lookup table * Can't have '=' and '{' (or at least, don't need to support it. * = is an override value replacement * { is an override value transformation */ literal_type = prop_name[target_len]; literal_value = prop_name + target_len + 1; } if (name_len < target_len) { /* There is a separator specified */ char sep = prop_name[name_len]; if (sep == '?') { /* The target is a boolean parameter (present->true, absent->false) */ *offp = 0; *sizep = 0; type = DTOVERRIDE_BOOLEAN; dtoverlay_debug(" override %s: boolean target %.*s", override_name, name_len, prop_name); } else if (sep == '!') { /* The target is a boolean parameter (present->true, absent->false), * but the sense of the value is inverted */ *offp = 0; *sizep = 0; type = DTOVERRIDE_BOOLEAN_INV; dtoverlay_debug(" override %s: inverted boolean target %.*s", override_name, name_len, prop_name); } else if (sep == '[') { /* The target is a byte-string */ *offp = -1; *sizep = 0; type = DTOVERRIDE_BYTE_STRING; dtoverlay_debug(" override %s: byte-string target %.*s", override_name, name_len, prop_name); } else { /* The target is a cell/integer */ *offp = atoi(prop_name + name_len + 1); *sizep = 1 << (strchr(offset_seps, sep) - offset_seps); type = DTOVERRIDE_INTEGER; dtoverlay_debug(" override %s: cell target %.*s @ offset %d (size %d)", override_name, name_len, prop_name, *offp, *sizep); } } else { *offp = -1; *sizep = 0; type = DTOVERRIDE_STRING; dtoverlay_debug(" override %s: string target '%.*s'", override_name, name_len, prop_name); } if (literal_value) { if (literal_type == '=') { /* Immediate value */ if (type == DTOVERRIDE_STRING || type == DTOVERRIDE_BYTE_STRING || literal_value[0]) { /* String */ if (type == DTOVERRIDE_STRING && !literal_value[0]) { /* The empty string is a special case indicating that the literal * string follows the NUL. This could appear as * "foo=","bar"; * but the expecged use case is to support label paths: * "console=",&uart1; * which dtc will expand to something like: * "console=","/soc/serial@7e215040"; * Note that the corollary of this is that assigning an empty * string (not a likely scenario, and not one encountered at the * time of writing) requires an empty string to appear * immediately afterwards: * <&aliases>,"console=","",<&node>,"other:0"; * or that the empty string assignment is at the end: * <&aliases>,"console="; * although the same effect can be achieved with: * <&aliases>,"console[=00"; */ len = data_end - data; if (!len) { /* end-of-property case - treat as an empty string */ literal_value = data - 1; override_len = 1; } else { override_end = memchr(data, 0, len); if (!override_end) { dtoverlay_error(" override %s: string is not NUL-terminated", override_name); return -FDT_ERR_BADSTRUCTURE; } literal_value = data; data = override_end + 1; } *datap = data; } strcpy(override_value, literal_value); } else { /* Cell */ sprintf(override_value, "%u", dtoverlay_read_u32(data, 0)); cell_source = data; *datap = data + 4; } } else if (literal_type == '{') { /* Lookup */ data = dtoverlay_lookup_key(literal_value, data_end, override_value, override_value, value_size); *datap = data; if (!data) return -FDT_ERR_BADSTRUCTURE; } else { return -FDT_ERR_INTERNAL; } } if ((type == DTOVERRIDE_STRING) && (strmemcmp(prop_name, name_len, "status") == 0)) { /* Convert booleans to okay/disabled */ if ((strcmp(override_value, "y") == 0) || (strcmp(override_value, "yes") == 0) || (strcmp(override_value, "on") == 0) || (strcmp(override_value, "true") == 0) || (strcmp(override_value, "enable") == 0) || (strcmp(override_value, "1") == 0)) strcpy(override_value, "okay"); else if ((strcmp(override_value, "n") == 0) || (strcmp(override_value, "no") == 0) || (strcmp(override_value, "off") == 0) || (strcmp(override_value, "false") == 0) || (strcmp(override_value, "0") == 0)) strcpy(override_value, "disabled"); } return type; } /* Read the string or (if permitted) cell value, storing the result in buf. Returns a pointer to the first byte after the successfully parsed immediate, or NULL on error. */ static const char *dtoverlay_extract_immediate(const char *data, const char *data_end, char *buf, int buf_len) { if ((data + 1) < data_end && !data[0]) { uint32_t val; data++; if (data + 4 > data_end) { dtoverlay_error(" truncated cell immediate"); return NULL; } val = dtoverlay_read_u32(data, 0); if (buf) { cell_source = data; snprintf(buf, buf_len, "%d", val); } data += 4; } else if (data[0] == '\'') { // Continue to closing "'", error on end-of-string int len; data++; len = strcspn(data, "'"); if (!data[len]) { dtoverlay_error(" unterminated quoted string: '%s", data); return NULL; } if (len >= buf_len) { dtoverlay_error(" immediate string too long: '%s", data); return NULL; } if (buf) { memcpy(buf, data, len); buf[len] = '\0'; } data += len + 1; if (*data == ',') // Skip a comma, preserve a brace data++; } else { // Continue to a comma, right brace or end-of-string NUL int len = strcspn(data, ",}"); if (len >= buf_len) { dtoverlay_error(" immediate string too long: '%s", data); return NULL; } if (buf) { memcpy(buf, data, len); buf[len] = '\0'; } data += len; if (*data == ',') // Skip a comma, preserve a brace data++; } return data; } static const char *dtoverlay_lookup_key(const char *lookup_string, const char *data_end, const char *key, char *buf, int buf_len) { const char *p = lookup_string; int found = 0; while (p < data_end && *p && *p != '}') { int key_len = strcspn(p, "=,}"); char *q = NULL; char sep = p[key_len]; if (!key_len) { if (sep) // default value { if (!found) { q = buf; found = 2; } } } else { if (found != 1 && strmemcmp(p, key_len, key) == 0) { q = buf; found = 1; } } p += key_len; if (sep == '=') { p = dtoverlay_extract_immediate(p + 1, data_end, q, buf_len); } else { if (q && q != key) { strncpy(q, key, buf_len); q[buf_len - 1] = 0; } if (sep == ',') p++; } } if (!found) { dtoverlay_error("lookup -> no match for '%s'", key); return NULL; } if (p == data_end) return p; if (!*p) { dtoverlay_error(" malformed lookup"); return NULL; } assert(p[0] != 0 && p[1] == 0); return p + 2; } int dtoverlay_set_synonym(DTBLOB_T *dtb, const char *dst, const char *src) { /* Add/update all aliases, symbols and overrides named dst to be equivalent to those named src. An absent src is ignored. */ int err; err = dtoverlay_dup_property(dtb, "/aliases", dst, src); if (err == 0) err = dtoverlay_dup_property(dtb, "/__symbols__", dst, src); if (err == 0) dtoverlay_dup_property(dtb, "/__overrides__", dst, src); return err; } int dtoverlay_dup_property(DTBLOB_T *dtb, const char *node_name, const char *dst, const char *src) { /* Find the node and src property */ const DTBLOB_T *src_prop; int node_off; int prop_len = 0; int err = 0; node_off = fdt_path_offset(dtb->fdt, node_name); if (node_off < 0) return 0; src_prop = fdt_getprop(dtb->fdt, node_off, src, &prop_len); if (!src_prop) return 0; err = fdt_setprop_inplace(dtb->fdt, node_off, dst, src_prop, prop_len); if (err != 0) { void *prop_data; /* Copy the src property, just in case things move */ prop_data = malloc(prop_len); memcpy(prop_data, src_prop, prop_len); err = fdt_setprop(dtb->fdt, node_off, dst, prop_data, prop_len); free(prop_data); } if (err == 0) dtoverlay_debug("%s:%s=%s", node_name, dst, src); return err; } int dtoverlay_find_pins_for_device(DTBLOB_T *dtb, const char *symbol, PIN_ITER_T *iter) { int pos = dtoverlay_find_symbol(dtb, symbol); memset(iter, 0, sizeof(*iter)); if (pos < 0) return pos; iter->dtb = dtb; if (dtoverlay_node_is_enabled(dtb, pos)) iter->pinctrl = dtoverlay_get_property(dtb, pos, "pinctrl-0", &iter->pinctrl_len); return 0; } int dtoverlay_next_pin(PIN_ITER_T *iter, int *pin, int *func, int *pull) { if (pin) *pin = -1; if (func) *func = -1; if (pull) *pull = -1; while (1) { int phandle, pos; if ((iter->pin_off) + 4 <= iter->pins_len) { int off = iter->pin_off; if (pin) *pin = GETBE4(iter->pins, off); if (func && iter->funcs_len) *func = GETBE4(iter->funcs, (iter->funcs_len > 4) ? off : 0); if (pull && iter->pulls_len) *pull = GETBE4(iter->pulls, (iter->pulls_len > 4) ? off : 0); iter->pin_off = off + 4; return 1; } if ((iter->pinctrl_off + 4) > iter->pinctrl_len) break; phandle = GETBE4(iter->pinctrl, iter->pinctrl_off); iter->pinctrl_off += 4; pos = dtoverlay_find_phandle(iter->dtb, phandle); iter->pins = dtoverlay_get_property(iter->dtb, pos, "brcm,pins", &iter->pins_len); iter->funcs = dtoverlay_get_property(iter->dtb, pos, "brcm,function", &iter->funcs_len); iter->pulls = dtoverlay_get_property(iter->dtb, pos, "brcm,pull", &iter->pulls_len); iter->pin_off = 0; } return 0; } DTBLOB_T *dtoverlay_create_dtb(int max_size) { DTBLOB_T *dtb = NULL; void *fdt = NULL; fdt = malloc(max_size); if (!fdt) { dtoverlay_error("out of memory"); goto error_exit; } if (fdt_create_empty_tree(fdt, max_size) != 0) { dtoverlay_error("failed to create empty dtb"); goto error_exit; } dtb = calloc(1, sizeof(DTBLOB_T)); if (!dtb) { dtoverlay_error("out of memory"); goto error_exit; } dtb->fdt = fdt; dtb->max_phandle = 0; // Not a valid phandle return dtb; error_exit: free(fdt); if (dtb) free(dtb->trailer); free(dtb); return NULL; } DTBLOB_T *dtoverlay_load_dtb_from_fp(FILE *fp, int max_size) { DTBLOB_T *dtb = NULL; void *fdt = NULL; if (fp) { long len; long bytes_read; int dtb_len; fseek(fp, 0, SEEK_END); len = ftell(fp); fseek(fp, 0, SEEK_SET); if (max_size > 0) { if (max_size < len) { dtoverlay_error("file too large (%d bytes) for max_size", len); goto error_exit; } } else if (max_size < 0) { max_size = len - max_size; } else { max_size = len; } fdt = malloc(max_size); if (!fdt) { dtoverlay_error("out of memory"); goto error_exit; } bytes_read = fread(fdt, 1, len, fp); fclose(fp); if (bytes_read != len) { dtoverlay_error("fread failed"); goto error_exit; } // Record the total size before any expansion dtb_len = fdt_totalsize(fdt); dtb = dtoverlay_import_fdt(fdt, max_size); if (!dtb) goto error_exit; dtb->fdt_is_malloced = 1; if (len > dtb_len) { /* Load the trailer */ dtb->trailer_len = len - dtb_len; dtb->trailer = malloc(dtb->trailer_len); if (!dtb->trailer) { dtoverlay_error("out of memory"); goto error_exit; } dtb->trailer_is_malloced = 1; memcpy(dtb->trailer, (char *)fdt + dtb_len, dtb->trailer_len); } } return dtb; error_exit: free(fdt); if (dtb) free(dtb->trailer); free(dtb); return NULL; } DTBLOB_T *dtoverlay_load_dtb(const char *filename, int max_size) { FILE *fp = fopen(filename, "rb"); if (fp) return dtoverlay_load_dtb_from_fp(fp, max_size); dtoverlay_error("failed to open '%s'", filename); return NULL; } void dtoverlay_init_map_from_fp(FILE *fp, const char *compatible, int compatible_len) { if (!compatible) return; while (compatible_len > 0) { const char *p; int len; // Look for a string containing a comma p = memchr(compatible, ',', compatible_len); if (p) { p++; len = compatible + compatible_len - p; } else { // Otherwise treat it as a simple string p = compatible; len = compatible_len; } /* Group the members of the BCM2835 family */ if (strncmp(p, "bcm2708", len) == 0 || strncmp(p, "bcm2709", len) == 0 || strncmp(p, "bcm2710", len) == 0 || strncmp(p, "bcm2835", len) == 0 || strncmp(p, "bcm2836", len) == 0 || strncmp(p, "bcm2837", len) == 0) { platform_name = "bcm2835"; break; } else if (strncmp(p, "bcm2711", len) == 0) { platform_name = "bcm2711"; break; } else if (strncmp(p, "bcm2712", len) == 0) { platform_name = "bcm2712"; break; } compatible_len -= (p - compatible); compatible = p; len = strnlen(compatible, compatible_len) + 1; compatible += len; compatible_len -= len; } if (platform_name) { dtoverlay_debug("using platform '%s'", platform_name); platform_name_len = strlen(platform_name); if (fp) overlay_map = dtoverlay_load_dtb_from_fp(fp, 0); } else { dtoverlay_warn("no matching platform found"); } dtoverlay_debug("overlay map %sloaded", overlay_map ? "" : "not "); } void dtoverlay_init_map(const char *overlay_dir, const char *compatible, int compatible_len) { char map_file[DTOVERLAY_MAX_PATH]; int dir_len = strlen(overlay_dir); FILE *fp; static int tried; if (tried) return; tried = 1; if (!compatible) return; /* Handle the possibility that the supplied directory may or may not end with a slash */ sprintf(map_file, "%s%soverlay_map.dtb", overlay_dir, (!dir_len || overlay_dir[dir_len - 1] != '/') ? "/" : ""); fp = fopen(map_file, "rb"); dtoverlay_init_map_from_fp(fp, compatible, compatible_len); } const char *dtoverlay_remap_overlay(const char *overlay) { while (overlay_map) { const char *deprecated_msg; const char *new_name; int root_off; int overlay_off; int prop_len; root_off = fdt_path_offset(overlay_map->fdt, "/"); overlay_off = fdt_subnode_offset(overlay_map->fdt, root_off, overlay); if (overlay_off < 0) break; new_name = fdt_getprop_namelen(overlay_map->fdt, overlay_off, platform_name, platform_name_len, &prop_len); if (new_name) { if (new_name[0]) overlay = new_name; break; } // Has it been renamed or deprecated? new_name = fdt_getprop_namelen(overlay_map->fdt, overlay_off, "renamed", 7, &prop_len); if (new_name) { dtoverlay_warn("overlay '%s' has been renamed '%s'", overlay, new_name); overlay = new_name; continue; } deprecated_msg = fdt_getprop_namelen(overlay_map->fdt, overlay_off, "deprecated", 10, &prop_len); if (deprecated_msg) dtoverlay_error("overlay '%s' is deprecated: %s", overlay, deprecated_msg); else dtoverlay_error("overlay '%s' is not supported on the '%s' platform", overlay, platform_name); return NULL; } return overlay; } DTBLOB_T *dtoverlay_import_fdt(void *fdt, int buf_size) { DTBLOB_T *dtb = NULL; int node_off; int dtb_len; int err; err = fdt_check_header(fdt); if (err != 0) { dtoverlay_error("not a valid FDT - err %d", err); goto error_exit; } dtb_len = fdt_totalsize(fdt); if (buf_size < dtb_len) { dtoverlay_error("fdt is too large"); goto error_exit; } if (buf_size > dtb_len) fdt_set_totalsize(fdt, buf_size); dtb = calloc(1, sizeof(DTBLOB_T)); if (!dtb) { dtoverlay_error("out of memory"); goto error_exit; } dtb->fdt = fdt; dtb->max_phandle = 0; // Not a valid phandle // Find the minimum and maximum phandles, in case it is necessary to // relocate existing ones or create new ones. for (node_off = 0; node_off >= 0; node_off = fdt_next_node(fdt, node_off, NULL)) { uint32_t phandle = fdt_get_phandle(fdt, node_off); if (phandle > dtb->max_phandle) dtb->max_phandle = phandle; } error_exit: return dtb; } int dtoverlay_save_dtb(const DTBLOB_T *dtb, const char *filename) { FILE *fp = fopen(filename, "wb"); int err = 0; if (fp) { int len = fdt_totalsize(dtb->fdt); if (fwrite(dtb->fdt, len, 1, fp) != 1) { dtoverlay_error("fwrite failed"); err = -2; goto error_exit; } if (dtb->trailer_len && (fwrite(dtb->trailer, dtb->trailer_len, 1, fp) != 1)) { dtoverlay_error("fwrite failed"); err = -2; goto error_exit; } dtoverlay_debug("wrote %ld bytes to '%s'", len, filename); fclose(fp); } else { dtoverlay_debug("failed to create '%s'", filename); err = -1; } error_exit: return err; } int dtoverlay_extend_dtb(DTBLOB_T *dtb, int new_size) { int size = fdt_totalsize(dtb->fdt); int err = 0; if (new_size < 0) new_size = size - new_size; if (new_size > size) { void *fdt; fdt = malloc(new_size); if (fdt) { memcpy(fdt, dtb->fdt, size); fdt_set_totalsize(fdt, new_size); if (dtb->fdt_is_malloced) free(dtb->fdt); dtb->fdt = fdt; dtb->fdt_is_malloced = 1; } else { err = -FDT_ERR_NOSPACE; } } else if (new_size < size) { /* Can't shrink it */ err = -FDT_ERR_NOSPACE; } return err; } int dtoverlay_dtb_totalsize(DTBLOB_T *dtb) { return fdt_totalsize(dtb->fdt); } void dtoverlay_pack_dtb(DTBLOB_T *dtb) { fdt_pack(dtb->fdt); } void dtoverlay_free_dtb(DTBLOB_T *dtb) { if (dtb) { if (dtb->fdt_is_malloced) free(dtb->fdt); if (dtb->trailer_is_malloced) free(dtb->trailer); free(dtb); } } int dtoverlay_find_phandle(DTBLOB_T *dtb, int phandle) { return fdt_node_offset_by_phandle(dtb->fdt, phandle); } int dtoverlay_find_symbol(DTBLOB_T *dtb, const char *symbol_name) { int symbols_off, path_len; const char *node_path; node_path = dtoverlay_get_alias(dtb, symbol_name); if (node_path) { path_len = strlen(node_path); } else { symbols_off = fdt_path_offset(dtb->fdt, "/__symbols__"); if (symbols_off < 0) { dtoverlay_error("no symbols found"); return -FDT_ERR_NOTFOUND; } node_path = fdt_getprop(dtb->fdt, symbols_off, symbol_name, &path_len); if (path_len < 0) return -FDT_ERR_NOTFOUND; //Ensure we don't have trailing NULLs if (path_len > (int)strnlen(node_path, path_len)) path_len = (int)strnlen(node_path, path_len); } return fdt_path_offset_namelen(dtb->fdt, node_path, path_len); } int dtoverlay_find_matching_node(DTBLOB_T *dtb, const char **node_names, int pos) { while (1) { const char *node_name; pos = fdt_next_node(dtb->fdt, pos, NULL); if (pos < 0) break; node_name = fdt_get_name(dtb->fdt, pos, NULL); if (node_name) { int i; for (i = 0; node_names[i]; i++) { const char *node = node_names[i]; int matchlen = strlen(node); if ((strncmp(node_name, node, matchlen) == 0) && ((node[matchlen] == '\0') || (node[matchlen] == '@'))) return pos; } } } return -1; } int dtoverlay_node_is_enabled(DTBLOB_T *dtb, int pos) { if (pos >= 0) { const void *prop = dtoverlay_get_property(dtb, pos, "status", NULL); if (prop && ((strcmp((const char *)prop, "okay") == 0) || (strcmp((const char *)prop, "ok") == 0))) return 1; } return 0; } const void *dtoverlay_get_property(DTBLOB_T *dtb, int pos, const char *prop_name, int *prop_len) { return fdt_getprop(dtb->fdt, pos, prop_name, prop_len); } int dtoverlay_set_property(DTBLOB_T *dtb, int pos, const char *prop_name, const void *prop, int prop_len) { int err = fdt_setprop(dtb->fdt, pos, prop_name, prop, prop_len); if (err < 0) dtoverlay_error("failed to set property '%s'", prop_name); return err; } const char *dtoverlay_get_alias(DTBLOB_T *dtb, const char *alias_name) { int node_off; int prop_len; const char *alias; node_off = fdt_path_offset(dtb->fdt, "/aliases"); alias = fdt_getprop(dtb->fdt, node_off, alias_name, &prop_len); if (alias && !prop_len) alias = ""; return alias; } int dtoverlay_set_alias(DTBLOB_T *dtb, const char *alias_name, const char *value) { int node_off; node_off = fdt_path_offset(dtb->fdt, "/aliases"); if (node_off < 0) node_off = fdt_add_subnode(dtb->fdt, 0, "aliases"); return fdt_setprop_string(dtb->fdt, node_off, alias_name, value); } void dtoverlay_set_logging_func(DTOVERLAY_LOGGING_FUNC *func) { dtoverlay_logging_func = func; } void dtoverlay_enable_debug(int enable) { dtoverlay_debug_enabled = enable; } void dtoverlay_error(const char *fmt, ...) { va_list args; va_start(args, fmt); (*dtoverlay_logging_func)(DTOVERLAY_ERROR, fmt, args); va_end(args); } void dtoverlay_warn(const char *fmt, ...) { va_list args; va_start(args, fmt); (*dtoverlay_logging_func)(DTOVERLAY_WARN, fmt, args); va_end(args); } void dtoverlay_debug(const char *fmt, ...) { va_list args; if (dtoverlay_debug_enabled) { va_start(args, fmt); (*dtoverlay_logging_func)(DTOVERLAY_DEBUG, fmt, args); va_end(args); } } static void dtoverlay_stdio_logging(dtoverlay_logging_type_t type, const char *fmt, va_list args) { const char *type_str; switch (type) { case DTOVERLAY_ERROR: type_str = "error"; break; case DTOVERLAY_WARN: type_str = "warn"; break; case DTOVERLAY_DEBUG: type_str = "debug"; break; default: type_str = "?"; } fprintf(stderr, "DTOVERLAY[%s]: ", type_str); vfprintf(stderr, fmt, args); fprintf(stderr, "\n"); } raspi-utils-20240903/dtmerge/dtoverlay.h000066400000000000000000000231311466562725600200670ustar00rootroot00000000000000/* Copyright (c) 2016-2023 Raspberry Pi Ltd. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef DTOVERLAY_H #define DTOVERLAY_H #include #define BE4(x) ((x)>>24)&0xff, ((x)>>16)&0xff, ((x)>>8)&0xff, ((x)>>0)&0xff #define GETBE4(p, off) ((((unsigned char *)p)[off + 0]<<24) + (((unsigned char *)p)[off + 1]<<16) + \ (((unsigned char *)p)[off + 2]<<8) + (((unsigned char *)p)[off + 3]<<0)) #define SETBE4(p, off, x) do { \ ((unsigned char *)p)[off + 0] = ((x>>24) & 0xff); \ ((unsigned char *)p)[off + 1] = ((x>>16) & 0xff); \ ((unsigned char *)p)[off + 2] = ((x>>8) & 0xff); \ ((unsigned char *)p)[off + 3] = ((x>>0) & 0xff); \ } while (0) #define NON_FATAL(err) (((err) < 0) ? -(err) : (err)) #define IS_FATAL(err) ((err) < 0) #define ONLY_FATAL(err) (IS_FATAL(err) ? (err) : 0) #define DTOVERLAY_PADDING(size) (-(size)) #define DTOVERLAY_MAX_PATH 256 typedef enum { DTOVERLAY_ERROR, DTOVERLAY_DEBUG, DTOVERLAY_WARN, // Append to preserve backwards compatibility } dtoverlay_logging_type_t; typedef struct dtoverlay_struct { const char *param; int len; const char *b; } DTOVERLAY_PARAM_T; typedef struct dtblob_struct { void *fdt; char fdt_is_malloced; char trailer_is_malloced; char fixups_applied; uint32_t min_phandle; uint32_t max_phandle; void *trailer; int trailer_len; } DTBLOB_T; typedef struct pin_iter_struct { DTBLOB_T *dtb; const void *pinctrl; int pinctrl_len; int pinctrl_off; const void *pins; const void *funcs; const void *pulls; int pins_len; int pin_off; int funcs_len; int pulls_len; } PIN_ITER_T; typedef void DTOVERLAY_LOGGING_FUNC(dtoverlay_logging_type_t type, const char *fmt, va_list args); typedef int (*override_callback_t)(int override_type, const char *override_value, DTBLOB_T *dtb, int node_off, const char *prop_name, int target_phandle, int target_off, int target_size, void *callback_state); uint8_t dtoverlay_read_u8(const void *src, int off); uint16_t dtoverlay_read_u16(const void *src, int off); uint32_t dtoverlay_read_u32(const void *src, int off); uint64_t dtoverlay_read_u64(const void *src, int off); void dtoverlay_write_u8(void *dst, int off, uint32_t val); void dtoverlay_write_u16(void *dst, int off, uint32_t val); void dtoverlay_write_u32(void *dst, int off, uint32_t val); void dtoverlay_write_u64(void *dst, int off, uint64_t val); /* Return values: -ve = fatal error, positive = non-fatal error */ int dtoverlay_create_node(DTBLOB_T *dtb, const char *node_name, int path_len); int dtoverlay_delete_node(DTBLOB_T *dtb, const char *node_name, int path_len); int dtoverlay_find_node(DTBLOB_T *dtb, const char *node_path, int path_len); int dtoverlay_first_subnode(DTBLOB_T *dtb, int node_off); int dtoverlay_next_subnode(DTBLOB_T *dtb, int subnode_off); int dtoverlay_set_node_properties(DTBLOB_T *dtb, const char *node_path, DTOVERLAY_PARAM_T *properties, unsigned int num_properties); int dtoverlay_create_prop_fragment(DTBLOB_T *dtb, int idx, int target_phandle, const char *prop_name, const void *prop_data, int prop_len); int dtoverlay_merge_fragment(DTBLOB_T *base_dtb, int target_off, const DTBLOB_T *overlay_dtb, int overlay_off, int depth); int dtoverlay_fixup_overlay(DTBLOB_T *base_dtb, DTBLOB_T *overlay_dtb); int dtoverlay_merge_overlay(DTBLOB_T *base_dtb, DTBLOB_T *overlay_dtb); int dtoverlay_merge_params(DTBLOB_T *dtb, const DTOVERLAY_PARAM_T *params, unsigned int num_params); int dtoverlay_filter_symbols(DTBLOB_T *dtb); const char *dtoverlay_find_fixup(DTBLOB_T *dtb, const char *fixup_loc); int dtoverlay_add_fixup(DTBLOB_T *dtb, const char *symbol, const char *fixup_loc); int dtoverlay_delete_fixup(DTBLOB_T *dtb, const char *fixup_loc); int dtoverlay_stringlist_replace(const char *src, int src_len, const char *src_prefix, int src_prefix_len, const char *dst_prefix, int dst_prefix_len, char *dst); void dtoverlay_set_intra_fragment_merged_callback(void (*callback)(DTBLOB_T *, int, int)); void dtoverlay_set_cell_changed_callback(void (*callback)(DTBLOB_T *, int, const char *, int, int)); const char *dtoverlay_find_override(DTBLOB_T *dtb, const char *override_name, int *data_len); int dtoverlay_override_one_target(int override_type, const char *override_value, DTBLOB_T *dtb, int node_off, const char *prop_name, int target_phandle, int target_off, int target_size, void *callback_state); int dtoverlay_foreach_override_target(DTBLOB_T *dtb, const char *override_name, const char *override_data, int data_len, const char *override_value, override_callback_t callback, void *callback_value); int dtoverlay_apply_override(DTBLOB_T *dtb, const char *override_name, const char *override_data, int data_len, const char *override_value); int dtoverlay_set_synonym(DTBLOB_T *dtb, const char *dst, const char *src); int dtoverlay_dup_property(DTBLOB_T *dtb, const char *node_name, const char *dst, const char *src); DTBLOB_T *dtoverlay_create_dtb(int max_size); DTBLOB_T *dtoverlay_load_dtb_from_fp(FILE *fp, int max_size); DTBLOB_T *dtoverlay_load_dtb(const char *filename, int max_size); void dtoverlay_init_map_from_fp(FILE *fp, const char *compatible, int compatible_len); void dtoverlay_init_map(const char *overlay_dir, const char *compatible, int compatible_len); const char *dtoverlay_remap_overlay(const char *overlay); DTBLOB_T *dtoverlay_import_fdt(void *fdt, int max_size); int dtoverlay_save_dtb(const DTBLOB_T *dtb, const char *filename); int dtoverlay_extend_dtb(DTBLOB_T *dtb, int new_size); int dtoverlay_dtb_totalsize(DTBLOB_T *dtb); void dtoverlay_pack_dtb(DTBLOB_T *dtb); void dtoverlay_free_dtb(DTBLOB_T *dtb); static inline void *dtoverlay_dtb_trailer(DTBLOB_T *dtb) { return dtb->trailer; } static inline int dtoverlay_dtb_trailer_len(DTBLOB_T *dtb) { return dtb->trailer_len; } static inline void dtoverlay_dtb_set_trailer(DTBLOB_T *dtb, void *trailer, int trailer_len) { dtb->trailer = trailer; dtb->trailer_len = trailer_len; dtb->trailer_is_malloced = 0; } int dtoverlay_find_pins_for_device(DTBLOB_T *dtb, const char *symbol, PIN_ITER_T *iter); int dtoverlay_next_pin(PIN_ITER_T *iter, int *pin, int *func, int *pull); int dtoverlay_find_phandle(DTBLOB_T *dtb, int phandle); int dtoverlay_find_symbol(DTBLOB_T *dtb, const char *symbol_name); int dtoverlay_find_matching_node(DTBLOB_T *dtb, const char **node_names, int pos); int dtoverlay_node_is_enabled(DTBLOB_T *dtb, int pos); const void *dtoverlay_get_property(DTBLOB_T *dtb, int pos, const char *prop_name, int *prop_len); int dtoverlay_set_property(DTBLOB_T *dtb, int pos, const char *prop_name, const void *prop, int prop_len); const char *dtoverlay_get_alias(DTBLOB_T *dtb, const char *alias_name); int dtoverlay_set_alias(DTBLOB_T *dtb, const char *alias_name, const char *value); void dtoverlay_set_logging_func(DTOVERLAY_LOGGING_FUNC *func); void dtoverlay_enable_debug(int enable); void dtoverlay_error(const char *fmt, ...); void dtoverlay_warn(const char *fmt, ...); void dtoverlay_debug(const char *fmt, ...); #endif raspi-utils-20240903/dtmerge/dtoverlay_main.c000066400000000000000000001154731466562725600211010ustar00rootroot00000000000000/* Copyright (c) 2016-2023 Raspberry Pi Ltd. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include "dtoverlay.h" #include "utils.h" #define CFG_DIR_1 "/sys/kernel/config" #define CFG_DIR_2 "/config" #define DT_SUBDIR "/device-tree" #define WORK_DIR "/tmp/.dtoverlays" #define OVERLAY_SRC_SUBDIR "overlays" #define README_FILE "README" #define DT_OVERLAYS_SUBDIR "overlays" #define DTOVERLAY_PATH_MAX 128 #define DIR_MODE 0755 enum { OPT_ADD, OPT_REMOVE, OPT_REMOVE_FROM, OPT_LIST, OPT_LIST_ALL, OPT_HELP }; static const char *boot_dirs[] = { #ifdef FORCE_BOOT_DIR FORCE_BOOT_DIR, #else "/boot/firmware", "/boot", "/flash", #ifdef OTHER_BOOT_DIR OTHER_BOOT_DIR, #endif #endif NULL /* Terminator */ }; typedef struct state_struct { int count; struct dirent **namelist; } STATE_T; static int dtoverlay_add(STATE_T *state, const char *overlay, int argc, const char **argv); static int dtoverlay_remove(STATE_T *state, const char *overlay, int and_later); static int dtoverlay_list(STATE_T *state); static int dtoverlay_list_all(STATE_T *state); static void usage(void); static void root_check(void); static void overlay_help(const char *overlay, const char **params); static int apply_overlay(const char *overlay_file, const char *overlay); static int overlay_applied(const char *overlay_dir); static STATE_T *read_state(const char *dir); static void free_state(STATE_T *state); const char *cmd_name; const char *work_dir = WORK_DIR; const char *overlay_src_dir; const char *dt_overlays_dir; const char *error_file = NULL; const char *platform_string; int platform_string_len; int dry_run = 0; char cell_source_loc[DTOVERLAY_MAX_PATH]; int cell_source_loc_len; int main(int argc, const char **argv) { int argn = 1; int opt = OPT_ADD; int is_dtparam; const char *overlay = NULL; const char **params = NULL; int ret = 0; STATE_T *state = NULL; const char *cfg_dir; cmd_name = argv[0]; if (strrchr(cmd_name, '/')) cmd_name = strrchr(cmd_name, '/') + 1; is_dtparam = (strcmp(cmd_name, "dtparam") == 0); while ((argn < argc) && (argv[argn][0] == '-')) { const char *arg = argv[argn++]; if (strcmp(arg, "-r") == 0) { if (opt != OPT_ADD) usage(); opt = OPT_REMOVE; } else if (strcmp(arg, "-R") == 0) { if (opt != OPT_ADD) usage(); opt = OPT_REMOVE_FROM; } else if (strcmp(arg, "-D") == 0) { if (opt != OPT_ADD) usage(); dry_run = 1; work_dir = "."; } else if ((strcmp(arg, "-l") == 0) || (strcmp(arg, "--list") == 0)) { if (opt != OPT_ADD) usage(); opt = OPT_LIST; } else if ((strcmp(arg, "-a") == 0) || (strcmp(arg, "--listall") == 0) || (strcmp(arg, "--all") == 0)) { if (opt != OPT_ADD) usage(); opt = OPT_LIST_ALL; } else if (strcmp(arg, "-d") == 0) { if (argn == argc) usage(); overlay_src_dir = argv[argn++]; } else if (strcmp(arg, "-p") == 0) { if (argn == argc) usage(); platform_string = argv[argn++]; platform_string_len = strlen(platform_string) + 1; } else if (strcmp(arg, "-v") == 0) { opt_verbose = 1; } else if (strcmp(arg, "-h") == 0) { opt = OPT_HELP; } else { fprintf(stderr, "* unknown option '%s'\n", arg); usage(); } } if ((opt == OPT_ADD) || (opt == OPT_REMOVE) || (opt == OPT_REMOVE_FROM) || (opt == OPT_HELP)) { if ((argn == argc) && ((!is_dtparam && ((opt == OPT_ADD) || (opt == OPT_HELP))) || (is_dtparam && (opt == OPT_HELP)))) usage(); if (is_dtparam && (opt == OPT_ADD) && (argn == argc)) opt = OPT_HELP; if (is_dtparam && ((opt == OPT_ADD) || (opt == OPT_HELP))) overlay = "dtparam"; else if (argn < argc) overlay = argv[argn++]; } if ((opt == OPT_HELP) && (argn < argc)) { params = &argv[argn]; argn = argc; } if ((opt != OPT_ADD) && (argn != argc)) usage(); dtoverlay_enable_debug(opt_verbose); if (!overlay_src_dir) { /* Find the overlays and README */ const char *os_prefix; int i; /* Check for an os_prefix value */ os_prefix = read_file_as_string("/proc/device-tree/chosen/os_prefix", NULL); if (!os_prefix) os_prefix = ""; for (i = 0; boot_dirs[i]; i++) { overlay_src_dir = sprintf_dup("%s/%s" OVERLAY_SRC_SUBDIR, boot_dirs[i], os_prefix); if (dir_exists(overlay_src_dir)) break; free_string(overlay_src_dir); overlay_src_dir = NULL; } if (!overlay_src_dir) fatal_error("Failed to find overlays directory"); } if (opt == OPT_HELP) { overlay_help(overlay, params); goto orderly_exit; } if (!dir_exists(work_dir)) { if (mkdir(work_dir, DIR_MODE) != 0) fatal_error("Failed to create '%s' - %d", work_dir, errno); } error_file = sprintf_dup("%s/%s", work_dir, "error.dtb"); cfg_dir = CFG_DIR_1 DT_SUBDIR; if (!dry_run && !dir_exists(cfg_dir)) { root_check(); cfg_dir = CFG_DIR_2; if (!dir_exists(cfg_dir)) { if (mkdir(cfg_dir, DIR_MODE) != 0) fatal_error("Failed to create '%s' - %d", cfg_dir, errno); } cfg_dir = CFG_DIR_2 DT_SUBDIR; if (!dir_exists(cfg_dir) && (run_cmd("mount -t configfs none '%s'", cfg_dir) != 0)) fatal_error("Failed to mount configfs - %d", errno); } if (!platform_string) platform_string = read_file_as_string("/proc/device-tree/compatible", &platform_string_len); if (platform_string) dtoverlay_init_map(overlay_src_dir, platform_string, platform_string_len); if (!dry_run) { dt_overlays_dir = sprintf_dup("%s/%s", cfg_dir, DT_OVERLAYS_SUBDIR); if (!dir_exists(dt_overlays_dir)) fatal_error("configfs overlays folder not found - incompatible kernel"); state = read_state(work_dir); if (!state) fatal_error("Failed to read state"); } switch (opt) { case OPT_ADD: case OPT_REMOVE: case OPT_REMOVE_FROM: if (!dry_run) { root_check(); run_cmd("which dtoverlay-pre >/dev/null 2>&1 && dtoverlay-pre"); } break; default: break; } switch (opt) { case OPT_ADD: ret = dtoverlay_add(state, overlay, argc - argn, argv + argn); break; case OPT_REMOVE: ret = dtoverlay_remove(state, overlay, 0); break; case OPT_REMOVE_FROM: ret = dtoverlay_remove(state, overlay, 1); break; case OPT_LIST: ret = dtoverlay_list(state); break; case OPT_LIST_ALL: ret = dtoverlay_list_all(state); break; default: ret = 1; break; } switch (opt) { case OPT_ADD: case OPT_REMOVE: case OPT_REMOVE_FROM: if (!dry_run) run_cmd("which dtoverlay-post >/dev/null 2>&1 && dtoverlay-post"); break; default: break; } orderly_exit: if (state) free_state(state); free_strings(); if ((ret == 0) && error_file) unlink(error_file); return ret; } static int dtparam_callback(int override_type, const char *override_value, DTBLOB_T *dtb, int node_off, const char *prop_name, int target_phandle, int target_off, int target_size, void *callback_state) { STRING_VEC_T *used_props = callback_state; char prop_id[80]; int err; err = dtoverlay_override_one_target(override_type, override_value, dtb, node_off, prop_name, target_phandle, target_off, target_size, callback_state); if ((err == 0) && (target_phandle != 0)) { if (snprintf(prop_id, sizeof(prop_id), "%08x%s", target_phandle, prop_name) < 0) err = FDT_ERR_INTERNAL; else if (string_vec_find(used_props, prop_id, 0) < 0) string_vec_add(used_props, prop_id, 0); } return err; } // Returns 0 on success, -ve for fatal errors and +ve for non-fatal errors static int dtparam_apply(DTBLOB_T *dtb, const char *override_name, const char *override_data, int data_len, const char *override_value, STRING_VEC_T *used_props) { void *data; int err; /* Copy the override data in case it moves */ data = malloc(data_len); if (data) { memcpy(data, override_data, data_len); err = dtoverlay_foreach_override_target(dtb, override_name, data, data_len, override_value, dtparam_callback, used_props); free(data); } else { dtoverlay_error("out of memory"); err = NON_FATAL(FDT_ERR_NOSPACE); } return err; } static void intra_fragment_merged_callback(DTBLOB_T *dtb, int fragment_off, int target_off) { // look for all fixups that refer to the indicated fragment, and rebase them // onto the target char fragment_path[DTOVERLAY_MAX_PATH]; char target_path[DTOVERLAY_MAX_PATH]; char *src_path; char *dst_path; DTBLOB_T clone_dtb; DTBLOB_T *src_dtb; int fragment_path_len; int target_path_len; int fixups_off; int fixup_off; int src_off; int dst_off; fdt_get_path(dtb->fdt, fragment_off, fragment_path, sizeof(fragment_path)); fragment_path_len = strlen(fragment_path); fragment_path[fragment_path_len++] = ':'; fragment_path[fragment_path_len] = '\0'; fdt_get_path(dtb->fdt, target_off, target_path, sizeof(target_path)); target_path_len = strlen(target_path); target_path[target_path_len++] = ':'; target_path[target_path_len] = '\0'; fixups_off = fdt_path_offset(dtb->fdt, "/__fixups__"); if (fixups_off < 0) goto try_local_fixups; for (fixup_off = fdt_first_property_offset(dtb->fdt, fixups_off); fixup_off >= 0; fixup_off = fdt_next_property_offset(dtb->fdt, fixup_off)) { const char *fixups_stringlist; const char *symbol_name; char *new_list = NULL; int list_len; int new_len; int err; int i; fixups_stringlist = fdt_getprop_by_offset(dtb->fdt, fixup_off, &symbol_name, &list_len); for (i = 0; i < 2; i++) { new_len = dtoverlay_stringlist_replace(fixups_stringlist, list_len, fragment_path, fragment_path_len, target_path, target_path_len, new_list); if (new_len < 0) break; if (!new_list) new_list = malloc(new_len); } if (!new_list) continue; err = fdt_setprop(dtb->fdt, fixups_off, symbol_name, new_list, new_len); free(new_list); if (err < 0) break; } try_local_fixups: // Strip off the trailing ':'s fragment_path[--fragment_path_len] = '\0'; target_path[--target_path_len] = '\0'; src_path = sprintf_dup("/__local_fixups__%s", fragment_path); dst_path = sprintf_dup("/__local_fixups__%s", target_path); src_dtb = dtb; src_off = fdt_path_offset(src_dtb->fdt, src_path); if (src_off < 0) // No phandles in the fragment payload return; dst_off = dtoverlay_create_node(dtb, dst_path, 0); if (dst_off < 0) fatal_error("failed to copy local fixups"); if (dst_off < src_off) { // Make a copy of the src, so that it doesn't move int overlay_size = fdt_totalsize(dtb->fdt); void *overlay_copy = malloc(overlay_size); if (!overlay_copy) fatal_error("out of memory"); memcpy(overlay_copy, dtb->fdt, overlay_size); memcpy(&clone_dtb, dtb, sizeof(DTBLOB_T)); clone_dtb.fdt = overlay_copy; src_dtb = &clone_dtb; src_off = fdt_path_offset(src_dtb->fdt, src_path); } if (dtoverlay_merge_fragment(dtb, dst_off, src_dtb, src_off, 1)) fatal_error("failed to copy local fixups"); if (src_dtb != dtb) free(src_dtb->fdt); } static void cell_changed_callback(DTBLOB_T *dtb, int node_off, const char *prop_name, int target_off, int cell_data_offset) { char cell_target_loc[DTOVERLAY_MAX_PATH]; const char *symbol; int path_len; sprintf(cell_source_loc + cell_source_loc_len, "%d", cell_data_offset); // XXX fdt_get_path(dtb->fdt, node_off, cell_target_loc, sizeof(cell_target_loc)); path_len = strlen(cell_target_loc); sprintf(cell_target_loc + path_len, ":%s:%d", prop_name, target_off); // XXX dtoverlay_delete_fixup(dtb, cell_target_loc); symbol = dtoverlay_find_fixup(dtb, cell_source_loc); if (symbol) dtoverlay_add_fixup(dtb, symbol, cell_target_loc); } // Returns 0 on success, -ve for fatal errors and +ve for non-fatal errors static int apply_override(DTBLOB_T *dtb, const char *override_name, const char *override_data, int data_len, const char *override_value) { cell_source_loc_len = sprintf(cell_source_loc, "/__overrides__:%s:", override_name); return dtoverlay_foreach_override_target(dtb, override_name, override_data, data_len, override_value, dtoverlay_override_one_target, NULL); } static int apply_parameter(DTBLOB_T *overlay_dtb, const char *arg, int is_dtparam, STRING_VEC_T *used_props, char **param_string) { const char *param_val = strchr(arg, '='); const char *param, *override; char *p = NULL; int override_len; int err = 0; if (param_val) { int len = (param_val - arg); p = sprintf_dup("%.*s", len, arg); param = p; param_val++; } else { /* Use the default parameter value - true */ param = arg; param_val = "true"; } override = dtoverlay_find_override(overlay_dtb, param, &override_len); if (!override) return error("Unknown parameter '%s'", param); if (is_dtparam) err = dtparam_apply(overlay_dtb, param, override, override_len, param_val, used_props); else err = apply_override(overlay_dtb, param, override, override_len, param_val); if (err != 0) return error("Failed to set %s=%s", param, param_val); *param_string = sprintf_dup("%s %s=%s", *param_string ? *param_string : "", param, param_val); free_string(p); return 0; } static int dtoverlay_add(STATE_T *state, const char *overlay, int argc, const char **argv) { const char *overlay_name; const char *overlay_file; const char *overrides = NULL; char *param_string = NULL; int is_dtparam; DTBLOB_T *base_dtb = NULL; DTBLOB_T *overlay_dtb; STRING_VEC_T used_props; int err; int len; int i; len = strlen(overlay) - 5; is_dtparam = (strcmp(overlay, "dtparam") == 0); if (is_dtparam) { /* Convert /proc/device-tree to a .dtb and load it */ overlay_file = sprintf_dup("%s/%s", work_dir, "base.dtb"); if (run_cmd("dtc -I fs -O dtb -o '%s' /proc/device-tree 1>/dev/null 2>&1", overlay_file) != 0) return error("Failed to read active DTB"); } else if ((len > 0) && (strcmp(overlay + len, ".dtbo") == 0)) { const char *p; overlay_file = overlay; p = strrchr(overlay, '/'); if (p) { overlay = p + 1; len = strlen(overlay) - 5; } overlay = sprintf_dup("%.*s", len, overlay); } else { const char *remapped = dtoverlay_remap_overlay(overlay); int name_len; if (!remapped) return error("Failed to load '%s'", overlay); if (strcmp(overlay, remapped)) dtoverlay_debug("mapped overlay '%s' to '%s'", overlay, remapped); overlay = remapped; name_len = strcspn(overlay, ","); if (overlay[name_len]) overrides = overlay + name_len + 1; overlay = sprintf_dup("%.*s", name_len, overlay); overlay_file = sprintf_dup("%s/%s.dtbo", overlay_src_dir, overlay); } if (dry_run) overlay_name = "dry_run"; else overlay_name = sprintf_dup("%d_%s", state->count, overlay); dtoverlay_debug("loading file '%s'", overlay_file); overlay_dtb = dtoverlay_load_dtb(overlay_file, DTOVERLAY_PADDING(4096)); if (!overlay_dtb) return error("Failed to read '%s'", overlay_file); if (is_dtparam) { base_dtb = overlay_dtb; string_vec_init(&used_props); } dtoverlay_set_cell_changed_callback(&cell_changed_callback); /* Apply any parameters that came from the overlay map */ while (overrides && *overrides) { int override_len = strcspn(overrides, ","); char *override = strndup(overrides, override_len); if (!override) return error("Out of memory applying parameter"); err = apply_parameter(overlay_dtb, override, is_dtparam, &used_props, ¶m_string); free(override); if (err) return err; if (!overrides[override_len]) break; overrides += override_len + 1; } /* Then any supplied on the commad line */ for (i = 0; i < argc; i++) { err = apply_parameter(overlay_dtb, argv[i], is_dtparam, &used_props, ¶m_string); if (err) return err; } dtoverlay_set_cell_changed_callback(NULL); /* Apply any intra-overlay fragments, filtering the symbols */ dtoverlay_set_intra_fragment_merged_callback(intra_fragment_merged_callback); err = dtoverlay_merge_overlay(NULL, overlay_dtb); if (err != 0) return error("Failed to apply intra-overlay fragments"); dtoverlay_set_intra_fragment_merged_callback(NULL); if (is_dtparam) { /* Build an overlay DTB */ overlay_dtb = dtoverlay_create_dtb(2048 + 256 * used_props.num_strings); for (i = 0; i < used_props.num_strings; i++) { int phandle, node_off, prop_len; const char *str, *prop_name; const void *prop_data; str = used_props.strings[i]; sscanf(str, "%8x", &phandle); prop_name = str + 8; node_off = dtoverlay_find_phandle(base_dtb, phandle); prop_data = dtoverlay_get_property(base_dtb, node_off, prop_name, &prop_len); err = dtoverlay_create_prop_fragment(overlay_dtb, i, phandle, prop_name, prop_data, prop_len); } dtoverlay_free_dtb(base_dtb); } if (param_string) dtoverlay_dtb_set_trailer(overlay_dtb, param_string, strlen(param_string) + 1); /* Create a filename with the sequence number */ overlay_file = sprintf_dup("%s/%s.dtbo", work_dir, overlay_name); /* then write the overlay to the file */ dtoverlay_pack_dtb(overlay_dtb); dtoverlay_save_dtb(overlay_dtb, overlay_file); dtoverlay_free_dtb(overlay_dtb); if (!dry_run && !apply_overlay(overlay_file, overlay_name)) { if (error_file) { rename(overlay_file, error_file); free_string(error_file); } return 1; } return 0; } static int dtoverlay_remove(STATE_T *state, const char *overlay, int and_later) { const char *overlay_dir; const char *dir_name = NULL; char *end; int overlay_len; int count = state->count; int rmpos; int i; if (chdir(work_dir) != 0) fatal_error("Failed to chdir to '%s'", work_dir); if (overlay) { overlay_len = strlen(overlay); rmpos = strtoul(overlay, &end, 10); if (end && (*end == '\0')) { if (rmpos >= count) return error("Overlay index (%d) too large", rmpos); dir_name = state->namelist[rmpos]->d_name; } /* Locate the most recent reference to the overlay */ else for (rmpos = count - 1; rmpos >= 0; rmpos--) { const char *left, *right; dir_name = state->namelist[rmpos]->d_name; left = strchr(dir_name, '_'); if (!left) return error("Internal error"); left++; right = strchr(left, '.'); if (!right) return error("Internal error"); if (((right - left) == overlay_len) && (memcmp(overlay, left, overlay_len) == 0)) break; dir_name = NULL; } if (rmpos < 0) return error("Overlay '%s' is not loaded", overlay); } else { if (!count) return error("No overlays loaded"); rmpos = and_later ? 0 : (count - 1); dir_name = state->namelist[rmpos]->d_name; } if (rmpos < count) { /* Unload it and all subsequent overlays in reverse order */ for (i = count - 1; i >= rmpos; i--) { const char *left, *right; left = state->namelist[i]->d_name; right = strrchr(left, '.'); if (!right) return error("Internal error"); overlay_dir = sprintf_dup("%s/%.*s", dt_overlays_dir, right - left, left); if (rmdir(overlay_dir) != 0) return error("Failed to remove directory '%s'", overlay_dir); free_string(overlay_dir); } /* Replay the sequence, deleting files for the specified overlay, and renumbering and reloading all other overlays. */ for (i = rmpos, state->count = rmpos; i < count; i++) { const char *left, *right; const char *filename = state->namelist[i]->d_name; left = strchr(filename, '_'); if (!left) return error("Internal error"); left++; right = strchr(left, '.'); if (!right) return error("Internal error"); if (and_later || (i == rmpos)) { /* This one is being deleted */ unlink(filename); } else { /* Keep this one - renumber and reload */ int len = right - left; char *new_name = sprintf_dup("%d_%.*s", state->count, len, left); char *new_file = sprintf_dup("%s.dtbo", new_name); int ret = 0; if ((len == 7) && (memcmp(left, "dtparam", 7) == 0)) { /* Regenerate the overlay in case multiple overlays target different parts of the same property. */ DTBLOB_T *dtb; char *params; const char **paramv; int paramc; int j; char *p; /* Extract the parameters */ dtb = dtoverlay_load_dtb(filename, 0); unlink(filename); if (!dtb) { error("Failed to re-apply dtparam"); continue; } params = (char *)dtoverlay_dtb_trailer(dtb); if (!params) { error("Failed to re-apply dtparam"); dtoverlay_free_dtb(dtb); continue; } /* Count and NUL-separate the params */ p = params; paramc = 0; while (*p) { int paramlen; *(p++) = '\0'; paramlen = strcspn(p, " "); paramc++; p += paramlen; } paramv = malloc((paramc + 1) * sizeof(const char *)); if (!paramv) { error("out of memory re-applying dtparam"); dtoverlay_free_dtb(dtb); continue; } for (j = 0, p = params + 1; j < paramc; j++) { paramv[j] = p; p += strlen(p) + 1; } paramv[j] = NULL; /* Create the new overlay */ ret = dtoverlay_add(state, "dtparam", paramc, paramv); free(paramv); dtoverlay_free_dtb(dtb); } else { rename(filename, new_file); ret = !apply_overlay(new_file, new_name); } if (ret != 0) { error("Failed to re-apply dtparam"); continue; } state->count++; } } } return 0; } static int dtoverlay_list(STATE_T *state) { if (state->count == 0) { printf("No overlays loaded\n"); } else { int i; printf("Overlays (in load order):\n"); for (i = 0; i < state->count; i++) { const char *name, *left, *right; const char *saved_overlay; DTBLOB_T *dtb; name = state->namelist[i]->d_name; left = strchr(name, '_'); if (!left) return error("Internal error"); left++; right = strchr(left, '.'); if (!right) return error("Internal error"); saved_overlay = sprintf_dup("%s/%s", work_dir, name); dtb = dtoverlay_load_dtb(saved_overlay, 0); if (dtoverlay_dtb_trailer(dtb)) printf("%d: %.*s %.*s\n", i, (int)(right - left), left, dtoverlay_dtb_trailer_len(dtb), (char *)dtoverlay_dtb_trailer(dtb)); else printf("%d: %.*s\n", i, (int)(right - left), left); dtoverlay_free_dtb(dtb); } } return 0; } static int dtoverlay_list_all(STATE_T *state) { int i; DIR *dh; struct dirent *de; STRING_VEC_T strings; string_vec_init(&strings); /* Enumerate .dtbo files in the /boot/overlays directory */ dh = opendir(overlay_src_dir); while ((de = readdir(dh)) != NULL) { int len = strlen(de->d_name) - 5; if ((len >= 0) && strcmp(de->d_name + len, ".dtbo") == 0) { char *str = string_vec_add(&strings, de->d_name, len + 2); str[len] = '\0'; str[len + 1] = ' '; } } closedir(dh); /* Merge in active overlays, marking them */ for (i = 0; i < state->count; i++) { const char *left, *right; char *str; int len, idx; left = strchr(state->namelist[i]->d_name, '_'); if (!left) return error("Internal error"); left++; right = strchr(left, '.'); if (!right) return error("Internal error"); len = right - left; if ((len == 7) && (memcmp(left, "dtparam", 7) == 0)) continue; idx = string_vec_find(&strings, left, len); if (idx >= 0) { str = strings.strings[idx]; len = strlen(str); } else { str = string_vec_add(&strings, left, len + 2); str[len] = '\0'; } str[len + 1] = '*'; } if (strings.num_strings == 0) { printf("No overlays found\n"); } else { /* Sort */ string_vec_sort(&strings); /* Display */ printf("All overlays (* = loaded):\n"); for (i = 0; i < strings.num_strings; i++) { const char *str = strings.strings[i]; printf("%c %s\n", str[strlen(str)+1], str); } } string_vec_uninit(&strings); return 0; } static void usage(void) { printf("Usage:\n"); if (strcmp(cmd_name, "dtparam") == 0) { printf(" %s Display help on all parameters\n", cmd_name); printf(" %s =...\n", cmd_name); printf(" %*s Add an overlay (with parameters)\n", (int)strlen(cmd_name), ""); printf(" %s -D Dry-run (prepare overlay, but don't apply -\n", cmd_name); printf(" %*s save it as dry-run.dtbo)\n", (int)strlen(cmd_name), ""); printf(" %s -r [] Remove an overlay (by index, or the last)\n", cmd_name); printf(" %s -R [] Remove from an overlay (by index, or all)\n", cmd_name); printf(" %s -l List active overlays/dtparams\n", cmd_name); printf(" %s -a List all overlays/dtparams (marking the active)\n", cmd_name); printf(" %s -h Show this usage message\n", cmd_name); printf(" %s -h ... Display help on the listed parameters\n", cmd_name); } else { printf(" %s [=...]\n", cmd_name); printf(" %*s Add an overlay (with parameters)\n", (int)strlen(cmd_name), ""); printf(" %s -D Dry-run (prepare overlay, but don't apply -\n", cmd_name); printf(" %*s save it as dry-run.dtbo)\n", (int)strlen(cmd_name), ""); printf(" %s -r [] Remove an overlay (by name, index or the last)\n", cmd_name); printf(" %s -R [] Remove from an overlay (by name, index or all)\n", cmd_name); printf(" %s -l List active overlays/params\n", cmd_name); printf(" %s -a List all overlays (marking the active)\n", cmd_name); printf(" %s -h Show this usage message\n", cmd_name); printf(" %s -h Display help on an overlay\n", cmd_name); printf(" %s -h .. Or its parameters\n", cmd_name); printf(" where is the name of an overlay or 'dtparam' for dtparams\n"); } printf("Options applicable to most variants:\n"); printf(" -d Specify an alternate location for the overlays\n"); printf(" (defaults to /boot/overlays or /flash/overlays)\n"); printf(" -p Force a compatible string for the platform\n"); printf(" -v Verbose operation\n"); printf("\n"); printf("Adding or removing overlays and parameters requires root privileges.\n"); exit(1); } static void root_check(void) { if (getuid() != 0) fatal_error("Must be run as root - try 'sudo %s ...'", cmd_name); } static void overlay_help(const char *overlay, const char **params) { OVERLAY_HELP_STATE_T *state; const char *readme_path = sprintf_dup("%s/%s", overlay_src_dir, README_FILE); state = overlay_help_open(readme_path); free_string(readme_path); if (state) { if (strcmp(overlay, "dtparam") == 0) overlay = ""; if (overlay_help_find(state, overlay)) { if (params && overlay_help_find_field(state, "Params")) { int in_param = 0; while (1) { const char *line = overlay_help_field_data(state); if (!line) break; if (line[0] == '\0') continue; if (line[0] != ' ') { /* This is a parameter name */ size_t param_len = strcspn(line, " "); const char **p = params; const char **q = p; in_param = 0; while (*p) { if ((param_len == strlen(*p)) && (memcmp(line, *p, param_len) == 0)) in_param = 1; else *(q++) = *p; p++; } *(q++) = 0; } if (in_param) printf("%s\n", line); } /* This only shows the first unknown parameter, but * that is enough. */ if (*params) fatal_error("Unknown parameter '%s'", *params); } else { printf("Name: %s\n\n", overlay); overlay_help_print_field(state, "Info", "Info:", 8, 0); overlay_help_print_field(state, "Load", "Usage:", 8, 0); overlay_help_print_field(state, "Params", "Params:", 8, 0); } } else { fatal_error("No help found for overlay '%s'", overlay); } overlay_help_close(state); } else { fatal_error("Help file not found"); } } static int apply_overlay(const char *overlay_file, const char *overlay) { const char *overlay_dir = sprintf_dup("%s/%s", dt_overlays_dir, overlay); int ret = 0; if (dir_exists(overlay_dir)) { error("Overlay '%s' is already loaded", overlay); } else if (mkdir(overlay_dir, DIR_MODE) == 0) { DTBLOB_T *dtb = dtoverlay_load_dtb(overlay_file, 0); if (!dtb) { error("Failed to apply overlay '%s' (load)", overlay); } else { const char *dest_file = sprintf_dup("%s/dtbo", overlay_dir); /* then write the overlay to the file */ if (dtoverlay_save_dtb(dtb, dest_file) != 0) error("Failed to apply overlay '%s' (save)", overlay); else if (!overlay_applied(overlay_dir)) error("Failed to apply overlay '%s' (kernel)", overlay); else ret = 1; free_string(dest_file); dtoverlay_free_dtb(dtb); } if (!ret) rmdir(overlay_dir); } else { error("Failed to create overlay directory"); } return ret; } static int overlay_applied(const char *overlay_dir) { char status[7] = { '\0' }; const char *status_path = sprintf_dup("%s/status", overlay_dir); FILE *fp = fopen(status_path, "r"); int bytes = 0; if (fp) { bytes = fread(status, 1, sizeof(status), fp); fclose(fp); } free_string(status_path); return (bytes == sizeof(status)) && (memcmp(status, "applied", sizeof(status)) == 0); } int seq_filter(const struct dirent *de) { int num; return (sscanf(de->d_name, "%d_", &num) == 1); } int seq_compare(const struct dirent **de1, const struct dirent **de2) { int num1 = atoi((*de1)->d_name); int num2 = atoi((*de2)->d_name); if (num1 < num2) return -1; else if (num1 == num2) return 0; else return 1; } static STATE_T *read_state(const char *dir) { STATE_T *state = malloc(sizeof(STATE_T)); int i; if (state) { state->count = scandir(dir, &state->namelist, seq_filter, seq_compare); for (i = 0; i < state->count; i++) { int num = atoi(state->namelist[i]->d_name); if (i != num) error("Overlay sequence error"); } } return state; } static void free_state(STATE_T *state) { int i; for (i = 0; i < state->count; i++) { free(state->namelist[i]); } free(state->namelist); free(state); } raspi-utils-20240903/dtmerge/dtparam.1000066400000000000000000000022711466562725600174210ustar00rootroot00000000000000.TH DTPARAM 1 . .SH NAME dtparam \- mainpulate parameters of the base device-tree . . .SH SYNOPSIS .SY dtparam .RI [ param=val \|.\|.\|.] .YS . .SY dtparam .B \-h .RI [ param ] .YS . . .SH DESCRIPTION .B dtparam is a command line utility for manipulating the base device-tree's parameters. For example, it can be used to enable the SPI or I2C interfaces at runtime without rebooting. If .B dtparam is run without any argument, it prints the names of all base device-tree parameters and their description. . . .SH OPTIONS . .TP .BR \-h " [\fIparam\fR]" If given without .I param displays help on the application overall. If .I param is specified, prints help about that parameter in the base device-tree. . . .SH EXAMPLES . .TP .B sudo dtparam spi=on Enable the SPI interface on the GPIO header. Note that root privileges are usually required for manipulating the base device-tree (hence, sudo in the example). . .TP .B dtparam -h spi Print help about the "spi" parameter. . .TP .B sudo dtparam audio=off Disable the audio output. . . .SH SEE ALSO .BR dtoverlay (1), .BR dtmerge (1), .B [DTREE] . . .SH REFERENCES .TP .B [DTREE] https://www.raspberrypi.com/documentation/computers/configuration.html#part3.5.2 raspi-utils-20240903/dtmerge/utils.c000066400000000000000000000255141466562725600172200ustar00rootroot00000000000000/* Copyright (c) 2016 Raspberry Pi (Trading) Ltd. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include "utils.h" #define OVERLAY_HELP_INDENT 8 int opt_verbose; int opt_dry_run; static STRING_T *allocated_strings; struct overlay_help_state_struct { FILE *fp; long rec_pos; int line_len; int line_pos; int blank_count; int end_of_field; char line_buf[82]; }; static int overlay_help_get_line(OVERLAY_HELP_STATE_T *state); OVERLAY_HELP_STATE_T *overlay_help_open(const char *helpfile) { OVERLAY_HELP_STATE_T *state = NULL; FILE *fp = fopen(helpfile, "r"); if (fp) { state = calloc(1, sizeof(OVERLAY_HELP_STATE_T)); if (!state) fatal_error("Out of memory"); state->fp = fp; state->line_pos = -1; state->rec_pos = -1; } return state; } void overlay_help_close(OVERLAY_HELP_STATE_T *state) { fclose(state->fp); free(state); } int overlay_help_find(OVERLAY_HELP_STATE_T *state, const char *name) { state->line_pos = -1; state->rec_pos = -1; state->blank_count = 0; fseek(state->fp, 0, SEEK_SET); while (overlay_help_find_field(state, "Name")) { const char *overlay = overlay_help_field_data(state); if (overlay && (strcmp(overlay, name) == 0)) { state->rec_pos = (long)ftell(state->fp); return 1; } } return 0; } int overlay_help_find_field(OVERLAY_HELP_STATE_T *state, const char *field) { int field_len = strlen(field); int found = 0; if (state->rec_pos >= 0) fseek(state->fp, state->rec_pos, SEEK_SET); while (!found) { int line_len = overlay_help_get_line(state); if (line_len < 0) break; /* Check for the ":" prefix */ if ((line_len >= (field_len + 1)) && (state->line_buf[field_len] == ':') && (memcmp(state->line_buf, field, field_len) == 0)) { /* Found it If this initial line has no content then skip it */ if (line_len > OVERLAY_HELP_INDENT) state->line_pos = OVERLAY_HELP_INDENT; else state->line_pos = -1; state->end_of_field = 0; found = 1; } else { state->line_pos = -1; } } return found; } const char *overlay_help_field_data(OVERLAY_HELP_STATE_T *state) { int line_len, pos; if (state->end_of_field) return NULL; line_len = state->line_len; if ((state->line_pos < 0) || (state->line_pos >= line_len)) { line_len = overlay_help_get_line(state); /* Fields end at the start of the next field or the end of the record */ if ((line_len < 0) || (state->line_buf[0] != ' ')) { state->end_of_field = 1; return NULL; } if (line_len == 0) return ""; } /* Return field data starting at OVERLAY_HELP_INDENT, if there is any */ pos = line_len; if (pos > OVERLAY_HELP_INDENT) pos = OVERLAY_HELP_INDENT; state->line_pos = -1; return &state->line_buf[pos]; } void overlay_help_print_field(OVERLAY_HELP_STATE_T *state, const char *field, const char *label, int indent, int strip_blanks) { if (!overlay_help_find_field(state, field)) return; while (1) { const char *line = overlay_help_field_data(state); if (!line) break; if (label) { int spaces = indent - strlen(label); if (spaces < 0) spaces = 0; printf("%s%*s%s\n", label, spaces, "", line); label = NULL; } else if (line[0]) { printf("%*s%s\n", indent, "", line); } else if (!strip_blanks) { printf("\n"); } } if (!strip_blanks) printf("\n"); } /* Returns the length of the line, or -1 on end of file or record */ static int overlay_help_get_line(OVERLAY_HELP_STATE_T *state) { int line_len; if (state->line_pos >= 0) return state->line_len; get_next_line: state->line_buf[sizeof(state->line_buf) - 1] = ' '; line_len = -1; if (fgets(state->line_buf, sizeof(state->line_buf), state->fp)) { // Check for overflow // Strip the newline line_len = strlen(state->line_buf); if (line_len && (state->line_buf[line_len - 1] == '\n')) { line_len--; state->line_buf[line_len] = '\0'; } } if (state->rec_pos >= 0) { if (line_len == 0) { state->blank_count++; if (state->blank_count >= 2) return -1; state->line_pos = 0; goto get_next_line; } else if (state->blank_count) { /* Return a single blank line now - the non-empty line will be returned next time */ state->blank_count = 0; return 0; } } state->line_len = line_len; state->line_pos = (line_len >= 0) ? 0 : -1; return line_len; } int run_cmd(const char *fmt, ...) { va_list ap; char *cmd; int ret; va_start(ap, fmt); cmd = vsprintf_dup(fmt, ap); va_end(ap); if (opt_dry_run || opt_verbose) fprintf(stderr, "run_cmd: %s\n", cmd); ret = opt_dry_run ? 0 : system(cmd); free_string(cmd); return ret; } /* Not thread safe */ void free_string(const char *string) { STRING_T *str; if (!string) return; str = (STRING_T *)(string - sizeof(STRING_T)); if (str == allocated_strings) { allocated_strings = str->next; if (allocated_strings == str) allocated_strings = NULL; } str->prev->next = str->next; str->next->prev = str->prev; free(str); } /* Not thread safe */ void free_strings(void) { if (allocated_strings) { STRING_T *str = allocated_strings; do { STRING_T *t = str; str = t->next; free(t); } while (str != allocated_strings); allocated_strings = NULL; } } /* Not thread safe */ char *sprintf_dup(const char *fmt, ...) { va_list ap; char *str; va_start(ap, fmt); str = vsprintf_dup(fmt, ap); va_end(ap); return str; } /* Not thread safe */ char *vsprintf_dup(const char *fmt, va_list ap) { char scratch[512]; size_t len; STRING_T *str; len = vsnprintf(scratch, sizeof(scratch), fmt, ap) + 1; if (len > sizeof(scratch)) fatal_error("Maximum string length exceeded"); str = malloc(sizeof(STRING_T) + len); if (!str) fatal_error("Out of memory"); memcpy(str->data, scratch, len); if (allocated_strings) { str->next = allocated_strings; str->prev = allocated_strings->prev; str->next->prev = str; str->prev->next = str; } else { str->next = str; str->prev = str; allocated_strings = str; } return str->data; } int dir_exists(const char *dirname) { struct stat finfo; return (stat(dirname, &finfo) == 0) && S_ISDIR(finfo.st_mode); } int file_exists(const char *dirname) { struct stat finfo; return (stat(dirname, &finfo) == 0) && S_ISREG(finfo.st_mode); } void string_vec_init(STRING_VEC_T *vec) { vec->num_strings = 0; vec->max_strings = 0; vec->strings = NULL; } char *string_vec_add(STRING_VEC_T *vec, const char *str, int len) { char *copy; if (vec->num_strings == vec->max_strings) { if (vec->max_strings) vec->max_strings *= 2; else vec->max_strings = 16; vec->strings = realloc(vec->strings, vec->max_strings * sizeof(const char *)); if (!vec->strings) fatal_error("Out of memory"); } if (len) { copy = malloc(len + 1); strncpy(copy, str, len); copy[len] = '\0'; } else copy = strdup(str); if (!copy) fatal_error("Out of memory"); vec->strings[vec->num_strings++] = copy; return copy; } int string_vec_find(STRING_VEC_T *vec, const char *str, int len) { int i; for (i = 0; i < vec->num_strings; i++) { if (len) { if ((strncmp(vec->strings[i], str, len) == 0) && (vec->strings[i][len] == '\0')) return i; } else if (strcmp(vec->strings[i], str) == 0) return i; } return -1; } int string_vec_compare(const void *a, const void *b) { return strcmp(*(const char **)a, *(const char **)b); } void string_vec_sort(STRING_VEC_T *vec) { qsort(vec->strings, vec->num_strings, sizeof(char *), &string_vec_compare); } void string_vec_uninit(STRING_VEC_T *vec) { int i; for (i = 0; i < vec->num_strings; i++) free(vec->strings[i]); free(vec->strings); } int error(const char *fmt, ...) { va_list ap; fprintf(stderr, "* "); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, "\n"); return 1; } void fatal_error(const char *fmt, ...) { va_list ap; fprintf(stderr, "* "); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, "\n"); exit(1); } char *read_file_as_string(const char *path, int *plen) { FILE *fp = fopen(path, "r"); char *string = NULL; long len = 0; if (fp) { long bytes_read; fseek(fp, 0, SEEK_END); len = ftell(fp); fseek(fp, 0, SEEK_SET); /* Ensure NUL-termination */ string = malloc(len + 1); if (!string) fatal_error("Out of memory", path); string[len] = 0; bytes_read = fread(string, 1, len, fp); if (bytes_read != len) fatal_error("Failed to read file '%s'", path); fclose(fp); } if (plen) *plen = (int)len; return string; } raspi-utils-20240903/dtmerge/utils.h000066400000000000000000000061151466562725600172210ustar00rootroot00000000000000/* Copyright (c) 2016 Raspberry Pi (Trading) Ltd. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef UTILS_H #define UTILS_H typedef struct string_struct { struct string_struct *prev; struct string_struct *next; char data[0]; } STRING_T; typedef struct string_vec_struct { int num_strings; int max_strings; char **strings; } STRING_VEC_T; typedef struct overlay_help_state_struct OVERLAY_HELP_STATE_T; extern int opt_verbose; extern int opt_dry_run; OVERLAY_HELP_STATE_T *overlay_help_open(const char *helpfile); void overlay_help_close(OVERLAY_HELP_STATE_T *state); int overlay_help_find(OVERLAY_HELP_STATE_T *state, const char *name); int overlay_help_find_field(OVERLAY_HELP_STATE_T *state, const char *field); const char *overlay_help_field_data(OVERLAY_HELP_STATE_T *state); void overlay_help_print_field(OVERLAY_HELP_STATE_T *state, const char *field, const char *label, int indent, int strip_blanks); int run_cmd(const char *fmt, ...); void free_string(const char *string); /* Not thread safe */ void free_strings(void); /* Not thread safe */ char *sprintf_dup(const char *fmt, ...); /* Not thread safe */ char *vsprintf_dup(const char *fmt, va_list ap); /* Not thread safe */ int dir_exists(const char *dirname); int file_exists(const char *dirname); void string_vec_init(STRING_VEC_T *vec); char *string_vec_add(STRING_VEC_T *vec, const char *str, int len); int string_vec_find(STRING_VEC_T *vec, const char *str, int len); void string_vec_sort(STRING_VEC_T *vec); void string_vec_uninit(STRING_VEC_T *vec); int error(const char *fmt, ...); void fatal_error(const char *fmt, ...); char *read_file_as_string(const char *path, int *plen); #endif raspi-utils-20240903/eeptools/000077500000000000000000000000001466562725600161105ustar00rootroot00000000000000raspi-utils-20240903/eeptools/CMakeLists.txt000066400000000000000000000010341466562725600206460ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.10...3.27) include(GNUInstallDirs) #set project name project(eeptools) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror") if (CMAKE_COMPILER_IS_GNUCC) add_definitions (-ffunction-sections) endif () add_executable(eepmake eepmake.c eeplib.c) install(TARGETS eepmake RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) add_executable(eepdump eepdump.c eeplib.c) install(TARGETS eepdump RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(PROGRAMS eepflash.sh DESTINATION ${CMAKE_INSTALL_BINDIR}) raspi-utils-20240903/eeptools/README.md000066400000000000000000000073061466562725600173750ustar00rootroot00000000000000# Utilities to create, flash and dump HAT EEPROM images **Build Instructions** Install the prerequisites with "sudo apt install cmake" - you need at least version 3.10 of cmake. Run the following commands here, or in the top-level directory to build and install all the utilities: - *cmake .* - *make* - *sudo make install* **Usage** 1. Copy `eeprom_settings.txt` (or `eeprom_v1_settings.txt` for a V1 HAT) to `myhat_eeprom.txt`. 2. Edit `myhat_eeprom.txt` to suit your specific HAT. 3. Run `eepmake myhat_eeprom.txt myhat.eep` (or `eepmake -v1 myhat_eeprom.txt myhat.eep` for a V1 HAT) to create the .eep binary. 4. If `eepmake` has generated a product UUID for you, it is a good idea to patch your myhat_eeprom.txt to include and hence preserve it (or use `eepdump` to regenerate it). **Flashing the EEPROM image** Follow these steps on the Raspberry Pi after installing the tools and building the .eep file: 1. Disable EEPROM write protection * Sometimes this requires a jumper on the board * Sometimes this is a GPIO * Check your schematics 2. Make sure you can talk to the EEPROM * In the HAT specification, the HAT EEPROM is connected to pins that can be driven by I2C0. However, this is the same interface as used by the camera and displays, so use of it by the ARMs is discouraged. The `eepflash.sh` script gets around this problem by instantiating a software driven I2C interface using those pins as GPIOs, calling it `i2c-9`: ``` sudo dtoverlay i2c-gpio i2c_gpio_sda=0 i2c_gpio_scl=1 bus=9 ``` * Install i2cdetect `sudo apt install i2c-tools` * As explained in the HAT+ specification, HAT EEPROMs can have a variety of (hexadecimal) I2C addresses: + 50: standard HAT+ (or legacy HAT) + 51: stackable HAT+ (e.g. Raspberry Pi M.2 M Key HAT) + 52: stackable Power HAT+ (MODE0) + 53: stackable Power HAT+ (MODE1) The examples below assume a standard HAT+/HAT, so be prepared to substitute the correct address for another type. * Check with `i2cdetect -y 9` (should be at address 0x50) ```bash i2cdetect -y 9 0x50 0x50 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: 10: 20: 30: 40: 50: 50 60: 70: ``` Normally, you can skip this step, and assume things are working. 3. Flash eep file `sudo ./eepflash.sh -w -t=24c32 -a=0x50 -f=eeprom.eep` 4. Enable EEPROM write protection, by undoing step 1 (putting back jumper, or resetting GPIO) ## String data formatting The tools support two string representations: 1. A single, simple string with no CR/NL, no NUL-termination, and no embedded double-quotes. 2. A multiline string using minimal escaping: * String begins with a single `"` followed by CR/NL (carriage return/newline) * NULs are escaped as \0, and followed by an extra NL (because they end a string) * Backslashes are escaped (`\\`) * NLs and TABs are included verbatim * CRs are escaped (`\r`) * The multiline string is terminated by `\"` * Any literal, unescaped CRs found in the string are ignored `eepmake` attempts to display dt_blob and custom_data blocks as simple strings, falling back to multiline strings, ultimately resorting to hexadecimal data. If in doubt, put the text in a file, add it using the `-c` option in `eepmake`, and use `eepdump` to see what it looks like. To confirm that blobs data is encoded and preserved correctly, use the `-b ` option to `eepdump` to save the blob data as separate files. Examples: ``` dt_blob "rpi-dacpro" custom_data " This is the start of a long string. End this line with a carriage return\r NUL-terminated\0 \" custom_data " NL and NUL-terminated \0 \" # This one could have been a simple string custom_data " End text with no NL\" custom_data " End text with NL \" ``` raspi-utils-20240903/eeptools/eepdump.c000066400000000000000000000167421466562725600177250ustar00rootroot00000000000000#include #include #include #include #include #include "eeplib.h" static struct var_blob_t dt_atom, custom_atom; static struct vendor_info_d vinf; static struct gpio_map_d gpiomap; static const char *blob_prefix; static const char *atom_type_names[] ={ [ATOM_INVALID_TYPE] = "invalid", [ATOM_VENDOR_TYPE] = "vendor type", [ATOM_GPIO_TYPE] = "GPIO map", [ATOM_DT_TYPE] = "DT overlay", [ATOM_CUSTOM_TYPE] = "manufacturer custom data", [ATOM_GPIO_BANK1_TYPE] = "GPIO map bank 1", }; static void dump_data(FILE *out, const uint8_t *data, int len) { bool is_string = true, is_simple_string = true; int j; for (j = 0; j < len; j++) { int c = data[j]; if (!c || c == '"' || !isprint(c)) is_simple_string = false; if (c && !isprint(c) && !isspace(c)) { is_string = false; break; } } if (is_simple_string) { fputs(" \"", out); for (j = 0; j < len; j++) fputc(data[j], out); fputs("\"\n", out); } else if (is_string) { fputs(" \"\n", out); for (j = 0; j < len; j++) { int c = data[j]; if (c == '\\') fputs("\\\\", out); else if (c == '\r') fputs("\\r", out); else if (!c) fputs("\\0\n", out); else fputc(c, out); } fputs("\\\"\n", out); } else { for (j = 0; j < len; j++) { if (j % 16 == 0) fprintf(out, "\n"); fprintf(out, "%02X ", data[j]); } fprintf(out, "\n"); } } static void dump_blob(const char *filename, const struct var_blob_t *blob) { FILE *fp = fopen(filename, "wb"); if (fp) { fwrite(blob->data, blob->dlen, 1, fp); fclose(fp); } } static int read_bin(const char *in, const char *outf) { enum file_ver_t hat_format; int numatoms; int custom_count = 0; const char *atom_name; FILE *fp, *out; int i; uint32_t j; fp = fopen(in, "rb"); if (!fp) { printf("Error reading file %s\n", in); return -1; } if (outf) out = fopen(outf, "w"); else out = stdout; if (!out) { printf("Error writing file %s\n", outf); return -1; } fprintf(out, "# ---------- Dump generated by eepdump handling format version 0x%02x ----------\n#\n", EEP_VERSION); eepio_start(&hat_format, &numatoms, EEPIO_READ, fp); if (EEP_VERSION_HATPLUS != hat_format && EEP_VERSION_HATV1 != hat_format) { fprintf(out, "# WARNING: format version mismatch!!!\n"); goto err; } if (EEP_VERSION != eep_header.ver && HEADER_SIGN != eep_header.signature) { printf("header version and signature mismatch, maybe wrong file?\n"); goto err; } fprintf(out, "# --Header--\n# signature=0x%08x\n# version=0x%02x\n# reserved=%u\n# numatoms=%u\n# eeplen=%u\n# ----------\n\n\n", eep_header.signature, eep_header.ver, eep_header.res, eep_header.numatoms, eep_header.eeplen); for (i = 0; i < numatoms && !eepio_got_error(); i++) { unsigned int gpio_count; enum atom_type_t type = ATOM_INVALID_TYPE; bool bank1 = false; uint32_t dlen; if (!eepio_atom_start(&type, &dlen)) goto err; atom_name = (type < count_of(atom_type_names)) ? atom_type_names[type] : "unknown"; printf("Reading atom %d (type = 0x%04x (%s), length = %i bytes)...\n", i, type, atom_name, eep_atom_header.dlen); fprintf(out, "# Start of atom #%u of type 0x%04x and length %u\n", eep_atom_header.count, type, eep_atom_header.dlen); switch (type) { case ATOM_VENDOR_TYPE: if (!eepio_atom_vinf(&vinf)) goto atom_read_err; fprintf(out, "# Vendor info\n"); fprintf(out, "product_uuid %08x-%04x-%04x-%04x-%04x%08x\n", vinf.serial[3], vinf.serial[2] >> 16, vinf.serial[2] & 0xffff, vinf.serial[1] >> 16, vinf.serial[1] & 0xffff, vinf.serial[0]); fprintf(out, "product_id 0x%04x\n", vinf.pid); fprintf(out, "product_ver 0x%04x\n", vinf.pver); fprintf(out, "vendor \"%s\" # length=%u\n", vinf.vstr, vinf.vslen); fprintf(out, "product \"%s\" # length=%u\n", vinf.pstr, vinf.pslen); break; case ATOM_GPIO_BANK1_TYPE: bank1 = true; /* fall through... */ case ATOM_GPIO_TYPE: if (!eepio_atom_gpio(&gpiomap)) goto atom_read_err; fprintf(out, "# GPIO "); if (bank1) fprintf(out, "bank 1 "); fprintf(out, "map info\n"); if (bank1) fprintf(out, "bank1_"); fprintf(out, "gpio_drive %d\n", gpiomap.flags & 15); // 1111 if (bank1) fprintf(out, "bank1_"); fprintf(out, "gpio_slew %d\n", (gpiomap.flags & 48) >> 4); // 110000 if (bank1) fprintf(out, "bank1_"); fprintf(out, "gpio_hysteresis %d\n", (gpiomap.flags & 192) >> 6); // 11000000 if (!bank1) fprintf(out, "back_power %d\n", gpiomap.power); fprintf(out, "# GPIO FUNCTION PULL\n# ---- -------- ----\n"); gpio_count = bank1 ? GPIO_COUNT_BANK1 : GPIO_COUNT; for (j = 0; j < gpio_count; j++) { if (gpiomap.pins[j] & (1 << 7)) { // board uses this pin char *pull_str = "INVALID"; char *func_str = "INVALID"; switch ((gpiomap.pins[j] & 96) >> 5) { // 1100000 case 0: pull_str = "DEFAULT"; break; case 1: pull_str = "UP"; break; case 2: pull_str = "DOWN"; break; case 3: pull_str = "NONE"; break; } switch ((gpiomap.pins[j] & 7)) { // 111 case 0: func_str = "INPUT"; break; case 1: func_str = "OUTPUT"; break; case 4: func_str = "ALT0"; break; case 5: func_str = "ALT1"; break; case 6: func_str = "ALT2"; break; case 7: func_str = "ALT3"; break; case 3: func_str = "ALT4"; break; case 2: func_str = "ALT5"; break; } fprintf(out, "setgpio %d %s %s\n", bank1 ? j + GPIO_COUNT : j, func_str, pull_str); } } break; case ATOM_DT_TYPE: dt_atom.dlen = dlen; if (!eepio_atom_var(&dt_atom)) goto atom_read_err; fprintf(out, "dt_blob"); dump_data(out, dt_atom.data, dt_atom.dlen); if (blob_prefix) { char filename[FILENAME_MAX]; sprintf(filename, "%s_dt_blob", blob_prefix); dump_blob(filename, &dt_atom); } break; case ATOM_CUSTOM_TYPE: custom_atom.dlen = dlen; if (!eepio_atom_var(&custom_atom)) goto atom_read_err; fprintf(out, "custom_data"); dump_data(out, custom_atom.data, custom_atom.dlen); if (blob_prefix) { char filename[FILENAME_MAX]; sprintf(filename, "%s_custom_data_%d", blob_prefix, custom_count); dump_blob(filename, &custom_atom); } custom_count++; break; default: printf("Error: unrecognised atom type\n"); fprintf(out, "# Error: unrecognised atom type\n"); goto err; } eepio_atom_end(); fprintf(out, "# End of atom. CRC16=0x%04x\n", eep_atom_crc); fprintf(out, "\n"); } eepio_end(); printf("Done.\n"); fclose(fp); if (outf) fclose(out); return 0; atom_read_err: printf("Error reading %s atom\n", atom_name); err: printf("Unexpected EOF or error occurred\n"); fclose(fp); fclose(out); return -1; } int usage(void) { printf("Usage: eepdump input_file [-b blob_prefix] [output_file]\n"); printf(" where\n"); printf(" blob_prefix is prefix string used to generate file names for any\n"); printf(" data blobs\n"); return 1; } int main(int argc, const char *argv[]) { int argn; for (argn = 1; argn < argc && argv[argn][0] == '-'; argn++) { const char *arg = argv[argn]; if (!strcmp(arg, "-b")) { if (argn == argc) return usage(); blob_prefix = argv[++argn]; } else { printf("Unknown option '%s'\n", arg); return usage(); } } if (argc <= argn) return usage(); return read_bin(argv[argn], argv[argn + 1]); } raspi-utils-20240903/eeptools/eepflash.sh000077500000000000000000000105021466562725600202340ustar00rootroot00000000000000#!/bin/sh me=$(basename "$0") MODE="NOT_SET" FILE="NOT_SET" TYPE="NOT_SET" BUS="NOT_SET" ADDR="NOT_SET" BATCH="NOT_SET" usage() { echo "eepflash: Writes or reads .eep binary image to/from HAT EEPROM on a Raspberry Pi" echo "" echo "$me" echo " -h --help: display this help message" echo " -r --read: read .eep from the EEPROM" echo " -w --write: write .eep to the EEPROM" echo " -y --yes: disable interactive mode. By default eepflash.sh will wait" echo " for confirmation from the user. When this flag is used, it will" echo " perform the operation directly. This is mainly meant to be used" echo " in scripts" echo " -f=file_name --file=file_name: binary .eep file to read to/from" echo " N.B. -f file_name and --file file_name (without =) also accepted" echo " -d= --device= i2c bus number (ex if the eeprom is on i2c-0 set -d=0)" echo ' -a= --address= i2c EEPROM address (0x50 for standard HAT+/HAT, 0x51' echo " for stackable HAT+, 0x52 for MODE0 Power HAT+, 0x53 for MODE1" echo ' Power HAT+)' echo " -t=eeprom_type --type=eeprom_type: EEPROM type to use" echo " The following EEPROM types are supported:" echo " 24c32" echo " 24c64" echo " 24c128" echo " 24c256" echo " 24c512" echo " 24c1024" echo "" echo "Example:" echo "$me -w -f=crex0.1.eep -t=24c32 -d=1 -a=57" echo "$me -r -f dump.eep -t=24c32 -d=1 -a=57" echo "" } while [ "$1" != "" ]; do PARAM=$(echo "$1" | awk -F= '{print $1}') VALUE=$(echo "$1" | awk -F= '{print $2}') case $PARAM in -h | --help) usage exit ;; -r | --read) MODE="read" ;; -w | --write) MODE="write" ;; -y | --yes) BATCH="yes" ;; -t | --type) if [ "$VALUE" = "24c32" ] || [ "$VALUE" = "24c64" ] || [ "$VALUE" = "24c128" ] || [ "$VALUE" = "24c256" ] || [ "$VALUE" = "24c512" ] || [ "$VALUE" = "24c1024" ]; then TYPE=$VALUE else echo "ERROR: Unrecognised eeprom type. Try -h for help" exit 1 fi ;; -d | --device) BUS=$VALUE ;; -a | --address) ADDR=$VALUE ;; -f | --file) FILE=$VALUE if [ "$1" = "-f" ] || [ "$1" = "--file" ]; then shift FILE=$1 fi ;; *) echo "ERROR: unknown parameter \"$PARAM\"" usage exit 1 ;; esac shift done if [ "$MODE" = "NOT_SET" ]; then echo 'You need to set mode (read or write). Try -h for help.' exit 1 elif [ "$FILE" = "NOT_SET" ]; then echo "You need to set binary .eep file to read to/from. Try -h for help." exit 1 elif [ "$TYPE" = "NOT_SET" ]; then echo "You need to set eeprom type. Try -h for help." exit 1 fi if [ "$ADDR" = "NOT_SET" ]; then ADDR=50 fi echo "This will attempt to talk to an eeprom at i2c address 0x$ADDR. Make sure there is an eeprom at this address." echo "This script comes with ABSOLUTELY no warranty. Continue only if you know what you are doing." if [ "$(id -u)" != "0" ]; then echo "This script must be run as root" 1>&2 exit 1 fi if [ "$BATCH" = "NOT_SET" ]; then while true; do printf 'Do you wish to continue? (yes/no): ' read -r yn case $yn in yes | Yes ) break;; no | No ) exit;; * ) echo "Please type yes or no.";; esac done fi modprobe i2c_dev if [ "$BUS" = "NOT_SET" ]; then if [ -e "/dev/i2c-0" ]; then BUS=0 elif [ -e "/dev/i2c-9" ]; then BUS=9 else dtoverlay i2c-gpio i2c_gpio_sda=0 i2c_gpio_scl=1 bus=9 rc=$? if [ $rc != 0 ]; then echo "Loading of i2c-gpio dtoverlay failed." exit $rc fi sleep 1 if [ -e "/dev/i2c-9" ]; then BUS=9 else echo 'Expected I2C bus (i2c-9) not found.' fi fi fi modprobe at24 rc=$? if [ $rc != 0 ]; then echo "Modprobe of at24 failed." exit $rc fi SYS=/sys/class/i2c-adapter/i2c-$BUS if [ ! -d "$SYS/$BUS-00$ADDR" ]; then echo "$TYPE 0x$ADDR" > $SYS/new_device fi DD_VERSION=$(dd --version 2>&1 | grep coreutils | sed -e 's/\.//' | cut -d' ' -f 3) if [ -z "$DD_VERSION" ]; then # probably busybox's dd DD_STATUS="" else if [ "$DD_VERSION" -ge 824 ]; then DD_STATUS="status=progress" else DD_STATUS="status=none" fi fi if [ "$MODE" = "write" ] then echo "Writing..." dd if="$FILE" of=$SYS/$BUS-00$ADDR/eeprom $DD_STATUS rc=$? elif [ "$MODE" = "read" ] then echo "Reading..." dd if=$SYS/$BUS-00$ADDR/eeprom of="$FILE" $DD_STATUS rc=$? fi echo "Closing EEPROM Device." echo "0x$ADDR" > $SYS/delete_device if [ $rc != 0 ]; then echo "Error doing I/O operation." exit $rc else echo "Done." fi raspi-utils-20240903/eeptools/eeplib.c000066400000000000000000000147701466562725600175250ustar00rootroot00000000000000#include #include #include #include "eeplib.h" struct header_t eep_header; struct atom_t eep_atom_header; uint16_t eep_atom_crc; unsigned int eep_atom_num; static enum eepio_dir_t eepio_dir; static FILE *eepio_fp; static uint16_t crc_state; static long eepio_pos_start; static long eepio_atom_data_start; static bool eepio_error_flag; static bool eepio_buffer_writes; static void *eepio_write_buf; static int eepio_write_buf_pos; static int eepio_write_buf_size; static log_callback_t eep_error_callback; static log_callback_t eep_warning_callback; void eepio_clear_error(void) { eepio_error_flag = false; } bool eepio_error(const char *msg, ...) { va_list ap; va_start(ap, msg); eepio_error_flag = true; if (eep_error_callback) (*eep_error_callback)(msg, ap); else { printf("ERROR: "); vprintf(msg, ap); printf("\n"); } va_end(ap); return true; } void eepio_fatal_error(const char *msg, ...) { va_list ap; va_start(ap, msg); if (eep_error_callback) (*eep_error_callback)(msg, ap); else { printf("ERROR: "); vprintf(msg, ap); printf("\n"); } va_end(ap); exit(1); } void eepio_warning(const char *msg, ...) { va_list ap; va_start(ap, msg); if (eep_warning_callback) (*eep_warning_callback)(msg, ap); else { printf("WARNING: "); vprintf(msg, ap); printf("\n"); } va_end(ap); } bool eepio_got_error(void) { return eepio_error_flag; } void crc_init(void) { crc_state = 0; } void crc_add(uint8_t *data, unsigned int size) { int bits_read = 0, bit_flag; while (size > 0) { bit_flag = crc_state >> 15; /* Get next bit: */ crc_state <<= 1; /* work from the least significant bits */ crc_state |= (*data >> bits_read) & 1; /* Increment bit counter: */ bits_read++; if (bits_read > 7) { bits_read = 0; data++; size--; } /* Cycle check: */ if (bit_flag) crc_state ^= CRC16; } } uint16_t crc_get(void) { uint16_t crc; int i, j; /* "push out" the last 16 bits */ for (i = 0; i < 16; ++i) { int bit_flag = crc_state >> 15; crc_state <<= 1; if (bit_flag) crc_state ^= CRC16; } /* reverse the bits */ crc = 0; i = 0x8000; j = 0x0001; for (; i != 0; i >>= 1, j <<= 1) { if (i & crc_state) crc |= j; } return crc; } static void *eepio_write_buf_space(int len) { void *space; if ((eepio_write_buf_pos + len) > eepio_write_buf_size) { eepio_write_buf_size += eepio_write_buf_size + len * 2; eepio_write_buf = realloc(eepio_write_buf, eepio_write_buf_size); if (!eepio_write_buf) eepio_fatal_error("Out of memory"); } space = (void *)((uint8_t *)eepio_write_buf + eepio_write_buf_pos); eepio_write_buf_pos += len; return space; } void eepio_blob(void *blob, int len) { if (eepio_buffer_writes) { memcpy(eepio_write_buf_space(len), blob, len); return; } if (eepio_dir == EEPIO_READ) { if (!fread(blob, len, 1, eepio_fp)) eepio_error("Failed to read from file"); } else { if (!fwrite(blob, len, 1, eepio_fp)) eepio_error("Failed to write to file"); } crc_add(blob, len); } void eepio_string(char **pstr, int len) { if (eepio_dir == EEPIO_READ) *pstr = (char *)malloc(len + 1); eepio_blob(*pstr, len); (*pstr)[len] = 0; } void eepio_start(enum file_ver_t *pver, int *pnumatoms, enum eepio_dir_t dir, FILE *fp) { if (dir == EEPIO_WRITE) { eep_header.signature = HEADER_SIGN; eep_header.ver = *pver; eep_header.res = 0; eep_header.numatoms = 0; } eepio_dir = dir; eepio_fp = fp; eepio_pos_start = ftell(eepio_fp); eepio_blob(&eep_header, sizeof(eep_header)); if (dir == EEPIO_READ) { *pver = eep_header.ver; if (eep_header.signature != HEADER_SIGN) eepio_warning("Format signature mismatch"); if (pnumatoms) *pnumatoms = eep_header.numatoms; } eep_atom_num = 0; } bool eepio_end(void) { // Patch/check header with atom count and total length long pos, pos_end; pos = ftell(eepio_fp); if (pos == -1) return eepio_error("ftell failed"); if (eepio_dir == EEPIO_WRITE) { eep_header.eeplen = pos - eepio_pos_start; eep_header.numatoms = eep_atom_num; fseek(eepio_fp, eepio_pos_start, SEEK_SET); eepio_blob(&eep_header, sizeof(eep_header)); } else { fseek(eepio_fp, 0L, SEEK_END); pos_end = ftell(eepio_fp); if (pos_end == -1) return eepio_error("ftell failed"); if (pos != pos_end) eepio_warning("Dump finished before EOF"); if (pos != (long)eep_header.eeplen) eepio_warning("Dump finished before length specified in header"); if (pos_end != (long)eep_header.eeplen) eepio_warning("EOF does not match length specified in header"); if (pos_end != (long)eep_header.eeplen) eepio_warning("%i bytes of file not processed"); } return !eepio_got_error(); } bool eepio_atom_start(enum atom_type_t *type, uint32_t *pdlen) { if (eepio_dir == EEPIO_WRITE) { eep_atom_header.type = *type; eep_atom_header.count = eep_atom_num; eepio_write_buf_pos = 0; eepio_buffer_writes = true; } crc_init(); if (eepio_dir == EEPIO_READ) { eepio_blob(&eep_atom_header, ATOM_HDR_SIZE); *type = eep_atom_header.type; if (eep_atom_num != eep_atom_header.count) eepio_error("Atom count mismatch (expected %u)", eep_atom_num); } eepio_atom_data_start = ftell(eepio_fp); if (pdlen) *pdlen = eep_atom_header.dlen - CRC_SIZE; return !eepio_got_error(); } void eepio_atom_end(void) { uint16_t crc_actual; if (eepio_dir == EEPIO_WRITE) { eep_atom_header.dlen = eepio_write_buf_pos + 2; eepio_buffer_writes = false; eepio_blob(&eep_atom_header, ATOM_HDR_SIZE); eepio_blob(eepio_write_buf, eepio_write_buf_pos); } crc_actual = crc_get(); eep_atom_crc = crc_actual; eepio_blob(&eep_atom_crc, 2); if (eepio_dir == EEPIO_READ) { long pos = ftell(eepio_fp); if (pos - eepio_atom_data_start != (long)eep_atom_header.dlen) eepio_warning("atom data length mismatch"); if (crc_actual != eep_atom_crc) eepio_warning("atom CRC16 mismatch. Calculated CRC16=0x%02x", crc_actual); } eep_atom_num++; } bool eepio_atom_var(struct var_blob_t *var) { if (eepio_dir == EEPIO_READ) { var->data = malloc(var->dlen); if (!var->data) eepio_fatal_error("out of memory"); } eepio_blob(var->data, var->dlen); return !eepio_got_error(); } bool eepio_atom_vinf(struct vendor_info_d *vinf) { eepio_blob(vinf, VENDOR_SIZE); eepio_string(&vinf->vstr, vinf->vslen); eepio_string(&vinf->pstr, vinf->pslen); return !eepio_got_error(); } bool eepio_atom_gpio(struct gpio_map_d *map) { eepio_blob(map, GPIO_SIZE); return !eepio_got_error(); } bool eepio_atom_gpio_bank1(struct gpio_map_d *map) { eepio_blob(map, GPIO_BANK1_SIZE); return !eepio_got_error(); } raspi-utils-20240903/eeptools/eeplib.h000066400000000000000000000051351466562725600175250ustar00rootroot00000000000000#ifndef _EEPLIB_H #define _EEPLIB_H #include #include #include #include #include // minimal sizes of data structures #define HEADER_SIZE 12 #define ATOM_SIZE 10 #define ATOM_HDR_SIZE 8 #define VENDOR_SIZE 22 #define GPIO_SIZE 30 #define GPIO_BANK1_SIZE 20 #define CRC_SIZE 2 #define GPIO_MIN 2 #define GPIO_COUNT 28 #define GPIO_COUNT_BANK1 18 #define GPIO_COUNT_TOTAL (GPIO_COUNT + GPIO_COUNT_BANK1) #define CRC16 0x8005 // Signature is "R-Pi" in ASCII. It is required to reversed (little endian) on disk. #define HEADER_SIGN (uint32_t)be32toh((((char)'R' << 24) | ((char)'-' << 16) | ((char)'P' << 8) | ((char)'i'))) #define count_of(x) ((sizeof(x) / sizeof(x[0]))) #define max(x, y) ((x) > (y) ? (x) : (y)) typedef void (*log_callback_t)(const char *, va_list); /* Atom types */ enum atom_type_t { ATOM_INVALID_TYPE = 0x0000, ATOM_VENDOR_TYPE = 0x0001, ATOM_GPIO_TYPE = 0x0002, ATOM_DT_TYPE = 0x0003, ATOM_CUSTOM_TYPE = 0x0004, ATOM_GPIO_BANK1_TYPE = 0x0005, ATOM_HINVALID_TYPE = 0xffff }; enum file_ver_t { EEP_VERSION_HATV1 = 0x01, EEP_VERSION_HATPLUS = 0x02, EEP_VERSION = EEP_VERSION_HATPLUS, }; enum eepio_dir_t { EEPIO_READ, EEPIO_WRITE, }; /* EEPROM header structure */ struct header_t { uint32_t signature; unsigned char ver; unsigned char res; uint16_t numatoms; uint32_t eeplen; }; /* Atom structure */ struct atom_t { uint16_t type; uint16_t count; uint32_t dlen; char *data; }; struct var_blob_t { uint8_t *data; uint32_t dlen; }; /* Vendor info atom data */ struct vendor_info_d { uint32_t serial[4]; // 0 = least significant, 3 = most significant uint16_t pid; uint16_t pver; unsigned char vslen; unsigned char pslen; char *vstr; char *pstr; }; /* GPIO map atom data */ struct gpio_map_d { unsigned char flags; unsigned char power; unsigned char pins[GPIO_COUNT]; }; extern struct header_t eep_header; extern struct atom_t eep_atom_header; extern uint16_t eep_atom_crc; void eepio_start(enum file_ver_t *pver, int *pnumatoms, enum eepio_dir_t dir, FILE *fp); bool eepio_end(void); bool eepio_atom_start(enum atom_type_t *ptype, uint32_t *pdlen); bool eepio_atom_vinf(struct vendor_info_d *vinf); bool eepio_atom_gpio(struct gpio_map_d *map); bool eepio_atom_gpio_bank1(struct gpio_map_d *map); bool eepio_atom_var(struct var_blob_t *var); void eepio_atom_end(void); void eepio_clear_error(void); bool eepio_error(const char *msg, ...); void eepio_warning(const char *msg, ...); bool eepio_got_error(void); void eepio_set_error_callback(log_callback_t callback); void eepio_set_warning_callback(log_callback_t callback); #endif raspi-utils-20240903/eeptools/eepmake.c000066400000000000000000000353131466562725600176700ustar00rootroot00000000000000/* * Parses EEPROM text file and createds binary .eep file * Usage: eepmake input_file output_file */ #include #include #include #include #include #include #include "eeplib.h" static struct vendor_info_d vinf; static struct gpio_map_d gpiomap_bank0, gpiomap_bank1; static struct var_blob_t dt_blob; struct var_blob_t *custom_blobs; static struct var_blob_t *data_blob; bool in_string; static bool product_serial_set, product_id_set, product_ver_set, vendor_set, product_set, gpio_drive_set, gpio_slew_set, gpio_hysteresis_set, gpio_power_set, bank1_gpio_drive_set, bank1_gpio_slew_set, bank1_gpio_hysteresis_set; static bool has_dt, has_gpio_bank0, has_gpio_bank1; static unsigned int custom_ct, data_cap, custom_cap; static enum file_ver_t hat_format = EEP_VERSION_HATPLUS; static void fatal_error(const char *msg, ...) { va_list ap; va_start(ap, msg); printf("FATAL: "); vprintf(msg, ap); printf("\n"); exit(1); } static void hatplus_unsupported(const char *cmd) { if (hat_format == EEP_VERSION_HATV1) return; printf("'%s' not supported on HAT+\n", cmd); exit(1); } static int write_binary(const char *out) { FILE *fp; unsigned int i; enum eepio_dir_t dir = EEPIO_WRITE; enum atom_type_t type; fp = fopen(out, "wb"); if (!fp) { printf("Error writing file %s\n", out); return -1; } eepio_start(&hat_format, NULL, dir, fp); type = ATOM_VENDOR_TYPE; eepio_atom_start(&type, NULL); eepio_atom_vinf(&vinf); eepio_atom_end(); if (has_gpio_bank0) { type = ATOM_GPIO_TYPE; eepio_atom_start(&type, NULL); eepio_atom_gpio(&gpiomap_bank0); eepio_atom_end(); } if (has_dt) { printf("Writing out DT...\n"); type = ATOM_DT_TYPE; eepio_atom_start(&type, NULL); eepio_atom_var(&dt_blob); eepio_atom_end(); } for (i = 0; i < custom_ct; i++) { type = ATOM_CUSTOM_TYPE; eepio_atom_start(&type, NULL); eepio_atom_var(&custom_blobs[i]); eepio_atom_end(); } if (has_gpio_bank1) { type = ATOM_GPIO_BANK1_TYPE; eepio_atom_start(&type, NULL); eepio_atom_gpio_bank1(&gpiomap_bank1); eepio_atom_end(); } eepio_end(); fclose(fp); return 0; } static void add_data_byte(int byte) { if (data_blob->dlen >= data_cap) { data_cap = max(data_cap * 2, 64); data_blob->data = realloc(data_blob->data, data_cap); } data_blob->data[data_blob->dlen++] = byte; } static int parse_string(const char *p) { /* Read string data, stopping at escaped-doublequote */ int c; while ((c = *(p++)) != 0) { if (c == '\\') { int c2 = *(p++); if (c2 == 'r') c = '\r'; else if (c2 == '\\') c = '\\'; // Technically a no-op else if (c2 == '0') { add_data_byte(0); break; // Ignore the rest of the line } else if (c2 == '"') { in_string = false; break; } else fatal_error("Bad escape sequence '\\%c'", c2); } else if (c == '\r') // Real CRs are escaped, so ignore this as MS droppings continue; add_data_byte(c); } return 0; } static int parse_data(char *c) { char *i = c; char *j = c; int len; int k; while (isspace(*j)) j++; if (j[0] == '"') { if (j[1] == '\n' || j[1] == '\r') { // Ignore anything else in_string = true; return 0; } // Simple in-line string while (1) { int byte = *(++j); if (byte == '"') return 0; if (!isprint(byte)) fatal_error("Bad character 0x%02x in simple string '%s'", byte, c); add_data_byte(byte); } return 0; } /* Filter out the non-hex-digits */ while (*j != '\0') { *i = *j++; if (isxdigit(*i)) i++; } *i = '\0'; len = strlen(c); if (len % 2 != 0) { printf("Error: data must have an even number of hex digits\n"); return -1; } else { for (k = 0; k < len / 2; k++) { int byte; sscanf(c, "%2x", &byte); add_data_byte(byte); c += 2; } } return 0; } static void init_blob(struct var_blob_t *blob) { blob->data = NULL; blob->dlen = 0; } struct var_blob_t *add_custom_blob(void) { struct var_blob_t *blob; if (custom_cap == custom_ct) { custom_cap = max(custom_cap * 2, 1); custom_blobs = realloc(custom_blobs, custom_cap * sizeof(struct var_blob_t)); } blob = &custom_blobs[custom_ct++]; init_blob(blob); return blob; } static void finish_data(void) { if (data_blob) { data_blob->data = realloc(data_blob->data, data_blob->dlen); data_blob = NULL; data_cap = 0; } } static int parse_command(char *cmd, char *c) { int val; uint32_t high1, high2; char *fn, *pull; char pin; bool valid; /* Vendor info related part */ if (strcmp(cmd, "product_uuid") == 0) { product_serial_set = true; // required field high1 = 0; high2 = 0; sscanf(c, "%100s %08x-%04x-%04x-%04x-%04x%08x\n", cmd, &vinf.serial[3], &high1, &vinf.serial[2], &high2, &vinf.serial[1], &vinf.serial[0]); vinf.serial[2] |= high1 << 16; vinf.serial[1] |= high2 << 16; if ((vinf.serial[3] == 0) && (vinf.serial[2] == 0) && (vinf.serial[1] == 0) && (vinf.serial[0] == 0)) { // read 128 random bits from /dev/urandom int random_file = open("/dev/urandom", O_RDONLY); ssize_t result = read(random_file, vinf.serial, 16); close(random_file); if (result <= 0) { printf("Unable to read from /dev/urandom to set up UUID"); return -1; } else { // put in the version vinf.serial[2] = (vinf.serial[2] & 0xffff0fff) | 0x00004000; // put in the variant vinf.serial[1] = (vinf.serial[1] & 0x3fffffff) | 0x80000000; printf("UUID=%08x-%04x-%04x-%04x-%04x%08x\n", vinf.serial[3], vinf.serial[2] >> 16, vinf.serial[2] & 0xffff, vinf.serial[1] >> 16, vinf.serial[1] & 0xffff, vinf.serial[0]); } } } else if (strcmp(cmd, "product_id") == 0) { product_id_set = true; // required field sscanf(c, "%100s %hx", cmd, &vinf.pid); } else if (strcmp(cmd, "product_ver") == 0) { product_ver_set = true; // required field sscanf(c, "%100s %hx", cmd, &vinf.pver); } else if (strcmp(cmd, "vendor") == 0) { vendor_set = true; // required field vinf.vstr = malloc(256); sscanf(c, "%100s \"%255[^\"]\"", cmd, vinf.vstr); vinf.vslen = strlen(vinf.vstr); } else if (strcmp(cmd, "product") == 0) { product_set = true; // required field vinf.pstr = malloc(256); sscanf(c, "%100s \"%255[^\"]\"", cmd, vinf.pstr); vinf.pslen = strlen(vinf.pstr); } /* GPIO map related part */ else if (strcmp(cmd, "gpio_drive") == 0) { hatplus_unsupported(cmd); gpio_drive_set = true; // required field has_gpio_bank0 = true; sscanf(c, "%100s %1x", cmd, &val); if (val > 8 || val < 0) printf("Warning: gpio_drive property in invalid region, using default value instead\n"); else gpiomap_bank0.flags |= val; } else if (strcmp(cmd, "gpio_slew") == 0) { hatplus_unsupported(cmd); gpio_slew_set = true; // required field has_gpio_bank0 = true; sscanf(c, "%100s %1x", cmd, &val); if (val > 2 || val < 0) printf("Warning: gpio_slew property in invalid region, using default value instead\n"); else gpiomap_bank0.flags |= val << 4; } else if (strcmp(cmd, "gpio_hysteresis") == 0) { hatplus_unsupported(cmd); gpio_hysteresis_set = true; // required field has_gpio_bank0 = true; sscanf(c, "%100s %1x", cmd, &val); if (val > 2 || val < 0) printf("Warning: gpio_hysteresis property in invalid region, using default value instead\n"); else gpiomap_bank0.flags |= val << 6; } else if (strcmp(cmd, "back_power") == 0) { hatplus_unsupported(cmd); gpio_power_set = true; // required field has_gpio_bank0 = true; sscanf(c, "%100s %1x", cmd, &val); if (val > 2 || val < 0) printf("Warning: back_power property in invalid region, using default value instead\n"); else gpiomap_bank0.power = val; } else if (strcmp(cmd, "bank1_gpio_drive") == 0) { hatplus_unsupported(cmd); bank1_gpio_drive_set = true; // required field if bank 1 is used has_gpio_bank1 = true; sscanf(c, "%100s %1x", cmd, &val); if (val > 8 || val < 0) printf("Warning: bank1 gpio_drive property in invalid region, using default value instead\n"); else gpiomap_bank1.flags |= val; } else if (strcmp(cmd, "bank1_gpio_slew") == 0) { hatplus_unsupported(cmd); bank1_gpio_slew_set = true; // required field if bank 1 is used has_gpio_bank1 = true; sscanf(c, "%100s %1x", cmd, &val); if (val > 2 || val < 0) printf("Warning: bank1 gpio_slew property in invalid region, using default value instead\n"); else gpiomap_bank1.flags |= val << 4; } else if (strcmp(cmd, "bank1_gpio_hysteresis") == 0) { hatplus_unsupported(cmd); bank1_gpio_hysteresis_set = true; // required field if bank 1 is used has_gpio_bank1 = true; sscanf(c, "%100s %1x", cmd, &val); if (val > 2 || val < 0) printf("Warning: bank1 gpio_hysteresis property in invalid region, using default value instead\n"); else gpiomap_bank1.flags |= val << 6; } else if (strcmp(cmd, "setgpio") == 0) { hatplus_unsupported(cmd); fn = malloc(101); pull = malloc(101); sscanf(c, "%100s %d %100s %100s", cmd, &val, fn, pull); if (val < GPIO_MIN || val >= GPIO_COUNT_TOTAL) printf("Error: GPIO number out of bounds\n"); else { struct gpio_map_d *gpiomap = &gpiomap_bank0; if (val >= GPIO_COUNT) { gpiomap = &gpiomap_bank1; val -= GPIO_COUNT; has_gpio_bank1 = true; } valid = true; pin = 0; if (strcmp(fn, "INPUT") == 0) { // no action } else if (strcmp(fn, "OUTPUT") == 0) { pin |= 1; } else if (strcmp(fn, "ALT0") == 0) { pin |= 4; } else if (strcmp(fn, "ALT1") == 0) { pin |= 5; } else if (strcmp(fn, "ALT2") == 0) { pin |= 6; } else if (strcmp(fn, "ALT3") == 0) { pin |= 7; } else if (strcmp(fn, "ALT4") == 0) { pin |= 3; } else if (strcmp(fn, "ALT5") == 0) { pin |= 2; } else { printf("Error at setgpio: function type not recognised\n"); valid = false; } if (strcmp(pull, "DEFAULT") == 0) { // no action } else if (strcmp(pull, "UP") == 0) { pin |= 1 << 5; } else if (strcmp(pull, "DOWN") == 0) { pin |= 2 << 5; } else if (strcmp(pull, "NONE") == 0) { pin |= 3 << 5; } else { printf("Error at setgpio: pull type not recognised\n"); valid = false; } pin |= 1 << 7; // board uses this pin if (valid) gpiomap->pins[val] = pin; } } else if (strcmp(cmd, "dt_blob") == 0) { finish_data(); if (has_dt) fatal_error("Only one dt_blob allowed"); has_dt = true; data_blob = &dt_blob; c += strlen("dt_blob"); return parse_data(c); } else if (strcmp(cmd, "custom_data") == 0) { finish_data(); data_blob = add_custom_blob(); c += strlen("custom_data"); return parse_data(c); } else if (strcmp(cmd, "end") == 0) { // close last data atom finish_data(); } else if (data_blob) { return parse_data(c); } return 0; } static int read_text(const char *in) { FILE *fp; char *line = NULL; char *c = NULL; size_t len = 0; ssize_t read; int linect = 0; char *command = (char *)malloc(101); int i; has_dt = false; printf("Opening file '%s' for read\n", in); fp = fopen(in, "r"); if (fp == NULL) { printf("Error opening input file '%s'\n", in); return -1; } // allocating memory and setting up required atoms custom_cap = 1; custom_blobs = malloc(sizeof(struct atom_t) * custom_cap); in_string = false; while ((read = getline(&line, &len, fp)) != -1) { linect++; c = line; if (in_string) { if (parse_string(c)) return -1; continue; } for (i = 0; i < read; i++) { if (c[i] == '#') c[i] = '\0'; } while (isspace(*c)) ++c; if (*c == '\0' || *c == '\n' || *c == '\r') { // empty line, do nothing } else if (isalnum(*c)) { sscanf(c, "%100s", command); if (parse_command(command, c)) return -1; } else printf("Can't parse line %u: %s", linect, c); } finish_data(); if (!product_serial_set || !product_id_set || !product_ver_set || !vendor_set || !product_set) printf("Warning: required fields missing in vendor information, using default values\n"); if (hat_format == EEP_VERSION_HATV1 && !has_gpio_bank0) fatal_error("GPIO bank 0 is required for HAT V1"); if (has_gpio_bank0 && (!gpio_drive_set || !gpio_slew_set || !gpio_hysteresis_set || !gpio_power_set)) printf("Warning: required fields missing in GPIO map, using default values\n"); if (has_gpio_bank1 && (!bank1_gpio_drive_set || !bank1_gpio_slew_set || !bank1_gpio_hysteresis_set)) printf("Warning: required fields missing in GPIO map of bank 1, using default values\n"); if (hat_format != EEP_VERSION_HATV1 && dt_blob.data && !isalnum(dt_blob.data[0])) fatal_error("Only embed the name of the overlay"); printf("Done reading\n"); return 0; } static int read_blob_file(const char *in, const char *type, struct var_blob_t *blob) { FILE *fp; unsigned long size = 0; printf("Opening %s file '%s' for read\n", type, in); fp = fopen(in, "rb"); if (fp == NULL) fatal_error("Error opening input file '%s'", in); fseek(fp, 0L, SEEK_END); size = ftell(fp); fseek(fp, 0L, SEEK_SET); printf("Adding %lu bytes of %s data\n", size, type); blob->dlen = size; blob->data = malloc(size); if (!fread(blob->data, size, 1, fp)) goto err; fclose(fp); return 0; err: printf("Unexpected EOF or error occurred\n"); fclose(fp); return -1; } int main(int argc, char *argv[]) { const char *output_file; int argn = 1; int ret; while (argn < argc && argv[argn][0] == '-') { const char *arg = argv[argn++]; if (!strcmp(arg, "-v1")) { printf("[ Reverting to legacy HAT V1 format ]\n"); hat_format = EEP_VERSION_HATV1; } else { printf("Unrecognised option '%s'\n", arg); return 1; } } if ((argc - argn) < 2) { printf("Wrong input format.\n"); printf("Try 'eepmake [-v1] input_file output_file [dt_file] [-c custom_file_1 ... custom_file_n]'\n"); return 1; } ret = read_text(argv[argn++]); if (ret) fatal_error("Error reading and parsing input, aborting"); output_file = argv[argn++]; if (argn < argc && argv[argn][0] != '-') { // DT file specified if (hat_format == EEP_VERSION_HATPLUS) fatal_error("For HAT+ EEPROMs, specify the name of the DT overlay in the text file"); ret = read_blob_file(argv[argn++], "DT", &dt_blob); if (ret) fatal_error("Error reading DT file, aborting"); has_dt = true; } if (argn < argc && argv[argn][0] == '-') { if (strcmp(argv[argn], "-c") != 0) fatal_error("Unknown option - expected '-c'"); argn++; } while (argn < argc) { struct var_blob_t *blob = add_custom_blob(); // new custom data file ret = read_blob_file(argv[argn++], "custom data", blob); if (ret) fatal_error("Error reading custom file, aborting"); } printf("Writing out...\n"); ret = write_binary(output_file); if (ret) fatal_error("Error writing output"); printf("Done.\n"); return 0; } raspi-utils-20240903/eeptools/eeprom_settings.txt000077500000000000000000000027301466562725600220650ustar00rootroot00000000000000######################################################################## # EEPROM settings text file # # Edit this file for your particular board and run through eepmake tool, # then use eepflash tool to write to attached HAT ID EEPROM # # Tools available: # eepmake Parses EEPROM text file and creates binary .eep file # eepdump Dumps a binary .eep file as human readable text (for debug) # eepflash Write or read .eep binary image to/from HAT EEPROM # ######################################################################## # 128 bit UUID. If left at zero eepmake tool will auto-generate # RFC 4122 compliant UUID product_uuid 00000000-0000-0000-0000-000000000000 # 16 bit product id product_id 0x0000 # 16 bit product version product_ver 0x0000 # ASCII vendor string (max 255 characters) vendor "ACME Technology Company" # ASCII product string (max 255 characters) product "Special Sensor Board" # Which Device Tree overlay to load dt_blob "acme-sensor" # Custom binary data custom_data deadbeef c00 1c0d e end custom_data " Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. \" raspi-utils-20240903/eeptools/eeprom_v1_settings.txt000077500000000000000000000077061466562725600225030ustar00rootroot00000000000000######################################################################## # EEPROM settings text file # # Edit this file for your particular board and run through eepmake tool, # then use eepflash tool to write to attached HAT ID EEPROM # # Tools available: # eepmake Parses EEPROM text file and creates binary .eep file # eepdump Dumps a binary .eep file as human readable text (for debug) # eepflash Write or read .eep binary image to/from HAT EEPROM # ######################################################################## ######################################################################## # Vendor info # 128 bit UUID. If left at zero eepmake tool will auto-generate # RFC 4122 compliant UUID product_uuid 00000000-0000-0000-0000-000000000000 # 16 bit product id product_id 0x0000 # 16 bit product version product_ver 0x0000 # ASCII vendor string (max 255 characters) vendor "ACME Technology Company" # ASCII product string (max 255 characters) product "Special Sensor Board" # Custom binary data custom_data deadbeef c00 1c0d e end ######################################################################## # GPIO bank settings, set to nonzero to change from the default. # NOTE these setting can only be set per BANK, uncommenting any of # these will force the bank to use the custom setting. # drive strength, 0=default, 1-8=2,4,6,8,10,12,14,16mA, 9-15=reserved gpio_drive 0 # 0=default, 1=slew rate limiting, 2=no slew limiting, 3=reserved gpio_slew 0 # 0=default, 1=hysteresis disabled, 2=hysteresis enabled, 3=reserved gpio_hysteresis 0 # If board back-powers Pi via 5V GPIO header pins: # 0 = board does not back-power # 1 = board back-powers and can supply the Pi with a minimum of 1.3A # 2 = board back-powers and can supply the Pi with a minimum of 2A # 3 = reserved # If back_power=2 then USB high current mode will be automatically # enabled on the Pi back_power 0 ######################################################################## # GPIO pins, uncomment for GPIOs used on board # Options for FUNCTION: INPUT, OUTPUT, ALT0-ALT5 # Options for PULL: DEFAULT, UP, DOWN, NONE # NB GPIO0 and GPIO1 are reserved for ID EEPROM so cannot be set # GPIO FUNCTION PULL # ---- -------- ---- #setgpio 2 INPUT DEFAULT #setgpio 3 INPUT DEFAULT #setgpio 4 INPUT DEFAULT #setgpio 5 INPUT DEFAULT #setgpio 6 INPUT DEFAULT #setgpio 7 INPUT DEFAULT #setgpio 8 INPUT DEFAULT #setgpio 9 INPUT DEFAULT #setgpio 10 INPUT DEFAULT #setgpio 11 INPUT DEFAULT #setgpio 12 INPUT DEFAULT #setgpio 13 INPUT DEFAULT #setgpio 14 INPUT DEFAULT #setgpio 15 INPUT DEFAULT #setgpio 16 INPUT DEFAULT #setgpio 17 INPUT DEFAULT #setgpio 18 INPUT DEFAULT #setgpio 19 INPUT DEFAULT #setgpio 20 INPUT DEFAULT #setgpio 21 INPUT DEFAULT #setgpio 22 INPUT DEFAULT #setgpio 23 INPUT DEFAULT #setgpio 24 INPUT DEFAULT #setgpio 25 INPUT DEFAULT #setgpio 26 INPUT DEFAULT #setgpio 27 INPUT DEFAULT ######################################################################## # Settings for bank 1 (only valid for CM1/3/3+/4S). Setting one or more of # these GPIOs requires setting of drive, slew and hysteresis for bank 1. # bank1_gpio_drive 0 # bank1_gpio_slew 0 # bank1_gpio_hysteresis 0 #setgpio 28 INPUT DEFAULT #setgpio 29 INPUT DEFAULT #setgpio 30 INPUT DEFAULT #setgpio 31 INPUT DEFAULT #setgpio 32 INPUT DEFAULT #setgpio 33 INPUT DEFAULT #setgpio 34 INPUT DEFAULT #setgpio 35 INPUT DEFAULT #setgpio 36 INPUT DEFAULT #setgpio 37 INPUT DEFAULT #setgpio 38 INPUT DEFAULT #setgpio 39 INPUT DEFAULT #setgpio 40 INPUT DEFAULT #setgpio 41 INPUT DEFAULT #setgpio 42 INPUT DEFAULT #setgpio 43 INPUT DEFAULT #setgpio 44 INPUT DEFAULT #setgpio 45 INPUT DEFAULT raspi-utils-20240903/otpset/000077500000000000000000000000001466562725600155745ustar00rootroot00000000000000raspi-utils-20240903/otpset/CMakeLists.txt000066400000000000000000000002671466562725600203410ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.10...3.27) include(GNUInstallDirs) #set project name project(otpset) #add executables install(PROGRAMS otpset DESTINATION ${CMAKE_INSTALL_BINDIR}) raspi-utils-20240903/otpset/README.md000066400000000000000000000010621466562725600170520ustar00rootroot00000000000000# otpset Wrapper script for programming the OTP on Raspberry Pi devices. OTP setting is done via `vcmailbox` calls, and are not particularly easy to use. This wrapper makes it easier to read and set customer OTP bits, and provides some level of error checking to the process. Once OTP bits are set, they CANNOT be unset, so this wrapper requests confirmation before performing destructive/irreversible operations. See official docs on the subject here: https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#industrial-use-of-the-raspberry-pi raspi-utils-20240903/otpset/otpset000077500000000000000000000134561466562725600170510ustar00rootroot00000000000000#!/usr/bin/env python3 # # Copyright (c) 2022 Raspberry Pi Ltd. # # SPDX-License-Identifier: BSD-3-Clause # import argparse import subprocess import sys customer_otp_offset = 36 legacy_offset = 8 def ParseCommandLine(): parser = argparse.ArgumentParser(description='A helper application for reading and setting Raspberry Pi OTP (One Time Programmable) bits.\n Take care when using this application, changes to OTP are irreversible.', epilog="Script provided by Raspberry Pi Ltd under a BSD-3-Clause license") parser.add_argument("data", nargs="*", type=str, help="Customer OTP data to set. 1 to 8 32 bit values. Can be decimal, hex (0x...), or binary (0b...)") parser.add_argument("-r", "--row", type=int, help="Customer OTP start row. Valid values are 0 to 7", default=-1, choices=range(0,8), metavar="0-7") parser.add_argument("-c", "--customer", action='store_true', help="Read customer OTP bits") parser.add_argument("-p", "--pi", action='store_true', help="Read Raspberry Pi OTP bits") parser.add_argument("-v", "--verbose", action='store_true', help="Extra detailed messages") parser.add_argument("--yes", action='store_true', help="Make changes to OTP (without this a dry run is done). WARNING: Changes to OTP are irreversible") parser.add_argument("--lock", action='store_true', help="Permanently disable writing to customer OTP. WARNING: This operation is irreversible") parser.add_argument("-f", "--fake", action='store_true', help="Use fake data for testing") if len(sys.argv) == 1: parser.print_help(sys.stderr) sys.exit(1) return parser.parse_args() def ReadOTP(args): # Map key is actually the value from the docs, so we do need to subtract/add the legacy_offset from it where necessary. row_info = { 17: "Bootmode register", 18: "Bootmode register copy", 28: "Serial number", 29: "One's compliment of serial number", 30: "Revision code", 33: "Extended board revision", 36: "Customer OTP Row 0", 37: "Customer OTP Row 1", 38: "Customer OTP Row 2", 39: "Customer OTP Row 3", 40: "Customer OTP Row 4", 41: "Customer OTP Row 5", 42: "Customer OTP Row 6", 43: "Customer OTP Row 7", 46: "MPEG2 decode key", 47: "WVC1 decode key", 64: "MAC address", 65: "MAC address", 66: "Advanced boot mode", } if args.fake: result = b'08:00000000\n09:00000000\n10:00000000\n11:00000000\n12:00000000\n13:00000000\n14:00000000\n15:00000000\n16:00280000\n17:000008b0\n18:000008b0\n19:ffffffff\n20:ffffffff\n21:ffffffff\n22:ffffffff\n23:ffffffff\n24:ffffffff\n25:ffffffff\n26:ffffffff\n27:00005d5d\n28:58d2eb5c\n29:a72d14a3\n30:00c03111\n31:00000000\n32:00000000\n33:00000000\n34:00000000\n35:00000000\n36:00000000\n37:00000000\n38:00000000\n39:00000000\n40:00000000\n41:00000000\n42:00000000\n43:00000000\n44:00000000\n45:00000000\n46:00000000\n47:00000000\n48:00000000\n49:00000000\n50:00000000\n51:00000000\n52:00000000\n53:00000000\n54:00000000\n55:00000000\n56:00000000\n57:00000000\n58:00000000\n59:00000000\n60:00000000\n61:00000000\n62:00000000\n63:00000000\n64:00650000\n65:00000000\n66:00000000\n' else: result = subprocess.check_output(['vcgencmd', 'otp_dump']) result = result.split(b"\n") result.pop() # don't need the last entry, it's fluff for i, val in enumerate(result): result[i] = int(val[3:], base=16) if args.customer: if i >= customer_otp_offset - legacy_offset and i < 44 - legacy_offset: print(i - (customer_otp_offset - legacy_offset), "0x{:08x} b'{:032b}".format(result[i], result[i])) else : print(i + legacy_offset, "0x{:08x} b'{:032b} {}".format(result[i], result[i], row_info.get(i + legacy_offset, ""))) def WriteOTP(args): # At this stage we know the args.rows are valid as argparser sorted that out, and we have an number of position parameters in args.data which contain the 32 bit values to write # We also have the flag to say defo do this, rather than dry run in args.yes # Format of the mailbox call is # vcmailbox [8 + number * 4] [8 + number * 4] [start_num] [number] [value] [value] [value] ... # write command is 0x00038021 write_cmd = 0x00038021 num_rows = len(args.data) first_row = 0 if args.row == -1 else args.row if (num_rows < 1 or num_rows > 8 - first_row): print("Invalid combination of start row ({}) and number of words to write to OTP ({})". format(first_row, num_rows)) sys.exit(-1) num_bytes = (num_rows * 4) + 8 cmd = ['vcmailbox', hex(write_cmd), str(num_bytes), str(num_bytes), str(first_row), str(num_rows) ] for i, val in enumerate(args.data): cmd.append(hex(int(val, 0))) if args.verbose : print('Setting {} rows of customer OTP from row {} onwards'.format(num_rows, first_row)) print('Sending VC mailbox call: ',' '.join(cmd)) if args.yes: print("Are you ABSOLUTELY sure you want to make changes to the customer OTP?. This operation is IRREVERSIBLE. Type YES to confirm") confirm = input() if confirm == 'YES': result = subprocess.check_output(cmd) def LockOTP(args): cmd = ['vcmailbox', '0x00038021', '8', '8', '0xffffffff', '0xaffe0000' ] if args.verbose : print('Sending VC mailbox call to lock OTP: ',' '.join(cmd)) print("Are you ABSOLUTELY sure you want to permamently disable changes to the customer OTP?. This operation is IRREVERSIBLE. Type YES to confirm") confirm = input() if confirm == 'YES': result = subprocess.check_output(cmd) ################################################################################### # main execution starteth here args = ParseCommandLine() if args.customer or args.pi : ReadOTP(args) sys.exit(0) if args.lock : LockOTP(args) sys.exit(0) WriteOTP(args) raspi-utils-20240903/overlaycheck/000077500000000000000000000000001466562725600167355ustar00rootroot00000000000000raspi-utils-20240903/overlaycheck/CMakeLists.txt000066400000000000000000000004221466562725600214730ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.10...3.27) include(GNUInstallDirs) #set project name project(overlaycheck) #add executables install(PROGRAMS overlaycheck DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES overlaycheck_exclusions.txt DESTINATION ${CMAKE_INSTALL_BINDIR}) raspi-utils-20240903/overlaycheck/README.md000066400000000000000000000012511466562725600202130ustar00rootroot00000000000000 # overlaycheck overlaycheck is a tool for validating the overlay files and README in a kernel source tree. Note that overlaycheck makes use of ovmerge, dtmerge, dtc, dtdiff and fdtget, and therefore requires them all to be installed and on the path. **Build Instructions** Install the prerequisites with "sudo apt install cmake device-tree-compiler" - you need at least version 3.10 of cmake. Run the following commands, either here or in the top-level directory to build and install everything: - *cmake .* - *make* - *sudo make install* **Usage** * overlaycheck * overlaycheck -v (for verbose output) * overlaycheck -s (to see all the strict warnings from dtc) raspi-utils-20240903/overlaycheck/overlaycheck000077500000000000000000000546641466562725600213610ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use File::Temp qw/ tempdir /; my $DTMERGE = "dtmerge"; my $TMPDIR = tempdir( CLEANUP => 1); my $MERGED_DTB = "$TMPDIR/merged.dtb"; my @base_files = ( "bcm2712-rpi-5-b", "bcm2712-rpi-cm5-cm5io", "bcm2711-rpi-4-b", "bcm2711-rpi-cm4", "bcm2711-rpi-cm4s", "bcm2708-rpi-b", "bcm2708-rpi-b-rev1", "bcm2708-rpi-b-plus", "bcm2708-rpi-zero", "bcm2708-rpi-zero-w", "bcm2708-rpi-cm", "bcm2709-rpi-2-b", "bcm2710-rpi-3-b", "bcm2710-rpi-3-b-plus", "bcm2710-rpi-cm3", "bcm2710-rpi-zero-2-w", ); my @extra_base_files = ( "bcm2709-rpi-cm2", "bcm2710-rpi-2-b", "bcm2711-rpi-400", "bcm2712-rpi-cm5-cm4io", "bcm2712-rpi-cm5l-cm5io", "bcm2712-rpi-cm5l-cm4io", ); my %platform_reps = ( 'bcm2835' => 'bcm2710-rpi-3-b', 'bcm2711' => 'bcm2711-rpi-4-b', 'bcm2712' => 'bcm2712-rpi-5-b', ); $ENV{'LD_LIBRARY_PATH'} = "$ENV{'HOME'}/lib"; my %overlay_checkers = ( # With the current overlays, the exclusions are no longer necessary, but an # example for i2c-sensor would be: # '^(addr|lm75addr|int_pin|i2c\d|i2c_csi_dsi|no_timeout)$' 'i2c-rtc' => [ \&container_checker, undef ], 'i2c-fan' => [ \&container_checker, undef ], 'i2c-sensor' => [ \&container_checker, undef ], ); my $word_pattern = '[0-9a-zA-Z0-9][-_a-zA-Z0-9]*'; my $readme_param_pattern = '([-_a-zA-Z0-9]|<[a-z]>|<[a-z]-[a-z]>)*'; my $verbose; my $strict_dtc; my $try_all; my $fail = 0; while (@ARGV && $ARGV[0] =~ /^-/) { my $arg = shift @ARGV; if ($arg eq '-v') { $verbose = 1; } elsif ($arg eq '-s') { $strict_dtc = 1; } elsif ($arg eq '-t') { $try_all = 1; } else { fatal_error("Unknown option '$arg'"); } } # Aims: # * Check that each overlay has an entry in the README # * Check that the parameters of each overlay are in the README # * Check that the parameters of the base DTBs are in the README # * Check that each overlay has an entry in the Makefile # Process: # 1) Parse the base dts's and overlays # 2) Parse the README # 3) Compare the two # 4) Parse the Makefile, and compare with the others my $kerndir = `git rev-parse --show-toplevel 2>/dev/null`; chomp($kerndir); fatal_error("This isn't a Linux repository") if (!-d "$kerndir/kernel"); # 32-bit first, to avoid the #include "arm/broadcom..." versions in the arm64 tree my $dtspath = (-f $kerndir."/arch/arm/boot/dts/broadcom/Makefile") ? $kerndir."/arch/arm/boot/dts/broadcom" : $kerndir."/arch/arm/boot/dts"; my $dts64path = (-f $kerndir."/arch/arm64/boot/dts/broadcom/Makefile") ? $kerndir."/arch/arm64/boot/dts/broadcom" : $kerndir."/arch/arm64/boot/dts"; my (@base32_files, @base64_files, @extra_base32_files, @extra_base64_files); foreach my $base (@base_files) { if (-f $dtspath."/".$base.".dts") { push @base32_files, $base; } elsif (-f $dts64path."/".$base.".dts") { push @base64_files, $base; } } foreach my $base (@extra_base_files) { if (-f $dtspath."/".$base.".dts") { push @extra_base32_files, $base; } elsif (-f $dts64path."/".$base.".dts") { push @extra_base64_files, $base; } } my @dtsgroups = ( [ $dts64path, \@base64_files ], [ $dtspath, \@base32_files ], ); my @extra_dtsgroups = ( [ $dts64path, \@extra_base64_files ], [ $dtspath, \@extra_base32_files ], ); my $overlaysdir = $kerndir."/arch/arm/boot/dts/overlays"; my @cpp_cmd = ( 'arm-linux-gnueabihf-cpp', '-nostdinc', '-I.', "-I$kerndir/include", '-Ioverlays', '-undef', '-D__DTS__', '-x', 'assembler-with-cpp' ); my $DTC = "$kerndir/scripts/dtc/dtc"; my $exclusions_file = $0 . "_exclusions.txt"; my @warnings_to_suppress = ( 'unit_address_vs_reg', 'simple_bus_reg', 'unit_address_format', 'interrupts_property', 'gpios_property', 'label_is_string', 'unique_unit_address', 'avoid_unnecessary_addr_size' ); push @warnings_to_suppress, ( 'pci_device_reg', 'pci_device_bus_num', 'reg_format', 'interrupt_provider', 'dma_ranges_format', 'avoid_default_addr_size' ) if (!$strict_dtc); my @dtc_opts; foreach my $warn (@warnings_to_suppress) { push @dtc_opts, '-W', "no-$warn" if (system("$DTC -W no-$warn -v >/dev/null 2>&1") == 0); } my $dtc_opts = join(' ', @dtc_opts); # Parse the exclusions my ($ignore_missing, $ignore_vestigial) = parse_exclusions($exclusions_file); # Parse the README chdir($overlaysdir); my $readme = parse_readme("README"); # Parse the base dts files and overlays my $source = parse_source_files($overlaysdir, @dtsgroups, @extra_dtsgroups); # Compare the two my ($left_only, $common, $right_only) = compare_sets([keys(%$source)], [keys(%$readme)]); ($left_only) = compare_sets($left_only, [keys(%$ignore_missing)]); ($right_only) = compare_sets($right_only, [keys(%$ignore_vestigial)]); list_print("Overlays without documentation", $left_only); list_print("Vestigial overlay documentation", $right_only); $fail ||= (@$left_only || @$right_only); foreach my $overlay (@$common) { my ($l, $b, $r) = compare_sets($source->{$overlay}, $readme->{$overlay}); my $rpos = 0; while ($rpos < @$r) { my $rv = $r->[$rpos]; if (($rv =~ s/<[i-z]>/[0-9a-fA-F]+/g) || ($rv =~ s/<[a-h]>/[a-z]/g)) { my $lpos = 0; my $found = 0; while ($lpos < @$l) { if ($l->[$lpos] =~ /^$rv$/) { splice(@$l, $lpos, 1); $found = 1; } else { $lpos++; } } splice(@$r, $rpos, 1) if ($found); $rpos++ if (!$found); } else { $rpos++; } } ($l) = compare_sets($l, $ignore_missing->{$overlay}); ($r) = compare_sets($r, $ignore_vestigial->{$overlay}); list_print("$overlay undocumented parameters", $l); list_print("$overlay vestigial parameter documentation", $r); $fail ||= (@$l || @$r); } # Parse the Makefile chdir($overlaysdir); my $makefile = parse_makefile("Makefile"); ($left_only, $common, $right_only) = compare_sets([keys(%$source)], $makefile); list_print("Overlays missing from the Makefile", $left_only); list_print("Vestigial overlay Makefile entries", $right_only); $fail ||= (@$left_only || @$right_only); # Now some build/runtime checks foreach my $group (@dtsgroups, @extra_dtsgroups) { chdir($group->[0]); foreach my $base (@{$group->[1]}) { dtc_cpp("$base.dts", "$TMPDIR/$base.dtb"); if (system("ovmerge -q $base.dts") >> 8) { error(" Error ^ in base file $base.dts\n"); } } } chdir($overlaysdir); dtc_cpp("overlay_map.dts", "$TMPDIR/overlay_map.dtb") if (-r "overlay_map.dts"); foreach my $overlay (sort(keys(%$source))) { next if ($overlay =~ /^> 8) { error(" Error ^ in overlay $overlay\n"); } dtc_cpp("${overlay}-overlay.dts", "$TMPDIR/$overlay.dtbo"); my $overlay_props = $source->{$overlay}[0]; my $base; while (my ($plat, $dts) = each(%platform_reps)) { if ($overlay_props->{$plat}) { $base = $dts; last; } } if ($base && (system($DTMERGE, "$TMPDIR/$base.dtb", $MERGED_DTB, "$TMPDIR/$overlay.dtbo") >> 8)) { error(" Error ^ in overlay $overlay\n"); } } if ($try_all) { foreach my $overlay (sort(keys(%$source))) { next if ($overlay =~ /^{$overlay}[0]; foreach my $base (@base_files) { next if (!-f "$TMPDIR/$base.dtb"); next if (($overlay =~ /(wifi|bt)/) && ($base !~ /^(bcm2708-rpi-zero-w|bcm2709-rpi-zero-2|bcm2710-rpi-3-|bcm2711-rpi-(4|cm4$)|bcm2712-rpi-(5|cm5))/)); next if ($overlay_props->{'bcm2711'} && $base !~ /^bcm2711/); next if ($overlay_props->{'bcm2712'} && $base !~ /^bcm2712/); next if (system("$DTMERGE $TMPDIR/$base.dtb $MERGED_DTB $TMPDIR/$overlay.dtbo >/dev/null 2>&1") == ((-2 & 0xff) << 8)); error("Failed to merge $overlay with $base") if (system($DTMERGE, $verbose ? ('-d') : (), "$TMPDIR/$base.dtb", $MERGED_DTB, "$TMPDIR/$overlay.dtbo") != 0); } my $checker = $overlay_checkers{$overlay}; ($checker->[0])->($overlay, $checker->[1], $source->{$overlay}) if ($checker); } } rmdir($TMPDIR); printf("%s\n", $fail ? "Failed" : "OK"); exit($fail); sub compare_sets { my ($l, $r) = @_; my (@lonly, @both, @ronly); my @l = ($l ? sort(not_ref(@$l)) : ()); my @r = ($r ? sort(not_ref(@$r)) : ()); my $lidx = 0; my $ridx = 0; while (($lidx < @l) && ($ridx < @r)) { my $l = $l[$lidx]; my $r = $r[$ridx]; my $cmp = $l cmp $r; if ($cmp == -1) { push @lonly, $l; $lidx++; } elsif ($cmp == 1) { push @ronly, $r; $ridx++; } else { push @both, $l; $lidx++; $ridx++; } } push @lonly, @l[$lidx..$#l]; push @ronly, @r[$ridx..$#r]; return (\@lonly, \@both, \@ronly); } sub not_ref { my @res; foreach my $i (@_) { push @res, $i if (!ref $i); } return @res; } sub list_print { my ($label, $list) = @_; if (@$list) { print ("$label:\n"); foreach my $x (@$list) { print (" $x\n"); } } } sub parse_source_files { my ($overlaydir, @dtsgroups) = @_; my $overlays = {}; my %params; foreach my $group (@dtsgroups) { chdir($group->[0]); foreach my $file (@{$group->[1]}) { foreach my $param (get_params($file.".dts")) { next if (ref $param); $params{$param} = 1; } } } chdir($overlaydir); $overlays->{''} = [ sort(keys(%params)) ]; foreach my $file (glob("*-overlay.dts")) { my $overlay = ($file =~ /^(.*)-overlay.dts$/)[0]; my $redo_cmd = `grep '// redo: ' $file`; if ($redo_cmd =~ /\/\/ redo: ([^\r\n]+)/) { system("cp $file overlaycheck.dts"); system("ovmerge -r overlaycheck.dts > $overlay-overlay.dts"); system("rm overlaycheck.dts"); if (system("git diff --quiet $file") >> 8) { print("* '$overlay' overlay changed after redo\n"); } } $overlays->{ $overlay } = [ get_params($file) ]; } return $overlays; } sub parse_readme { my ($file) = @_; my $overlays = {}; my $fh; error("Failed to open '$file'") if (!open($fh, '<', $file)); my $overlay; my $last_overlay = ''; my $params; my $has_params; my $in_params; my $blank_count; my $linenum = 0; my $descr_column = 32; while (my $line = <$fh>) { $linenum++; chomp($line); error("TABs in README ($linenum)") if ($line =~ /\t/); error("Trailing whitespace in README ($linenum)") if ($line =~ /\s$/); error("Line too long in README ($linenum)") if (length($line) > 80 && $line !~ /^\s+[^\s]+$/); if ($overlay && !$line) { $blank_count++; if ($blank_count == 2) { error("Missing params for overlay $overlay ($linenum)") if (!$params); if (ref $params) { $overlays->{ $overlay } = [ sort(@{$params}) ]; } elsif ($params) { $overlays->{ $overlay } = $params; } else { $overlays->{ $overlay } = [ ]; } $overlay = undef; $params = undef; $in_params = 0; } next; } $blank_count = 0; if (($line =~ /^\w+:\s/) && ($line !~ /^(Name|Info|Load|Params):/)) { error("Bad label ($linenum)"); } if ($line =~ /^Name:(\s*)(.*)\s*$/) { error("Bad formatting in README ($linenum)") if ($1 ne ' '); error("Missing blank lines after overlay? ($linenum)") if ($params); $overlay = $2; $params = undef; $in_params = 0; if ($overlay !~ /^$word_pattern$/ && $overlay ne '') { error("Illegal overlay name '$overlay' in README ($linenum)"); } error("Overlay '$overlay' - order violation in README ($linenum)") if (($overlay cmp $last_overlay) != 1); $last_overlay = ($overlay ne '') ? $overlay : ' '; } elsif ($line =~ /^Info:(\s*)(.*)\s*$/) { error("Bad formatting in README ($linenum)") if ($1 ne ' '); error("Info label with no Name? ($linenum)") if (!$overlay); my $info = $2; if ($2 =~ /^See ($word_pattern)(?:\s+\(.*)?$/) { $params = $1; } } elsif ($line =~ /^Load:(\s*)(.*)\s*$/) { error("Bad formatting in README ($linenum)") if ($1 ne ' '); error("Load label with no Name? ($linenum)") if (!$overlay); my $cmd = $2; $has_params = 0; if ($overlay ne '') { if ($cmd eq '') { # Ignore this overlay $ignore_missing->{$overlay} = 1; $ignore_vestigial->{$overlay} = 1; $overlay = undef; } else { if ($cmd !~ /^dtoverlay=($word_pattern)(,(?:=|\[=\])?)?$/) { error("Invalid Load example ($linenum)"); } error("Wrong overlay name in Load example ($linenum)") if ($1 ne $overlay); $has_params = 1 if ($2); } } } elsif (defined($overlay) && ($line =~ /^Params:(.*)$/)) { $blank_count = 0; my $rol = $1; $params = []; $in_params = 1; if ($rol) { if ($rol eq ' ') { error("Parameter presence mismatch ($linenum)") if ($has_params); next; } error("Bad formatting in README ($linenum)") if ($rol !~ /^ ([^ ]+)( *)/); error("Parameter presence mismatch ($linenum)") if (!$has_params); my ($param, $indent2) = ($1, length($2)); if ($param !~ /^$readme_param_pattern$/) { error("Invalid parameter name '$param' in README ($linenum)"); } my $descr_indent = 8 + length($param) + $indent2; if (($descr_indent != $descr_column) && ($indent2 != 0)) { error("Bad formatting in README ($linenum)"); } push @$params, $param; } } elsif ($in_params && ($line =~ /^( +)([^ ]+)( *)/)) { my ($indent, $param, $indent2) = (length($1), $2, length($3)); if ($indent == 8) { my $descr_indent = 8 + length($2) + $indent2; # Try to spot a comment if ((($indent2 == 1) || ($indent2 == 0 && $param =~ /:$/)) && ($descr_indent < $descr_column)) { $in_params = 0; next; } if ($param !~ /^$readme_param_pattern$/) { error("Invalid parameter name '$param' in README ($linenum)"); } if (($descr_indent != $descr_column) && ($indent2 != 0)) { error("Bad formatting in README ($linenum)"); } push @$params, $param; } else { error("Bad formatting in README ($linenum)") if (($indent < 8) || (($indent > 8) && ($indent < $descr_column))); } } } # Resolve inter-overlay ("See") references foreach my $overlay (keys(%$overlays)) { my $src = $overlays->{$overlay}; if (!ref $src) { $src = $overlays->{$src}; if (ref $src) { $overlays->{$overlay} = $src; } else { error("Chained 'See' link from $overlay to $src") if (!ref $src) } } } return $overlays; } sub parse_makefile { my ($file) = @_; my $overlays = []; my $fh; error("Failed to open '$file'") if (!open($fh, '<', $file)); push @$overlays, ''; my $last_overlay = ''; my $linenum = 0; my $in_multiline = 0; while (my $line = <$fh>) { $linenum++; chomp($line); error("Trailing whitespace in Makefile ($linenum)") if ($line =~ /\s$/); my $overlay; if ($in_multiline) { if ($line =~ /^\t(.+)\.dtbo( \\)?$/) { $overlay = $1; $in_multiline = 0 if (!$2); } else { error("Syntax error in Makefile ($linenum)"); } } elsif ($line =~ /^dtbo-\$\(RPI_DT_OVERLAYS\) \+= (.+)\.dtbo$/) { $overlay = $1; } elsif ($line =~ /^dtbo-\$\(CONFIG_ARCH_BCM2835\) \+= \\$/) { $in_multiline = 1; } if ($overlay) { error("Overlay '$overlay' - order violation in Makefile ($linenum)") if (($overlay cmp $last_overlay) != 1); push @$overlays, $overlay; $last_overlay = $overlay; } } return $overlays; } sub parse_exclusions { my ($file) = @_; my $missing = {}; my $vestigial = {}; my $fh; error("Failed to open '$file'") if (!open($fh, '<', $file)); my $overlay; my $linenum = 0; while (my $line = <$fh>) { $linenum++; chomp($line); if ($line =~ /^=\s*(.+)$/) { $overlay = $1; $missing->{$overlay} = []; $vestigial->{$overlay} = []; } elsif ($line =~ /^-\s*(.+)$/) { push @{$missing->{$overlay}}, $1; } elsif ($line =~ /^\+\s*(.+)$/) { push @{$vestigial->{$overlay}}, $1; } } return ($missing, $vestigial); } sub get_params { my ($file) = @_; my @params; my $props = {}; print ("[ get_params $file ]\n") if ($verbose); # Run the file through DTS to expand and clean up my $cpp_cmd = join(" ", @cpp_cmd); my @lines = `$cpp_cmd $file | $DTC $dtc_opts -i overlays -@ -I dts -O dts`; my $is_overlay = ($file =~ /-overlay\.dts$/); for (my $i = 0; $i < @lines; $i++) { my $line = $lines[$i]; if ($line =~ /^\s*(__overrides__ \{|target-path\s*=\s*"\/__overrides__"\s*;)\s*$/) { # Support parameters pushed into the base DTB if (0 && $1 =~ /^target-path/) { while ($lines[++$i] !~ /^\s*__overlay__\s*{\s*$/) { } } while ($lines[++$i] =~ /^\s*([^ \}]+)(?: = |;)/) { my $param = $1; my $linenum = $i + 1; error("Invalid parameter name '$param' in '$file'") if ($param !~ /^$word_pattern$/); push @params, $param; if ($lines[$i] =~ /(deadbeef|de ad be ef)/) { printf("$file: $param\n"); $fail = 1; } } } elsif ($is_overlay && $line =~ /^\s*\/\s*{\s*$/) { while ($line !~ /^\s*compatible\s*=/) { $line = $lines[++$i]; if (!$line || $line =~ /{/) { error("Missing overlay compatible string in '$file'"); last; } } my ($comp) = ($line =~ /^\s*compatible\s*=\s*(.*)?\s*;\s*$/); if ($comp) { $comp =~ s/\s+//g; if ($comp !~ /^\"brcm,(bcm2835|bcm2711|bcm2712)\"$/) { error("Invalid overlay compatible string '$comp' in '$file'"); } $props->{$1} = 1; } } elsif ($line =~ /^\s+(?:__symbols__|__fixups__|__local_fixups__)/) { last; } } #print(join("\n", @lines), "\n") if ($fail); return ($props, sort(@params)); } sub dtc_cpp { my ($infile, $outfile) = @_; my @dtc_cmd = ( $DTC, @dtc_opts, '-i', 'overlays', '-@', '-I', 'dts', '-O', 'dtb', '-o' ); my $tmpfile = $outfile.".tmp"; error("Failed to CPP '$infile'") if (system(@cpp_cmd, '-o', $tmpfile, $infile) != 0); error("Failed to compile '$infile'") if (system(@dtc_cmd, $outfile, $tmpfile) != 0); unlink($tmpfile); } sub container_checker { my ($name, $exclude, $params) = @_; my $basename = $base_files[0]; my $base = "$TMPDIR/$basename.dtb"; foreach my $param (@$params) { next if (ref $param || ($exclude && $param =~ $exclude)); my $target = `fdtget -t bx "$TMPDIR/$name.dtbo" /__overrides__ $param`; chomp($target); next if ($target !~ /^0 0 0 0 /); error("Failed to merge $name with $basename") if (system($DTMERGE, $verbose ? ('-d') : (), $base, $MERGED_DTB, "$TMPDIR/$name.dtbo", $param) != 0); my $diffs = `dtdiff $base $MERGED_DTB`; if ($diffs !~ /^\+\s*$param@/m) { error("container_checker($name): parameter '$param' doesn't enable matching node"); } } } sub error { print("* $_[0]\n"); $fail = 1; } sub fatal_error { error(@_); exit(1); } raspi-utils-20240903/overlaycheck/overlaycheck_exclusions.txt000066400000000000000000000014011466562725600244250ustar00rootroot00000000000000= - arm_freq - cache_line_size - core_freq - i2c0 - i2c0_baudrate - i2c1 - i2c1_baudrate - i2c2_baudrate - i2c2_iknowwhatimdoing - pwr_led_activelow - pwr_led_gpio - pwr_led_trigger - uart0_clkrate - cam0-led - cam0-led-ctrl - cam0-pwdn - cam0-pwdn-ctrl + i2c + i2c_arm + i2c_arm_baudrate + i2c_baudrate + i2c_vc + i2c_vc_baudrate =ads1015 - chb_cfg - chb_datarate - chb_enable - chb_gain - chc_cfg - chc_datarate - chc_enable - chc_gain - chd_cfg - chd_datarate - chd_enable - chd_gain =ads1115 - chb_cfg - chb_datarate - chb_enable - chb_gain - chc_cfg - chc_datarate - chc_enable - chc_gain - chd_cfg - chd_datarate - chd_enable - chd_gain =ov5647 - cam0-led - cam0-led-ctrl - cam0-pwdn - cam0-pwdn-ctrl =rotary-encoder - rotary0_pin_a - rotary0_pin_b raspi-utils-20240903/ovmerge/000077500000000000000000000000001466562725600157225ustar00rootroot00000000000000raspi-utils-20240903/ovmerge/CMakeLists.txt000066400000000000000000000002711466562725600204620ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.10...3.27) include(GNUInstallDirs) #set project name project(ovmerge) #add executables install(PROGRAMS ovmerge DESTINATION ${CMAKE_INSTALL_BINDIR}) raspi-utils-20240903/ovmerge/README.md000066400000000000000000000026721466562725600172100ustar00rootroot00000000000000 # ovmerge ovmerge is tool for merging DT overlay source files (`*-overlay.dts`), flattening and sorting `.dts` files for easy comparison, displaying the include tree, etc. **Build Instructions** Install the prerequisites with "sudo apt install cmake" - you need at least version 3.10 of cmake. Run the following commands here, or in the top-level directory to build and install all the utilities: - *cmake .* - *make* - *sudo make install* **Usage** ``` Usage: ovmerge where is the name of an overlay, optionally followed by a comma-separated list of parameters, each with optional '=' assignments. The presence of any parameters, or a comma followed by no parameters, removes the parameter declarations from the merged overlay to avoid a potential name clash. and are any of: -b Read files from specified git branch -c Include 'redo' comment with command line (c.f. '-r') -e Expand mode - list non-skipped lines in order of inclusion -h Display this help info -i Show include hierarchy for each file -l Like expand mode, but labels each line with source file -n No .dts file header (just parsing .dtsi files) -p Emulate Pi firmware manipulation -r Redo command comment in named files (c.f. '-c') -s Sort nodes and properties (for easy comparison) -t Trace -w Show warnings ``` raspi-utils-20240903/ovmerge/ovmerge000077500000000000000000001321301466562725600173140ustar00rootroot00000000000000#!/usr/bin/perl # Author: Phil Elwell # Copyright (c) 2018-2021, Raspberry Pi (Trading) Ltd. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions, and the following disclaimer, # without modification. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The names of the above-listed copyright holders may not be used # to endorse or promote products derived from this software without # specific prior written permission. # # ALTERNATIVELY, this software may be distributed under the terms of the # GNU General Public License ("GPL") version 2, as published by the Free # Software Foundation. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # To Do: # * Combine duplicate labels on the same node # * Consider using hashes for properties and node names # * &{/path} syntax # * 'blame' mode that prepends filename to each line - good for grep. # Fundamental types # '' - string # '#' - 64-bit # ':' - 32-bit integer # ';' - 16-bit integer # '.' - 8-bit integer (byte) # '?' - boolean # '!' - inverted boolean # '[' - byte array // Byte array syntax accepts but doesn't require colons between bytes # 'prop[' // Interpret value as byte array and assign to prop # 'prop[=00:01:02' // property = literal byte array # # Operations on a fundamental type: # // N.B. The type must go before the list so we know how to interpret it. # # 'reg:0' // set reg and unit address to value # 'reg:0=0' // set reg and unit address to the supplied literal # // The reg property is only set if it already exists. # # 'name' // Assigning to name property automatically sets the node name # // The name property is only set if it already exists. # # '=' - literal assignment (if a string, the literal after the =), if an integer, # either the in-band integer or the next cell (useful for phandles). # "prop=foo" // String literal assignment # "prop=", &spi // String path literal assignment (the path to the node is substituted, used for aliases) # "prop:0=0" // Integer literal assignment # "prop:0=", <&spi>; // Integer cell assignment # # '{...}' - use the value as the key to an element in the set. The usual type indicators apply. # 'prop{a='alpha',b='bravo',c='charlie'}"; # 'prop:0{0=",<&i2c0>,"1=",<&i2c1>,"3=0x2a}"; use strict; use integer; use warnings; use POSIX qw(strftime); my %elem_sizes = ( '"' => 0, # string '.' => 1, # byte ';' => 2, # 16-bit int ':' => 4, # 32-bit int '#' => 8, # 64-bit int ); my $branch; my $comment = 0; my $expand = 0; my $expand_label = 0; my $show_includes = 0; my $pi_extras = 0; my $redo = 0; my $sort = 0; my $trace = 0; my $warnings = 0; my $no_dts = 0; my $cur_dt; my $retcode = 0; my $query = 0; my $trace_prop = ''; my $trace_label = ''; my $indent_str = "\t"; my @redo_comments; my @cmdline; while ($ARGV[0] =~ /^-/) { my $arg = shift @ARGV; if ($arg eq '-b') { $branch = shift @ARGV; if (!defined $branch) { print STDERR ("* Branch parameter missing\n"); usage(); } push @cmdline, $arg, $branch; } elsif ($arg eq '-c') { $comment = 1; } elsif ($arg eq '-e') { $expand = 1; } elsif ($arg eq '-h') { usage(); } elsif ($arg eq '-i') { $show_includes = 1; } elsif ($arg eq '-l') { $expand = 1; $expand_label = 1; } elsif ($arg eq '-n') { $no_dts = 1; } elsif ($arg eq '-p') { $pi_extras = 1; push @cmdline, $arg; } elsif ($arg eq '-r') { my $firstline = <>; if ($firstline !~ /^\/\/ redo: ovmerge (.*)/) { print STDERR ("* Redo but input has no 'redo:' comment\n"); usage(); } my $cmdline = $1; @ARGV = split(/\s/, $cmdline); while (my $line = <>) { chomp($line); last if ($line =~ /^\/dts-v1\//); push @redo_comments, $line; } } elsif ($arg eq '-s') { $sort = 1; push @cmdline, $arg; } elsif ($arg eq '-t') { $trace = 1; } elsif ($arg eq '-w') { $warnings = 1; } elsif ($arg eq '-q') { $query = 1; } elsif ($arg eq '-S') { my $indent_spaces = shift @ARGV; if (!defined $indent_spaces) { print STDERR ("* Spaces count parameter missing\n"); usage(); } elsif ($indent_spaces !~ /^\d+$/) { print STDERR ("* Invalid spaces count parameter. Expected an integer.\n"); usage(); } $indent_str = ' ' x $indent_spaces; push @cmdline, $arg, $indent_spaces; } else { print STDERR ("* Unknown option '$arg'\n"); usage(); } } usage() if (!@ARGV); push @cmdline, @ARGV; my @overlays; foreach my $overlay (@ARGV) { $overlay =~ s/^([^,:]+)//; my $ovname = $1; my $dt = dtparse($ovname, $no_dts); next if ($show_includes || $expand); my $model = get_prop_string($dt->{'root'}, 'model'); if ($model && $model =~ /^Raspberry Pi/ && $pi_extras) { # Pi firmware adds some labels and aliases that overlays # also require. my $aliases = get_child($dt->{'root'}, 'aliases'); my $i2c = get_prop($aliases, 'i2c1')->[1]; set_prop($aliases, 'i2c', $i2c); set_prop($aliases, 'i2c_arm', $i2c); $i2c = resolve_label($dt, $i2c->[1]); add_label($dt, $i2c, 'i2c_arm'); $i2c = get_prop($aliases, 'i2c0')->[1]; set_prop($aliases, 'i2c_vc', $i2c); $i2c = resolve_label($dt, $i2c->[1]); add_label($dt, $i2c, 'i2c_vc'); my $overrides = get_child($dt->{'root'}, '__overrides__'); $i2c = get_prop($overrides, 'i2c1'); my @prop = @$i2c[1..$#$i2c]; set_prop($overrides, 'i2c', @prop); set_prop($overrides, 'i2c_arm', @prop); $i2c = get_prop($overrides, 'i2c0'); @prop = @$i2c[1..$#$i2c]; set_prop($overrides, 'i2c_vc', @prop); } while ($overlay =~ /\G[,:]([^=,]+)(?:=([^,]+))?/g) { dtparam($dt, $1, defined($2) ? $2 : ''); } my $exports = get_node($dt, '/__exports__'); $cur_dt = $dt; if ($exports) { foreach my $symbol (@{$exports->[1]}) { # Only the name is required my $expname = $symbol->[0]; # Increment the reference count of the named label adj_ref(1, $expname); } } if ($overlay =~ /^,/) { delete_node(get_node($dt, '/__overrides__')); foreach my $fragment (get_fragments($dt)) { delete_node($fragment) if (get_child($fragment, '__dormant__')); } } $cur_dt = undef; ovstrip($dt) if ($dt->{'plugin'}); push @overlays, $dt; } exit(0) if (!@overlays); if ($overlays[0]->{'plugin'}) { # Count and renumber the fragments in the base renumber_fragments($overlays[0], 0); for (my $i = 1; $i < @overlays; $i++) { ovmerge($overlays[0], $overlays[$i]); } } else { my $base = $overlays[0]; if (@overlays > 1) { # A real Pi base tree will have a __symbols__ node # Some overlays rely on one being present, so ensure one is my $symbols = get_child($base->{'root'}, '__symbols__'); $symbols = add_node($base->{'root'}, '__symbols__') if (!$symbols); # Count and renumber the fragments in the first overlay renumber_fragments($overlays[1], 0); for (my $i = 2; $i < @overlays; $i++) { ovmerge($overlays[1], $overlays[$i]); } ovapply($base, $overlays[1]); delete_node($symbols) if (is_node_empty($symbols)); } } if ($comment) { print('// redo: ovmerge -c'); foreach my $opt (@cmdline) { if ($opt =~ /\s/) { print(" '$opt'"); } else { print(" $opt"); } } print("\n"); if (@redo_comments) { print(join("\n", @redo_comments)); } print("\n"); } dtdump($overlays[0]) if (!$query); exit($retcode); sub dtparse { # DT = hash of: # 'root' => '/' node # 'plugin' => boolean true if /plugin/ tag is present. # 'labels' => hash of labels used in tree # 'refcount' => hash of reference count of labels in tree (excluding the definition) # 'includes' => array of included headers (array to preserve order) # 'memreserves' => array of memreseve [base,length] pairs # 'defines' => array of memreseve [base,length] pairs my ($filename, $got_header) = @_; my $defines = {}; my $state = [ read_tokens($filename, 0, $defines), 0, undef ]; my $labels = {}; my $dt = { 'labels'=>$labels, 'includes'=>[], 'memreserves'=>[], 'refcount'=>{}, 'defines'=>$defines }; my $next = get_head($state); while ($next =~ /^(\/.+\/|#include)$/) { my $type = $next; $next = match($state, $next); if ($type eq '#include') { set_add($dt->{'includes'}, $next); $next = match($state, $next); } else { if (!$got_header) { die "* File missing /dts-v1/ tag\n" if ($type ne '/dts-v1/'); $got_header = 1; } elsif ($type eq '/dts-v1/') { print("* Ignoring duplicate /dts-v1/ tag\n") if ($warnings); } elsif ($type eq '/plugin/') { $dt->{'plugin'} = 1; } elsif ($type eq '/memreserve/') { my $start = get_int($state); my $length = get_int($state); set_add($dt->{'memreserves'}, [ $start, $length ]); } else { die "* Unexpected token '$type'\n"; } $next = match($state, ';'); } } $cur_dt = $dt; while (defined $next) { if ($next eq '/') { match($state, '/'); $next = parse_node($state, undef, 0, '/'); } else { my @newlabels; while ($next =~ /^(\w+):$/) { push @newlabels, $1; print("[Label: $1]\n") if ($trace); $next = match($state, $next); } if ($next =~ /^&(\w+)$/) { my $subnode = $labels->{$1}; match($state, $next); if (defined $subnode) { $next = parse_node($state, $subnode->[4], $subnode->[5], $subnode, @newlabels); } else { print STDERR ("* Unknown label '$1'\n"); $next = parse_node($state, undef, 0, '/', @newlabels); } } elsif ($next eq '/delete-node/') { $next = match($state, $next); if ($next =~ /^&(\w+)$/) { my $label = $1; my $subnode = $labels->{$label}; match($state, $next); if (defined $subnode) { delete_node($subnode); } else { print STDERR ("* Unknown label '$1'\n"); } $next = match($state, ';'); } } elsif ($next eq '#include') { $next = match($state, $next); set_add($dt->{'includes'}, $next); $next = match($state, $next); } else { die "* Unexpected token '$next'\n"; } } } $cur_dt = undef; if ($state->[1] != @{$state->[0]}) { # For now printf("* Junk at the end - %s ...\n", get_head($state)); } # Check reference counts while (my ($key, $value) = each (%{$dt->{'refcount'}})) { if ($value < 0) { die "* Internal error - negative refcount on '$key'\n"; } elsif ($value > 0 && !defined $dt->{'labels'}->{$key} && !$dt->{'plugin'}) { print STDERR ("* symbol '$key' is undefined in '$filename'\n"); $retcode = 1; } } # Check the fragment order ordercheck($dt, {}) if ($dt->{'plugin'}); # Check the overrides my $overrides = get_node($dt, '/__overrides__'); foreach my $param (@{$overrides->[1]}) { dtparam($dt, $param->[0], undef); } return $dt; } sub dtdump { my ($dt) = @_; print("/dts-v1/;\n"); print("/plugin/;\n") if ($dt->{'plugin'}); print("\n"); if (!set_empty($dt->{'includes'})) { foreach my $inc (set_vals($dt->{'includes'})) { print("#include $inc\n"); } print("\n"); } if (!set_empty($dt->{'memreserves'})) { foreach my $res (set_vals($dt->{'memreserves'})) { print('/memreserve/ ', $res->[0], ' ', $res->[1], ";\n"); } print("\n"); } dump_node($dt->{'root'}, 0); } sub get_vector { my ($p, $size, $len) = @_; if (($p->[0] eq '<' || $p->[0] eq '[') && (($p->[1] == $size) || ($p->[1] == 4 && $size == 8)) && (!defined $len || $len == @{$p->[2]})) { return $p->[2]; } else { return undef; } } sub get_label_ref { my ($p) = @_; my $vector = get_vector($p, 4, 1); if ($vector && ($vector->[0] =~ /^(?:&.*|0)$/)) { return $vector->[0]; } else { return undef; } } sub parse_lookup_table { my ($table, $ovr, $ppos, $value) = @_; my $val; # "prop{a=alpha,b='bravo !',c,=default}"; # Non-match -> 'default' # "prop:0{0=",<&i2c0>,"1=",<&i2c1>,"3=0x2a,}"; # Non-match copies # "not:0{0=1,1=0}"; # Non-match -> error while ($table =~ /(?:'([^']*)'|([^=,\}]*))(?:=(?:'([^']*)'|([^,\}]*)))?([,\}])?/cg) { my ($key, $sub, $sep) = (defined($1) ? $1 : $2, defined($3) ? $3 : $4, $5); if (!defined $sep) { my $p = $ovr->[$$ppos++]; $sub = get_vector($p, 4, 1)->[0]; $table = $ovr->[$$ppos++]; if (!defined $table) { $sep = '}'; } else { die "* Expected string in lookup table\n" if ($table->[0] ne '"'); $table = $table->[1]; $sep = $1 if ($table =~ /^(\})/); } } if (defined $value) { if ($key eq '') { if (!defined $val) { $val = (defined $sub) ? $sub : $value; } } elsif ($key eq $value) { $val = (defined $sub) ? $sub : $key; } } last if (($sep || '') eq '}'); } die "* No match for '$value'\n" if (defined $value && !defined $val); return $val; } sub dtparam { my ($dt, $param, $value) = @_; my $overrides = get_node($dt, '/__overrides__'); die "* No overrides found\n" if (!$overrides); my $ovr = get_prop($overrides, $param); die "* dtparam '$param' not found\n" if (!$ovr); for (my $pos = 1; $pos < @$ovr;) { my $p = $ovr->[$pos++]; my $label = get_label_ref($p); die "* Invalid override 1: $param\n" if (!defined $label); $p = $ovr->[$pos++]; die "* Invalid override 2: $param\n" if ($p->[0] ne '"'); my $decl = $p->[1]; if ($label =~ /^&(.*)/) { my $node = resolve_label($dt, $1); die "* Override '$param' targets unknown label '$1'\n" if (!$node); if ($decl =~ /^([-a-zA-Z0-9_,]+)([.;:#])(\d+)(?:(=|\{)(.*))?$/) { # Integer parameter my ($prop, $type, $offset, $op, $opdata) = ($1, $2, $3, $4 || '', $5); my $size = $elem_sizes{$type}; my $val = $value; if ($op eq '=') { if ($opdata ne '') { $val = $opdata; } else { my $vector = get_vector($ovr->[$pos++], 4, 1); die "* Expected cell value in parameter '$param'\n" if (!defined $vector); $val = $vector->[0]; } } elsif ($op eq '{') { $val = parse_lookup_table($opdata, $ovr, \$pos, $value); } my $intval = integer_value($val, $size); if (defined $value && $prop eq 'reg') { my $regval = sprintf("%x", $intval); $node->[0] =~ s/@[0-9a-fA-F]*$/\@$regval/; } # Locate the offset within the property my ($chunk, $chunk_idx) = find_prop_chunk($node, $prop, $offset, $size, $param, defined($value) && $prop ne 'reg'); if ($chunk) { # Check the override type matches the property type my $vector = get_vector($chunk, $size); die "* Probably incorrect override property type for '$prop'\n" if (!$vector); if (defined $value) { # Apply the override for (my $i = @$vector; $i < $chunk_idx; $i++) { $vector->[$i] = 0; } $vector->[$chunk_idx] = $intval; } } } elsif ($decl =~ /^([-a-zA-Z0-9_,]+)([\?\!])(?:(=|\{)(.*))?$/) { # boolean my ($prop, $sense, $op, $opdata) = ($1, $2, $3 || '', $4); my $val = $value; if ($op eq '=') { if ($opdata ne '') { $val = $opdata; } else { my $vector = get_vector($ovr->[$pos++], 4, 1); die "* Expected cell value in parameter '$param'\n" if (!defined $vector); $val = $vector->[0]; } } elsif ($op eq '{') { $val = parse_lookup_table($opdata, $ovr, \$pos, $value); } if (defined $value) { my $bool = boolean_value($val); $bool = !$bool if ($sense eq '!'); if ($bool) { set_prop($node, $prop); } else { delete_prop($node, $prop); } } } elsif ($decl =~ /^([-a-zA-Z0-9_,]+)\[(?:(=|\{)(.*))?$/) { # byte array my ($prop, $op, $opdata) = ($1, $2 || '', $3); my $val = $value; if ($op eq '=') { $val = $opdata; } elsif ($op eq '{') { $val = parse_lookup_table($opdata, $ovr, \$pos, $value); } if (defined $value) { apply_prop($node, $prop, ['[', 1, byte_array_value($val)]); } } elsif ($decl =~ /^([-a-zA-Z0-9_,]+)(?:(=|\{)(.*))?$/) { # string my ($prop, $op, $opdata) = ($1, $2 || '', $3); my $val = $value; if ($op eq '=') { if ($opdata ne '') { $val = $opdata; } elsif ($pos == @$ovr) { $val = ''; } else { $val = $ovr->[$pos++]; die "* Expected a string or label reference in parameter '$param'\n" if (($val->[0] ne '"') && ($val->[0] ne '&')); } } elsif ($op eq '{') { $val = parse_lookup_table($opdata, $ovr, \$pos, $value); } if (defined $value) { if ($prop eq 'name') { $node->[0] = $val; } else { apply_prop($node, $prop, ref($val) ? $val : [ '"', $val ]); } } } else { die "* Invalid parameter declaration '$decl'\n"; } } else { while ($decl =~ /\G([=!+-])(\d+)/g) { my ($op, $num) = ($1, $2); my $frag = get_node($dt, '/fragment-'.$num) || get_node($dt, '/fragment@'.$num); die "* Param $param: no fragment $num\n" if (!$frag); if (defined $value) { # Enable or disable the fragment as needed my $bool = boolean_value($value); if ($op eq '!') { $bool = !$bool; } elsif ($op eq '+') { $bool = 1; } elsif ($op eq '-') { $bool = 0; } $frag->[2]->[0]->[0] = ($bool ? '__overlay__' : '__dormant__'); } } die "* Invalid override 3:$param\n" if (defined(pos($decl))); } } } # Combine two (possibly partially overridden) overlays sub ovmerge { my ($base, $ov) = @_; die "* Cannot merge a non-overlay\n" if (!$base->{'plugin'} || !$ov->{'plugin'}); # Combine the list of includes, removing any duplicates foreach my $inc (set_vals($ov->{'includes'})) { set_add($base->{'includes'}, $inc); } # Count and renumber the fragments in the overlay renumber_fragments($ov, $base->{'frag_count'}); # Uniquify and merge the overlay labels my %transform; my $base_labels = $base->{'labels'}; my $ov_labels = $ov->{'labels'}; foreach my $l (keys(%$ov_labels)) { my $nl = $l; my $n = $ov_labels->{$l}; if ($base_labels->{$l}) { my $i; for ($i = 1; ; $i++) { $nl = "${l}_$i"; last if (!$base_labels->{$nl}); } $transform{$l} = $nl; # Don't use get_labels here because it returns a copy # and we need to modify the original foreach my $ol (@{$n->[3]}) { $ol = $nl if ($ol eq $l); } } $base_labels->{$nl} = $n; } relabel_node($ov->{'root'}, \%transform, 0); my $base_overrides = get_node($base, '/__overrides__'); my $ov_overrides = get_node($ov, '/__overrides__'); remove_node($base_overrides) if ($base_overrides); # Merge the fragments foreach my $child (get_fragments($ov)) { add_node($base->{'root'}, $child); $base->{'frag_count'}++; } # Merge the overrides if ($ov_overrides) { $base_overrides ||= new_node('__overrides__'); foreach my $ovr (@{$ov_overrides->[1]}) { die "* Duplicate parameter '$ovr->[0]'\n" if (get_prop($base_overrides, $ovr->[0])); set_prop($base_overrides, @$ovr); } } add_node($base->{'root'}, $base_overrides) if ($base_overrides); } # Remove the dormant fragments and unused labels from an overlay sub ovstrip { my ($dt) = @_; my @unused; $cur_dt = $dt; while (my ($key, $value) = each (%{$dt->{'labels'}})) { push @unused, $key if (!$dt->{'refcount'}->{$key}); } foreach my $label (@unused) { my $node = $dt->{'labels'}->{$label}; map_del($dt->{'labels'}, $label); my $node_labels = $node->[3]; for (my $i = 0; $i < @$node_labels; $i++) { if ($node_labels->[$i] eq $label) { splice(@$node_labels, $i, 1); last; } } } $cur_dt = undef; } # Apply an overlay to a base tree sub ovapply { my ($base, $ov) = @_; die "* Cannot apply a non-overlay\n" if (!$ov->{'plugin'}); die "* Cannot apply an overlay to an overlay\n" if ($base->{'plugin'}); # Combine the list of includes, removing any duplicates foreach my $inc (set_vals($ov->{'includes'})) { set_add($base->{'includes'}, $inc); } my $base_overrides = get_node($base, '/__overrides__'); # Apply fragments in two passes # Pass 1: Apply fragments that target other fragments within the overlay foreach my $fragment (get_fragments($ov)) { my $overlay = get_child($fragment, '__overlay__'); next if (!$overlay); my $target_node; my $target = get_prop($fragment, 'target'); if ($target) { my $label = get_label_ref($target->[1]); die "* Invalid target reference\n" if ($label !~ /^&(.*)/); $target_node = $ov->{'labels'}->{$1}; if ($target_node) { # Merge properties and subnodes apply_node($ov, $target_node, $overlay); $overlay->[0] = '__dormant__'; } } } # Pass 2: Apply fragments that target the base dtb foreach my $fragment (get_fragments($ov)) { my $overlay = get_child($fragment, '__overlay__'); next if (!$overlay); my $target_node; my $target = get_prop($fragment, 'target'); if ($target) { my $label = get_label_ref($target->[1]); die "* Invalid target reference\n" if ($label !~ /^&(.*)/); $target_node = $base->{'labels'}->{$1}; die "* Label '$1' not found in base\n" if (!$target_node); } else { $target = get_prop($fragment, 'target-path'); die "* Invalid target-path\n" if ($target->[1]->[0] ne '"'); $target_node = get_node($base, $target->[1]->[1]); die "* Path '$target->[1]->[1]' not found in base\n" if (!$target_node); } # Merge properties and subnodes apply_node($base, $target_node, $overlay); } } sub ordercheck { my ($ov, $applied) = @_; # Try to apply fragments that target other fragments within the overlay foreach my $fragment (get_fragments($ov)) { my $overlay = get_child($fragment, '__overlay__') || get_child($fragment, '__dormant__'); next if (!$overlay); my $target_node; my $target = get_prop($fragment, 'target'); if ($target) { my $label = get_label_ref($target->[1]); die "* Invalid target reference\n" if ($label !~ /^&(.*)/); $target_node = $ov->{'labels'}->{$1}; if ($target_node) { my $target_fragment = fragment_of($target_node); die "* $fragment->[0] should precede " . ($target_fragment ? $target_fragment->[0] : "fragment@?") . "\n" if ($applied->{$target_node}); set_applied($overlay, $applied); } } } } sub set_applied { my ($node, $applied) = @_; $applied->{$node} = 1; foreach my $subnode (get_children($node)) { set_applied($subnode, $applied); } } sub parse_node { my ($state, $parent, $depth, $node, @newlabels) = @_; # scalar name # array properties # array children # array labels # ref parent # scalar depth my $next = match($state, '{'); $node = (get_child($parent, $node) || add_node($parent, $node)) if (!ref $node); printf("parse_node(%s, %d ...) - %s\n", $node->[0], $depth) if ($trace); # Parse the properties first # Properties are "name=value;" while ($next ne '}') { my @childlabels; if ($next eq '/delete-node/') { $next = match($state, $next); if ($next =~ /^[-a-zA-Z0-9,._+#@]+$/) { delete_node(get_child($node, $next)); match($state, $next); $next = match($state, ';'); } next; } elsif ($next eq '/delete-property/') { $next = match($state, $next); if ($next =~ /^[-a-zA-Z0-9,._+#@]+$/) { delete_prop($node, $next); match($state, $next); $next = match($state, ';'); } next; } while ($next =~ /^(\w+):$/) { push @childlabels, $1; print("[Label: $1]\n") if ($trace); $next = match($state, $next); } if ($next =~ /^[-a-zA-Z0-9,._+#@]+$/) { my $name = $next; print("* Leading zero in node name '$name'\n") if ($name =~ /\@0[0-9a-fA-F]/); $next = match($state, $next); if ($next eq '{') { $next = parse_node($state, $node, $depth + 1, $name, @childlabels); } elsif ($next eq '=') { my @prop; print("* Ignoring label on property '$name'\n") if (@childlabels && $warnings); do { $next = match($state, $next); if ($next =~ /^"(.*)"$/) { # string push @prop, [ '"', $1 ]; $next = match($state, $next); } elsif ($next =~ /^&(.*)/) { # noderef string push @prop, [ '&', $1 ]; $next = match($state, $next); } elsif (($next eq '<') || ($next eq '/bits/')) { my $elemsize = 4; if ($next eq '/bits/') { $next = match($state, $next); if (($next != 8) && ($next != 16) && ($next != 32) && ($next != 64)) { die "* Invalid /bits/ value '$next'.\n"; } $elemsize = $next/8; match($state, $next); } $next = match($state, '<'); # vector my $vals = []; while ($next ne '>') { push @$vals, $next; $next = match($state, $next); } push @prop, [ '<', $elemsize, $vals ]; $next = match($state, '>'); } else { # bytestring my $vals = []; $next = match($state, '['); while ($next ne ']') { push @$vals, @{byte_array_value($next)}; $next = match($state, $next); } $next = match($state, ']'); push @prop, [ '[', 1, $vals ]; } } while ($next eq ','); $next = match($state, ';'); set_prop($node, $name, @prop); } else { print("* Ignoring label on property '$name'\n") if (@childlabels && $warnings); $next = match($state, ';'); set_prop($node, $name); } } else { die "* Unexpected token '$next'\n"; } } my $labels = $cur_dt->{'labels'}; foreach my $newlabel (@newlabels) { my $labelled_node = map_find($labels, $newlabel); if ($labelled_node) { if ($labelled_node != $node) { print STDERR ("* Duplicated label '$newlabel' - '", $labelled_node->[0], "' and '", $node->[0], "'\n"); } else { print("* Replicated label '$newlabel' (on the same node)\n") if ($warnings); } } add_label($cur_dt, $node, $newlabel); } match($state, '}'); return match($state, ';'); } sub add_label { my ($dt, $node, $label) = @_; my $old_value = map_find($dt->{'labels'}, $label); if (defined $old_value) { return if ($old_value == $node); die "* Label '$label' redefined\n"; } map_add($dt->{'labels'}, $label, $node); push @{$node->[3]}, $label; print("* Multiple labels on '" . node_path($node) . "'\n") if ($warnings && @{$node->[3]} > 1); } sub resolve_label { my ($dt, $label) = @_; return $dt->{'labels'}->{$label}; } sub resolve_alias { my ($dt, $alias) = @_; my $aliases = get_node($dt, '/aliases'); $alias = get_prop($aliases, $alias); return undef if (!$alias); if ($alias->[1][0] eq '&') { return resolve_label($dt, $alias->[1][1]); } else { return get_node($dt, $alias->[1][1]); } } sub fragment_of { my ($node) = @_; return undef if (!$node); return $node if ($node->[0] =~ /^fragment@/); return fragment_of($node->[4]); } sub dump_node { my ($node, $depth) = @_; my $indent = $indent_str x $depth; print($indent, join(': ', get_labels($node), $node->[0]), " {\n"); # Properties foreach my $prop (get_props($node)) { my @terms; print($indent, $indent_str, $prop->[0]); for (my $i = 1; $i < @$prop; $i++) { my $chunk = $prop->[$i]; if ($chunk->[0] eq '"') { push @terms, '"'.$chunk->[1].'"'; } elsif ($chunk->[0] eq '&') { push @terms, '&'.$chunk->[1]; } elsif ($chunk->[0] =~ '<') { push @terms, (($chunk->[1] != 4) ? sprintf("/bits/ %d ", $chunk->[1] * 8) : '') . '<'.join(' ', @{$chunk->[2]}).'>'; } elsif ($chunk->[0] eq '[') { push @terms, '['.byte_array_string($chunk->[2]).']'; } else { push @terms, '?'; } } print(' = ', join(', ', @terms)) if (@terms); print(";\n"); } # Sub-nodes foreach my $subnode (get_children($node)) { dump_node($subnode, $depth + 1); } print($indent, "};\n"); } sub read_tokens { my ($filename, $depth, $defines) = @_; my $linenum = 0; my $fh; my $tokens = [ ['/file/', $filename] ]; my $in_comment = 0; my $if_count = 0; my $hidden_count = 0; my $expr; my $expr_level = 0; my $filepath = $filename; $filepath =~ s/\/?[^\/]*$//; $filepath .= '/' if ($filepath); print(" " x $depth, $filename, "\n") if ($show_includes); print("[read_tokens '$filename']\n") if ($trace); print("#### Start of '$filename'\n") if ($expand && !$expand_label); die "* Failed to open '$filename'\n" if ($branch ? !open($fh, '-|', "git show $branch:./$filename") : !open($fh, '<', $filename)); while (my $line = <$fh>) { $linenum++; if ($in_comment) { next if ($line !~ s/^.*?\*\///); $in_comment = 0; } if ($line =~ /^\s*#if(def|ndef)?\s+(\w+)/) { my $mode = $1; my $defined = defined($defines->{$2}); $if_count++; $hidden_count++ if ($hidden_count || !$mode || ($mode eq 'def' && !$defined) || ($mode eq 'ndef' && $defined)); next; } elsif ($line =~ /^\s*#else/) { if ($hidden_count == 0) { $hidden_count = 1; } elsif ($hidden_count == 1) { $hidden_count = 0; } next; } elsif ($line =~ /^\s*#endif/) { die "* Unmatched #endif ($filename:$linenum)\n" if (!$if_count); $if_count--; $hidden_count-- if ($hidden_count); next; } next if ($hidden_count); if ($line =~ /^\s*(?:#include|\/include\/)\s+(["<][^">]+[">])\s*$/) { my $incfile = $1; if ($incfile =~ /\.h.$/) { push @$tokens, '#include', $incfile; } elsif ($incfile =~ /\.dtsi?.$/) { my $dtsfile = search_path($filepath.substr($incfile, 1, -1)); die "* Failed to find include file '$incfile'" if (!$dtsfile); my $inc_tokens = read_tokens($dtsfile, $depth + 1, $defines); push @$tokens, @$inc_tokens; push @$tokens, ['/file/', $filename]; print("#### Continue '$filename'\n") if ($expand && !$expand_label); } else { die "* Invalid include file '$incfile'\n"; } next; } elsif ($line =~ /^\s*#define\s+(\w+)(?:\s+([^\r\n]+))?/) { my $symbol = $1; my $val = $2 || ''; $val =~ s/\/\/.*//; $val =~ s/\s+$//; $defines->{$symbol} = $val; next; } elsif ($line =~ /^\s*#undef\s+(\w+)/) { delete $defines->{$1}; next; } elsif ($line =~ /^\s*#bkpt/) { $DB::single = 1; next; } print("$filename:$linenum: ") if ($expand_label); print($line) if ($expand); # Split the line into tokens $line =~ /^\s*/g; if ($expr_level) { while ($expr_level && $line =~ /\G(.*?)([\(\)])(\s*)/cg) { $expr_level += ($2 eq '(') ? 1 : -1; $expr .= $1 . $2; $expr .= $3 if ($expr_level); } if ($expr_level) { $expr .= $1 if ($line =~ /\G(.*?)\s*$/cg); next; } push @$tokens, $expr; } while ($line =~ /\G((?:\/(?:dts-v1|plugin|memreserve|bits|delete-node|delete-property)\/)|&[a-zA-Z_][a-zA-Z0-9_]*|[a-zA-Z_][a-zA-Z0-9_]*:|[-a-zA-Z0-9,._+#@]+|\(|"(?:[^\\"]|\\.)*"|'(?:[^']|\\.)*'|\/\/|\/\*|[\/{};=<>,\[\]])\s*/cg) { my $tok = $1; if ($tok eq '//') { $line = ''; last; } elsif ($tok eq '/*') { if ($line !~ /\G.*?\*\/\s*/cg) { $in_comment = 1; $line = ''; last; } next; } elsif ($tok eq '(') { $expr_level = 1; $expr = '('; while ($expr_level && $line =~ /\G(.*?)([\(\)])(\s*)/cg) { $expr_level += ($2 eq '(') ? 1 : -1; $expr .= $1 . $2; $expr .= $3 if ($expr_level); } if ($expr_level) { $expr .= $1 if ($line =~ /\G(.*?)\s*$/cg); last; } $tok = $expr; } if ($tok =~ /\b(\w+)\b/) { my $sym = $1; my $newsym = $defines->{$sym}; if (defined($newsym)) { printf("['%s' -> '%s']\n", $sym, $newsym) if ($trace); $tok =~ s/\b$sym\b/$newsym/; } } push @$tokens, $tok; } if ($line !~ /\G[\r\n]*$/cg) { $line = substr($line, pos($line)); die "* Bad token at '$line'\n"; } } close($fh); print("#### End of '$filename'\n") if ($expand && !$expand_label); return $tokens; } sub match { my ($state, $match) = @_; my $next = get_head($state); print("[match '$match' @ $state->[1]]\n") if ($trace); die "* Unexpected token '$next' - expected '$match'\n" if ($next ne $match); return get_next($state); } sub get_next { my ($state) = @_; $state->[1]++; return get_head($state); } sub get_head { my ($state) = @_; my $head = ${$state->[0]}[$state->[1]]; while (ref $head) { if ($head->[0] eq '/file/') { my $file = $head->[1]; $state->[2] = $file; print("[file $file]\n") if ($trace); } else { die "* Unknown metadata '$head->[0]'\n"; } $head = get_next($state); } return $head; } sub remove_node { my ($node) = @_; my $parent = $node->[4]; print("[remove_node($node->[0]\n") if ($trace); return if (!$parent); $node->[4] = undef; # Find the node in the parent my $found; for (my $i = 0; $i < @{$parent->[2]}; $i++) { if ($parent->[2]->[$i] == $node) { $found = $i; last; } } die "* Internal error - wrong parent/missing child\n" if (!defined $found); # Remove from the parent splice(@{$parent->[2]}, $found, 1); } sub delete_node { my ($node) = @_; my $found; return if (!$node); remove_node($node); # Delete all labels attached to the node foreach my $label (get_labels($node)) { map_del($cur_dt->{'labels'}, $label); } print(" [Deleted labels]\n") if ($trace); # Delete all label references from the properties foreach my $prop (get_props($node)) { adj_val_refs(-1, @$prop[1..$#$prop]); } # Delete all subnodes while (@{$node->[2]}) { delete_node($node->[2]->[0]); } print(" [Deleted subnodes]\n") if ($trace); return 1; } sub relabel_node { my ($node, $transform, $depth) = @_; # Properties foreach my $prop (get_props($node)) { if ($depth > 0) { for (my $i = 1; $i < @$prop; $i++) { my $chunk = $prop->[$i]; if ($chunk->[0] eq '<') { foreach my $term (@{$chunk->[2]}) { if ($term =~ /^&(.*)/) { my $newlabel = $transform->{$1}; if ($newlabel) { adj_ref(-1, $1); adj_ref(1, $newlabel); $term = '&'.$newlabel; } } } } elsif ($chunk->[0] eq '&') { my $newlabel = $transform->{$chunk->[1]}; if ($newlabel) { adj_ref(-1, $1); adj_ref(1, $newlabel); $chunk->[1] = $newlabel; } } } } } # Sub-nodes foreach my $subnode (get_children($node)) { relabel_node($subnode, $transform, $depth + 1); } } sub apply_node { my ($base, $dst, $src) = @_; # Properties foreach my $prop (get_props($src)) { apply_prop($dst, @$prop); } # Labels foreach my $label (get_labels($src)) { add_label($base, $dst, $label); } # Sub-nodes foreach my $subsrc (get_children($src)) { my $subdst = get_child($dst, $subsrc->[0]); if ($subdst) { die "* Subnode $subsrc->[0] already exists\n"; } else { $subdst = add_node($dst, $subsrc->[0]); } apply_node($base, $subdst, $subsrc); } } sub search_path { my ($fname) = @_; return $fname if ($branch && system("git cat-file -e $branch:./$fname") == 0); return $fname if (-r $fname); return undef; } sub new_node { my ($name) = @_; return [ $name, [], [], [] ]; } sub add_node { my ($parent, $name) = @_; my $node = (ref $name) ? $name : new_node($name); $node->[4] = $parent; if ($parent) { $node->[5] = $parent->[5] + 1; push @{$parent->[2]}, $node; } else { die "* Invalid root node '$name'\n" if ($name ne '/'); $node->[5] = 0; $cur_dt->{'root'} = $node; } return $node; } sub get_node { my ($dt, $path) = @_; my $node = $dt->{'root'}; if ($path =~ s/^([^\/]+)(\/|$)/\//) { $node = resolve_alias($dt, $1); } return $node if ($path eq '/'); while ($node && $path =~ /\G\/([-a-zA-Z0-9,._+#@]+)/g) { my $name = $1; $node = get_child($node, $name); } return $node; } sub is_node_empty { my ($node) = @_; return !get_children($node) && !get_props($node); } sub get_child { my ($node, $name) = @_; if ($node) { foreach my $child (@{$node->[2]}) { return $child if (($child->[0] eq $name) || ($name !~ /@/ && $child->[0] =~ /^$name@/)); } } else { return $cur_dt->{'root'} if ($name eq '/'); } return undef; } sub by_addr { my $a_addr = ($a->[0] =~ /@(.*)$/) ? hex($1) : undef; my $b_addr = ($b->[0] =~ /@(.*)$/) ? hex($1) : undef; return $a_addr <=> $b_addr if (defined $a_addr && defined $b_addr); return -1 if (defined $a_addr); return 1 if (defined $b_addr); return $a->[0] cmp $b->[0]; } sub get_children { my ($node) = @_; return sort by_addr (@{$node->[2]}) if ($sort); return (@{$node->[2]}); } sub get_fragments { my ($ov) = @_; my @fragments; foreach my $child (get_children($ov->{'root'})) { push @fragments, $child if ($child->[0] =~ /^fragment[@-](\d+)$/); } return @fragments; } sub renumber_fragments { my ($ov, $offset) = @_; my @fragments; my @remap; my $count = 0; my $overrides; foreach my $child (get_children($ov->{'root'})) { if ($child->[0] =~ /^fragment([@-])(\d+)$/) { my ($sep,$num) = ($1,$2); $remap[$num] = $count + $offset; $child->[0] = sprintf('fragment%s%d', $sep, $count + $offset); push @fragments, $child; $count++; } elsif ($child->[0] eq '__overrides__') { $overrides = $child; } } $ov->{'frag_count'} = $count; return if (!$overrides); foreach my $ovr (@{$overrides->[1]}) { for (my $pos = 1; $pos < @$ovr; $pos++) { if ((get_label_ref($ovr->[$pos]) || '') eq '0') { $pos++; while ($ovr->[$pos]->[1] =~ /\G[=!+-](\d+)/g) { die ("* override '$ovr->[0]}' references missing fragment $1\n") if (!defined $remap[$1]); } $ovr->[$pos]->[1] =~ s/\G([=!+-])(\d+)/$1.$remap[$2]/eg; } } } } sub node_path { my ($node) = @_; return '/' if ($node->[0] eq '/'); my $parent_path = node_path($node->[4]); $parent_path = '' if ($parent_path eq "/"); return $parent_path.'/'.$node->[0]; } sub get_prop_string { my ($node, $name) = @_; my $prop = get_prop($node, $name); return undef if (!$prop); return undef if (@$prop != 2); return undef if ($prop->[1]->[0] ne '"'); return $prop->[1]->[1]; } sub get_prop { my ($node, $name) = @_; foreach my $prop (@{$node->[1]}) { return $prop if ($prop->[0] eq $name); } return undef; } sub get_props { my ($node) = @_; return sort { $a->[0] cmp $b->[0] } (@{$node->[1]}) if ($sort); return (@{$node->[1]}); } sub add_prop { my ($node, $name, @vals) = @_; my $new = [ $name, @vals ]; push @{$node->[1]}, $new; return $new; } sub set_prop { my ($node, $name, @vals) = @_; $DB::single = 1 if ($name eq $trace_prop); adj_val_refs(1, @vals); foreach my $prop (@{$node->[1]}) { if ($prop->[0] eq $name) { adj_val_refs(-1, @$prop[1..$#$prop]); splice(@$prop, 1, @$prop - 1, @vals); return $prop; } } return add_prop($node, $name, @vals); } sub apply_prop { my ($node, $name, @vals) = @_; if ($name eq 'status') { @vals = (['"', boolean_value($vals[0][1]) ? 'okay' : 'disabled']); } elsif ($name eq 'bootargs') { # Concatenate bootargs @vals = (['"', get_prop($node, $name)->[1][1] . ' ' . $vals[0][1]]); } return set_prop($node, $name, @vals); } sub delete_prop { my ($node, $name) = @_; $DB::single = 1 if ($name eq $trace_prop); for (my $i = 0; $i < @{$node->[1]}; $i++) { my $prop = $node->[1]->[$i]; if ($prop->[0] eq $name) { adj_val_refs(-1, @$prop[1..$#$prop]); return splice(@{$node->[1]}, $i, 1); } } return undef; } sub find_prop_chunk { my ($node, $propname, $offset, $size, $ovrname, $create) = @_; my $chunk; my $prop = get_prop($node, $propname); if (!$prop && $create) { $prop = set_prop($node, $propname, [ '<', $size, [] ]); } return (undef, 0) if (!$prop); my $pos = 0; for (my $i = 1; $i < @$prop; $i++) { $chunk = $prop->[$i]; my $type = $chunk->[0]; my $end; if ($type eq '"') { $end = $pos + length($chunk->[1]) + 1; } elsif ($type eq '[') { $end = $pos + @{$chunk->[2]}; } else { $end = $pos + $chunk->[1] * @{$chunk->[2]}; } last if ($offset < $end); $pos = $end; } if (!$chunk && $create) { $chunk = [ '<', $size, [] ]; push @$prop, $chunk; } $offset -= $pos; die "* Unaligned override '$ovrname', property $prop\n" if ($offset % $size); return ($chunk, $offset / $size); } sub get_labels { my ($node) = @_; return sort { $a cmp $b } (@{$node->[3]}) if ($sort); return (@{$node->[3]}); } sub integer_value { my ($value, $size) = @_; # The following line should say '8=>0xffffffffffffffff', but this upsets # builds of Perl with ivsize=4. This won't give the correct result for # large values but Perl will already have rejected them due to integer # overflow. my %masks = (1=>0xff, 2=>0xffff, 4=>0xffffffff, 8=>-1); return undef if (!defined $value); if ($value =~ /^(y|yes|on|true|down)?$/) { return 1; } elsif ($value =~ /^(n|no|off|false|none)$/) { return 0; } elsif ($value =~ /^up$/) { return 2; } elsif ($value =~ /^&/) { die "* Label '$value' used as non-32-bit integer\n" if ($size != 4); return $value; } elsif ($value =~ /^[0-9]/) { my $mask = $masks{$size}; die "* Bad size '$size' for integer\n" if (!$mask); return eval($value) & $mask; } elsif ($value =~ /^[A-Z][A-Z0-9_]+$/) { # Assume it's a valid define, e.g. from a dt-bindings file return $value; } elsif ($value =~ /^\(.+\)$/) { # Assume it's a valid expression return $value; } die "* Bad integer value '$value'\n"; } sub boolean_value { my ($value) = @_; if ($value =~ /^(y|yes|on|true|okay)?$/) { return 1; } elsif ($value =~ /^(n|no|off|false|disabled)$/) { return 0; } elsif ($value !~ /^[0-9]/) { die "* Bad boolean value '$value'\n"; } return $value != 0; } sub byte_array_value { my ($value) = @_; my $arr = []; foreach my $val (split(/[: ]/, $value)) { die "* invalid bytestring at '$val'\n" if ($val !~ /^([0-9a-f][0-9a-f])*$/i); while ($val =~ m/(..)/g) { push @$arr, hex($1); } } return $arr; } sub byte_array_string { my ($arr) = @_; my $str = ""; foreach my $val (@$arr) { $str .= ' ' if ($str); $str .= sprintf("%02x", $val); } return $str; } sub set_add { my ($set, $val) = @_; for (my $i = 0; $i < @$set; $i++) { return if ((ref $val && $set->[$i] == $val) || ($set->[$i] eq $val)); } push @$set, $val; } sub set_vals { my ($set) = @_; return @$set; } sub set_empty { my ($set) = @_; return @$set == 0; } sub map_add { my ($map, $name, $val) = @_; $map->{$name} = $val; } sub map_del { my ($map, $name) = @_; delete $map->{$name}; } sub map_find { my ($map, $name) = @_; return $map->{$name}; } sub get_int { my ($state) = @_; my $head = get_head($state); return undef if ($head !~ /^[0-9]/); get_next($state); return $head; } sub adj_val_refs { my ($inc, @vals) = @_; foreach my $val (@vals) { if ($val->[0] eq '&') { adj_ref($inc, $val->[1]); } elsif ($val->[0] eq '<') { foreach my $elem (@{$val->[2]}) { adj_ref($inc, $1) if ($elem =~ /^&(.*)/); } } } } sub adj_ref { my ($inc, $label) = @_; return if (!$cur_dt); $DB::single = 1 if ($inc < 0 && $cur_dt->{'refcount'}->{$label} == 0); $cur_dt->{'refcount'}->{$label} += $inc; printf ("[ ref %s -> %s ]\n", $label, $cur_dt->{'refcount'}->{$label}) if ($label eq $trace_label); } sub usage { print STDERR ("Usage: ovmerge \n"); print STDERR (" where is the name of an overlay, optionally followed by\n"); print STDERR (" a comma-separated list of parameters, each with optional '='\n"); print STDERR (" assignments. The presence of any parameters, or a comma followed by\n"); print STDERR (" no parameters, removes the parameter declarations from the merged\n"); print STDERR (" overlay to avoid a potential name clash.\n"); print STDERR (" and are any of:\n"); print STDERR (" -b Read files from specified git branch\n"); print STDERR (" -c Include 'redo' comment with command line (c.f. '-r')\n"); print STDERR (" -e Expand mode - list non-skipped lines in order of inclusion\n"); print STDERR (" -h Display this help info\n"); print STDERR (" -i Show include hierarchy for each file\n"); print STDERR (" -l Like expand mode, but labels each line with source file\n"); print STDERR (" -n No .dts file header (just parsing .dtsi files)\n"); print STDERR (" -p Emulate Pi firmware manipulation\n"); print STDERR (" -r Redo command comment in named files (c.f. '-c')\n"); print STDERR (" -s Sort nodes and properties (for easy comparison)\n"); print STDERR (" -S Instead of tabs, use 'n' spaces for indentation\n"); print STDERR (" -t Trace\n"); print STDERR (" -w Show warnings\n"); exit(1); } raspi-utils-20240903/pinctrl/000077500000000000000000000000001466562725600157315ustar00rootroot00000000000000raspi-utils-20240903/pinctrl/CMakeLists.txt000066400000000000000000000007531466562725600204760ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.10...3.27) include(GNUInstallDirs) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror -pedantic") #set project name project(pinctrl) #add executables add_executable(pinctrl pinctrl.c gpiolib.c util.c gpiochip_bcm2835.c gpiochip_bcm2712.c gpiochip_rp1.c) install(TARGETS pinctrl RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES pinctrl-completion.bash RENAME pinctrl DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/bash-completion/completions") raspi-utils-20240903/pinctrl/README.md000066400000000000000000000031321466562725600172070ustar00rootroot00000000000000 # pinctrl pinctrl is a more powerful replacement for raspi-gpio, a tool for displaying and modifying the GPIO and pin muxing state of a system. It accesses the hardware directly, bypassing the kernel drivers, and as such requires root privilege (run with "sudo"). The improvements over raspi-gpio include: * GPIO controllers are detected at runtime from Device Tree. * GPIOs can be referred to by name or number. * Pin mode (-p) switches the UI to be in terms of 40-way header pin numbers. * The "poll" command causes it to constantly monitor the specified pins, displaying any level changes it sees. For slow signals (up to a few hundred kHz) it can act as a basic logic analyser. * The "get" and "set" keywords are optional in most cases. * Splitting into a general gpiolib library and a separate client application allows new applications to be added easily. **Build Instructions** Install the prerequisites with "sudo apt install cmake" - you need at least version 3.10 of cmake. Run the following commands, either here or in the top-level directory to build and install everything: - *cmake .* - *make* - *sudo make install* **Usage** * `sudo pinctrl` (Display the state of all recognised GPIOs) * `sudo pinctrl -p` (Show the state of the 40-way header pins) * `sudo pinctrl 4,6 op dl` (Make GPIOs 4 and 6 outputs, driving low) * `sudo pinctrl poll BT_CTS,BT_RTS` (Monitor the levels of the Bluetooth flow control signals) * `pinctrl funcs 9-11` (List the available alternate functions on GPIOs 9, 10 and 11) * `pinctrl help` (Show the full usage guide) raspi-utils-20240903/pinctrl/gpiochip.h000066400000000000000000000031261466562725600177060ustar00rootroot00000000000000#ifndef GPIOCHIP_H #define GPIOCHIP_H #include "gpiolib.h" #define DECLARE_GPIO_CHIP(name, compatible, iface, size, data) \ GPIO_CHIP_T name ## _chip __attribute__ ((section ("gpiochips"))) __attribute__ ((used)) = \ { #name, compatible, iface, size, data } typedef struct GPIO_CHIP_INTERFACE_ GPIO_CHIP_INTERFACE_T; typedef struct GPIO_CHIP_ { const char *name; const char *compatible; const GPIO_CHIP_INTERFACE_T *interface; int size; uintptr_t data; } GPIO_CHIP_T; struct GPIO_CHIP_INTERFACE_ { void * (*gpio_create_instance)(const GPIO_CHIP_T *chip, const char *dtnode); int (*gpio_count)(void *priv); void * (*gpio_probe_instance)(void *priv, volatile uint32_t *base); GPIO_FSEL_T (*gpio_get_fsel)(void *priv, uint32_t gpio); void (*gpio_set_fsel)(void *priv, uint32_t gpio, const GPIO_FSEL_T func); void (*gpio_set_drive)(void *priv, uint32_t gpio, GPIO_DRIVE_T drv); void (*gpio_set_dir)(void *priv, uint32_t gpio, GPIO_DIR_T dir); GPIO_DIR_T (*gpio_get_dir)(void *priv, uint32_t gpio); int (*gpio_get_level)(void *priv, uint32_t gpio); /* The actual level observed */ GPIO_DRIVE_T (*gpio_get_drive)(void *priv, uint32_t gpio); /* What it is being driven as */ GPIO_PULL_T (*gpio_get_pull)(void *priv, uint32_t gpio); void (*gpio_set_pull)(void *priv, uint32_t gpio, GPIO_PULL_T pull); const char * (*gpio_get_name)(void *priv, uint32_t gpio); const char * (*gpio_get_fsel_name)(void *priv, uint32_t gpio, GPIO_FSEL_T fsel); }; extern const GPIO_CHIP_T __start_gpiochips; extern const GPIO_CHIP_T __stop_gpiochips; #endif raspi-utils-20240903/pinctrl/gpiochip_bcm2712.c000066400000000000000000001121561466562725600210420ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "gpiochip.h" #include "util.h" #define ARRAY_SIZE(_a) (sizeof(_a)/sizeof(_a[0])) /* 2712 definitions */ #define BCM2712_GIO_DATA 0x04 #define BCM2712_GIO_IODIR 0x08 #define BCM2712_PAD_PULL_OFF 0 #define BCM2712_PAD_PULL_DOWN 1 #define BCM2712_PAD_PULL_UP 2 #define BCM2712_MAX_INSTANCES 2 #define BCM2712_FSEL_COUNT 9 #define FLAGS_AON 1 #define FLAGS_C0 2 #define FLAGS_D0 4 #define FLAGS_GPIO 8 #define FLAGS_PINCTRL 16 struct bcm2712_inst { volatile uint32_t *gpio_base; volatile uint32_t *pinmux_base; unsigned pad_offset; uint32_t *bank_widths; unsigned flags; unsigned num_gpios; unsigned num_banks; }; static unsigned num_instances; static struct bcm2712_inst bcm2712_instances[BCM2712_MAX_INSTANCES] = { 0 }; static unsigned shared_flags; static const char *bcm2712_c0_gpio_alt_names[][BCM2712_FSEL_COUNT - 1] = { { "BSC_M3_SDA" , "VC_SDA0" , "GPCLK0" , "ENET0_LINK" , "VC_PWM1_0" , "VC_SPI0_CE1_N" , "IR_IN" , }, // 0 { "BSC_M3_SCL" , "VC_SCL0" , "GPCLK1" , "ENET0_ACTIVITY" , "VC_PWM1_1" , "SR_EDM_SENSE" , "VC_SPI0_CE0_N" , "VC_TXD3" , }, // 1 { "PDM_CLK" , "I2S_CLK0_IN" , "GPCLK2" , "VC_SPI4_CE1_N" , "PKT_CLK0" , "VC_SPI0_MISO" , "VC_RXD3" , }, // 2 { "PDM_DATA0" , "I2S_LR0_IN" , "VC_SPI4_CE0_N" , "PKT_SYNC0" , "VC_SPI0_MOSI" , "VC_CTS3" , }, // 3 { "PDM_DATA1" , "I2S_DATA0_IN" , "ARM_RTCK" , "VC_SPI4_MISO" , "PKT_DATA0" , "VC_SPI0_SCLK" , "VC_RTS3" , }, // 4 { "PDM_DATA2" , "VC_SCL3" , "ARM_TRST" , "SD_CARD_LED_E" , "VC_SPI4_MOSI" , "PKT_CLK1" , "VC_PCM_CLK" , "VC_SDA5" , }, // 5 { "PDM_DATA3" , "VC_SDA3" , "ARM_TCK" , "SD_CARD_WPROT_E" , "VC_SPI4_SCLK" , "PKT_SYNC1" , "VC_PCM_FS" , "VC_SCL5" , }, // 6 { "I2S_CLK0_OUT" , "SPDIF_OUT" , "ARM_TDI" , "SD_CARD_PRES_E" , "VC_SDA3" , "ENET0_RGMII_START_STOP", "VC_PCM_DIN" , "VC_SPI4_CE1_N" , }, // 7 { "I2S_LR0_OUT" , "AUD_FS_CLK0" , "ARM_TMS" , "SD_CARD_VOLT_E" , "VC_SCL3" , "ENET0_MII_TX_ERR" , "VC_PCM_DOUT" , "VC_SPI4_CE0_N" , }, // 8 { "I2S_DATA0_OUT" , "AUD_FS_CLK0" , "ARM_TDO" , "SD_CARD_PWR0_E" , "ENET0_MII_RX_ERR" , "SD_CARD_VOLT_C" , "VC_SPI4_SCLK" , }, // 9 { "BSC_M3_SCL" , "MTSIF_DATA4_ALT1" , "I2S_CLK0_IN" , "I2S_CLK0_OUT" , "VC_SPI5_CE1_N" , "ENET0_MII_CRS" , "SD_CARD_PWR0_C" , "VC_SPI4_MOSI" , }, // 10 { "BSC_M3_SDA" , "MTSIF_DATA5_ALT1" , "I2S_LR0_IN" , "I2S_LR0_OUT" , "VC_SPI5_CE0_N" , "ENET0_MII_COL" , "SD_CARD_PRES_C" , "VC_SPI4_MISO" , }, // 11 { "SPI_S_SS0B" , "MTSIF_DATA6_ALT1" , "I2S_DATA0_IN" , "I2S_DATA0_OUT" , "VC_SPI5_MISO" , "VC_I2CSL_MOSI" , "SD0_CLK" , "SD_CARD_VOLT_D", }, // 12 { "SPI_S_MISO" , "MTSIF_DATA7_ALT1" , "I2S_DATA1_OUT" , "USB_VBUS_PRESENT" , "VC_SPI5_MOSI" , "VC_I2CSL_CE_N" , "SD0_CMD" , "SD_CARD_PWR0_D", }, // 13 { "SPI_S_MOSI_OR_BSC_S_SDA", "VC_I2CSL_SCL_SCLK", "ENET0_RGMII_RX_OK", "ARM_TCK" , "VC_SPI5_SCLK" , "VC_PWM0_0" , "VC_SDA4" , "SD_CARD_PRES_D", }, // 14 { "SPI_S_SCK_OR_BSC_S_SCL" , "VC_I2CSL_SDA_MISO", "VC_SPI3_CE1_N" , "ARM_TMS" , "VC_PWM0_1" , "VC_SCL4" , "GPCLK0" , }, // 15 { "SD_CARD_PRES_B" , "I2S_CLK0_OUT" , "VC_SPI3_CE0_N" , "I2S_CLK0_IN" , "SD0_DAT0" , "ENET0_RGMII_MDIO" , "GPCLK1" , }, // 16 { "SD_CARD_WPROT_B" , "I2S_LR0_OUT" , "VC_SPI3_MISO" , "I2S_LR0_IN" , "EXT_SC_CLK" , "SD0_DAT1" , "ENET0_RGMII_MDC", "GPCLK2" , }, // 17 { "SD_CARD_LED_B" , "I2S_DATA0_OUT" , "VC_SPI3_MOSI" , "I2S_DATA0_IN" , "SD0_DAT2" , "ENET0_RGMII_IRQ" , "VC_PWM1_0" , }, // 18 { "SD_CARD_VOLT_B" , "USB_PWRFLT" , "VC_SPI3_SCLK" , "PKT_DATA1" , "SPDIF_OUT" , "SD0_DAT3" , "IR_IN" , "VC_PWM1_1" , }, // 19 { "SD_CARD_PWR0_B" , "UUI_TXD" , "VC_TXD0" , "ARM_TMS" , "UART_TXD_2" , "USB_PWRON" , "VC_PCM_CLK" , "VC_TXD4" , }, // 20 { "USB_PWRFLT" , "UUI_RXD" , "VC_RXD0" , "ARM_TCK" , "UART_RXD_2" , "SD_CARD_VOLT_B" , "VC_PCM_FS" , "VC_RXD4" , }, // 21 { "USB_PWRON" , "ENET0_LINK" , "VC_CTS0" , "MTSIF_ATS_RST" , "UART_RTS_2" , "USB_VBUS_PRESENT" , "VC_PCM_DIN" , "VC_SDA5" , }, // 22 { "USB_VBUS_PRESENT" , "ENET0_ACTIVITY" , "VC_RTS0" , "MTSIF_ATS_INC" , "UART_CTS_2" , "I2S_DATA2_OUT" , "VC_PCM_DOUT" , "VC_SCL5" , }, // 23 { "MTSIF_ATS_RST" , "PKT_CLK0" , "UART_RTS_0" , "ENET0_RGMII_RX_CLK", "ENET0_RGMII_START_STOP", "VC_SDA4" , "VC_TXD3" , }, // 24 { "MTSIF_ATS_INC" , "PKT_SYNC0" , "SC0_CLK" , "UART_CTS_0" , "ENET0_RGMII_RX_EN_CTL" , "ENET0_RGMII_RX_OK" , "VC_SCL4" , "VC_RXD3" , }, // 25 { "MTSIF_DATA1" , "PKT_DATA0" , "SC0_IO" , "UART_TXD_0" , "ENET0_RGMII_RXD_00" , "VC_TXD4" , "VC_SPI5_CE0_N" , }, // 26 { "MTSIF_DATA2" , "PKT_CLK1" , "SC0_AUX1" , "UART_RXD_0" , "ENET0_RGMII_RXD_01" , "VC_RXD4" , "VC_SPI5_SCLK" , }, // 27 { "MTSIF_CLK" , "PKT_SYNC1" , "SC0_AUX2" , "ENET0_RGMII_RXD_02", "VC_CTS4" , "VC_SPI5_MOSI" , }, // 28 { "MTSIF_DATA0" , "PKT_DATA1" , "SC0_PRES" , "ENET0_RGMII_RXD_03", "VC_RTS4" , "VC_SPI5_MISO" , }, // 29 { "MTSIF_SYNC" , "PKT_CLK2" , "SC0_RST" , "SD2_CLK" , "ENET0_RGMII_TX_CLK" , "GPCLK0" , "VC_PWM0_0" , }, // 30 { "MTSIF_DATA3" , "PKT_SYNC2" , "SC0_VCC" , "SD2_CMD" , "ENET0_RGMII_TX_EN_CTL" , "VC_SPI3_CE1_N" , "VC_PWM0_1" , }, // 31 { "MTSIF_DATA4" , "PKT_DATA2" , "SC0_VPP" , "SD2_DAT0" , "ENET0_RGMII_TXD_00" , "VC_SPI3_CE0_N" , "VC_TXD3" , }, // 32 { "MTSIF_DATA5" , "PKT_CLK3" , "SD2_DAT1" , "ENET0_RGMII_TXD_01", "VC_SPI3_SCLK" , "VC_RXD3" , }, // 33 { "MTSIF_DATA6" , "PKT_SYNC3" , "EXT_SC_CLK" , "SD2_DAT2" , "ENET0_RGMII_TXD_02" , "VC_SPI3_MOSI" , "VC_SDA5" , }, // 34 { "MTSIF_DATA7" , "PKT_DATA3" , "SD2_DAT3" , "ENET0_RGMII_TXD_03", "VC_SPI3_MISO" , "VC_SCL5" , }, // 35 { "SD0_CLK" , "MTSIF_ATS_RST" , "SC0_RST" , "I2S_DATA1_IN" , "VC_TXD3" , "VC_TXD2" , }, // 36 { "SD0_CMD" , "MTSIF_ATS_INC" , "SC0_VCC" , "VC_SPI0_CE1_N" , "I2S_DATA2_IN" , "VC_RXD3" , "VC_RXD2" , }, // 37 { "SD0_DAT0" , "MTSIF_DATA4_ALT" , "SC0_VPP" , "VC_SPI0_CE0_N" , "I2S_DATA3_IN" , "VC_CTS3" , "VC_RTS2" , }, // 38 { "SD0_DAT1" , "MTSIF_DATA5_ALT" , "SC0_CLK" , "VC_SPI0_MISO" , "VC_RTS3" , "VC_CTS2" , }, // 39 { "SD0_DAT2" , "MTSIF_DATA6_ALT" , "SC0_IO" , "VC_SPI0_MOSI" , "BSC_M3_SDA" , }, // 40 { "SD0_DAT3" , "MTSIF_DATA7_ALT" , "SC0_PRES" , "VC_SPI0_SCLK" , "BSC_M3_SCL" , }, // 41 { "VC_SPI0_CE1_N" , "MTSIF_CLK_ALT" , "VC_SDA0" , "SD_CARD_PRES_A" , "MTSIF_CLK_ALT1" , "ARM_TRST" , "PDM_CLK" , "SPI_M_SS1B" , }, // 42 { "VC_SPI0_CE0_N" , "MTSIF_SYNC_ALT" , "VC_SCL0" , "SD_CARD_PWR0_A" , "MTSIF_SYNC_ALT1" , "ARM_RTCK" , "PDM_DATA0" , "SPI_M_SS0B" , }, // 43 { "VC_SPI0_MISO" , "MTSIF_DATA0_ALT" , "ENET0_LINK" , "SD_CARD_LED_A" , "MTSIF_DATA0_ALT1" , "ARM_TDO" , "PDM_DATA1" , "SPI_M_MISO" , }, // 44 { "VC_SPI0_MOSI" , "MTSIF_DATA1_ALT" , "ENET0_ACTIVITY" , "SD_CARD_VOLT_A" , "MTSIF_DATA1_ALT1" , "ARM_TCK" , "PDM_DATA2" , "SPI_M_MOSI" , }, // 45 { "VC_SPI0_SCLK" , "MTSIF_DATA2_ALT" , "SD_CARD_WPROT_A" , "MTSIF_DATA2_ALT1" , "ARM_TDI" , "PDM_DATA3" , "SPI_M_SCK" , }, // 46 { "ENET0_ACTIVITY" , "MTSIF_DATA3_ALT" , "I2S_DATA3_OUT" , "MTSIF_DATA3_ALT1" , "ARM_TMS" , }, // 47 { "SC0_RST" , "USB_PWRFLT" , "SPDIF_OUT" , "MTSIF_ATS_RST" , }, // 48 { "SC0_VCC" , "USB_PWRON" , "AUD_FS_CLK0" , "MTSIF_ATS_INC" , }, // 49 { "SC0_VPP" , "USB_VBUS_PRESENT" , "SC0_AUX1" , }, // 50 { "SC0_CLK" , "ENET0_LINK" , "SC0_AUX2" , "SR_EDM_SENSE" , }, // 51 { "SC0_IO" , "ENET0_ACTIVITY" , "VC_PWM1_1" , }, // 52 { "SC0_PRES" , "ENET0_RGMII_RX_OK", "EXT_SC_CLK" , }, // 53 }; static const char *bcm2712_d0_gpio_alt_names[][BCM2712_FSEL_COUNT - 1] = { { "" }, // 0 { "VC_SCL0" , "USB_PWRFLT" , "GPCLK0" , "SD_CARD_LED_E" , "VC_SPI3_CE1_N" , "SR_EDM_SENSE" , "VC_SPI0_CE0_N" , "VC_TXD0" , }, // 1 { "VC_SDA0" , "USB_PWRON" , "GPCLK1" , "SD_CARD_WPROT_E" , "VC_SPI3_CE0_N" , "CLK_OBSERVE" , "VC_SPI0_MISO" , "VC_RXD0" , }, // 2 { "VC_SCL3" , "USB_VBUS_PRESENT" , "GPCLK2" , "SD_CARD_PRES_E" , "VC_SPI3_MISO" , "VC_SPI0_MOSI" , "VC_CTS0" , }, // 3 { "VC_SDA3" , "VC_PWM1_1" , "VC_SPI3_CE0_N" , "SD_CARD_VOLT_E" , "VC_SPI3_MOSI" , "VC_SPI0_SCLK" , "VC_RTS0" , }, // 4 { "" }, // 5 { "" }, // 6 { "" }, // 7 { "" }, // 8 { "" }, // 9 { "BSC_M3_SCL" , "VC_PWM1_0" , "VC_SPI3_CE1_N" , "SD_CARD_PWR0_E" , "VC_SPI3_SCLK" , "GPCLK0" , }, // 10 { "BSC_M3_SDA" , "VC_SPI3_MISO" , "CLK_OBSERVE" , "SD_CARD_PRES_C" , "GPCLK1" , }, // 11 { "SPI_S_SS0B" , "VC_SPI3_MOSI" , "SD_CARD_PWR0_C" , "SD_CARD_VOLT_D" , }, // 12 { "SPI_S_MISO" , "VC_SPI3_SCLK" , "SD_CARD_PRES_C" , "SD_CARD_PWR0_D" , }, // 13 { "SPI_S_MOSI_OR_BSC_S_SDA", "UUI_TXD" , "ARM_TCK" , "VC_PWM0_0" , "VC_SDA0" , "SD_CARD_PRES_D" , }, // 14 { "SPI_S_SCK_OR_BSC_S_SCL" , "UUI_RXD" , "ARM_TMS" , "VC_PWM0_1" , "VC_SCL0" , "GPCLK0" , }, // 15 { "" }, // 16 { "" }, // 17 { "SD_CARD_PRES_F" , "VC_PWM1_0" , }, // 18 { "SD_CARD_PWR0_F" , "USB_PWRFLT" , "VC_PWM1_1" , }, // 19 { "VC_SDA3" , "UUI_TXD" , "VC_TXD0" , "ARM_TMS" , "VC_TXD2" , }, // 20 { "VC_SCL3" , "UUI_RXD" , "VC_RXD0" , "ARM_TCK" , "VC_RXD2" , }, // 21 { "SD_CARD_PRES_F" , "VC_CTS0" , "VC_SDA3" , }, // 22 { "VC_RTS0" , "VC_SCL3" , }, // 23 { "SD_CARD_PRES_B" , "VC_SPI0_CE1_N" , "ARM_TRST" , "UART_RTS_0" , "USB_PWRFLT" , "VC_RTS2" , "VC_TXD0" , }, // 24 { "SD_CARD_WPROT_B" , "VC_SPI0_CE0_N" , "ARM_TCK" , "UART_CTS_0" , "USB_PWRON" , "VC_CTS2" , "VC_RXD0" , }, // 25 { "SD_CARD_LED_B" , "VC_SPI0_MISO" , "ARM_TDI" , "UART_TXD_0" , "USB_VBUS_PRESENT" , "VC_TXD2" , "VC_SPI0_CE0_N" , }, // 26 { "SD_CARD_VOLT_B" , "VC_SPI0_MOSI" , "ARM_TMS" , "UART_RXD_0" , "VC_RXD2" , "VC_SPI0_SCLK" , }, // 27 { "SD_CARD_PWR0_B" , "VC_SPI0_SCLK" , "ARM_TDO" , "VC_SDA0" , "VC_SPI0_MOSI" , }, // 28 { "ARM_RTCK" , "VC_SCL0" , "VC_SPI0_MISO" , }, // 29 { "SD2_CLK" , "GPCLK0" , "VC_PWM0_0" , }, // 30 { "SD2_CMD" , "VC_SPI3_CE1_N" , "VC_PWM0_1" , }, // 31 { "SD2_DAT0" , "VC_SPI3_CE0_N" , "VC_TXD3" , }, // 32 { "SD2_DAT1" , "VC_SPI3_SCLK" , "VC_RXD3" , }, // 33 { "SD2_DAT2" , "VC_SPI3_MOSI" , "VC_SDA5" , }, // 34 { "SD2_DAT3" , "VC_SPI3_MISO" , "VC_SCL5" , }, // 35 }; static const char *bcm2712_c0_aon_gpio_alt_names[][BCM2712_FSEL_COUNT - 1] = { { "IR_IN" , "VC_SPI0_CE1_N" , "VC_TXD3" , "VC_SDA3" , "TE0" , "VC_SDA0" , }, // 0 { "VC_PWM0_0" , "VC_SPI0_CE0_N" , "VC_RXD3" , "VC_SCL3" , "TE1" , "AON_PWM0" , "VC_SCL0" , "VC_PWM1_0" , }, // 1 { "VC_PWM0_1" , "VC_SPI0_MISO" , "VC_CTS3" , "CTL_HDMI_5V" , "FL0" , "AON_PWM1" , "IR_IN" , "VC_PWM1_1" , }, // 2 { "IR_IN" , "VC_SPI0_MOSI" , "VC_RTS3" , "AON_FP_4SEC_RESETB", "FL1" , "SD_CARD_VOLT_G" , "AON_GPCLK" , }, // 3 { "GPCLK0" , "VC_SPI0_SCLK" , "VC_I2CSL_SCL_SCLK", "AON_GPCLK" , "PM_LED_OUT" , "AON_PWM0" , "SD_CARD_PWR0_G", "VC_PWM0_0" , }, // 4 { "GPCLK1" , "IR_IN" , "VC_I2CSL_SDA_MISO", "CLK_OBSERVE" , "AON_PWM1" , "SD_CARD_PRES_G" , "VC_PWM0_1" , }, // 5 { "UART_TXD_1" , "VC_TXD4" , "GPCLK2" , "CTL_HDMI_5V" , "VC_TXD0" , "VC_SPI3_CE0_N" , }, // 6 { "UART_RXD_1" , "VC_RXD4" , "GPCLK0" , "AON_PWM0" , "VC_RXD0" , "VC_SPI3_SCLK" , }, // 7 { "UART_RTS_1" , "VC_RTS4" , "VC_I2CSL_MOSI" , "CTL_HDMI_5V" , "VC_RTS0" , "VC_SPI3_MOSI" , }, // 8 { "UART_CTS_1" , "VC_CTS4" , "VC_I2CSL_CE_N" , "AON_PWM1" , "VC_CTS0" , "VC_SPI3_MISO" , }, // 9 { "TSIO_CLK_OUT" , "CTL_HDMI_5V" , "SC0_AUX1" , "SPDIF_OUT" , "VC_SPI5_CE1_N", "USB_PWRFLT" , "AON_GPCLK" , "SD_CARD_VOLT_F", }, // 10 { "TSIO_DATA_IN" , "UART_RTS_0" , "SC0_AUX2" , "AUD_FS_CLK0" , "VC_SPI5_CE0_N", "USB_VBUS_PRESENT", "VC_RTS2" , "SD_CARD_PWR0_F", }, // 11 { "TSIO_DATA_OUT" , "UART_CTS_0" , "VC_RTS0" , "TSIO_VCTRL" , "VC_SPI5_MISO" , "USB_PWRON" , "VC_CTS2" , "SD_CARD_PRES_F", }, // 12 { "BSC_M1_SDA" , "UART_TXD_0" , "VC_TXD0" , "UUI_TXD" , "VC_SPI5_MOSI" , "ARM_TMS" , "VC_TXD2" , "VC_SDA3" , }, // 13 { "BSC_M1_SCL" , "UART_RXD_0" , "VC_RXD0" , "UUI_RXD" , "VC_SPI5_SCLK" , "ARM_TCK" , "VC_RXD2" , "VC_SCL3" , }, // 14 { "IR_IN" , "AON_FP_4SEC_RESETB", "VC_CTS0" , "PM_LED_OUT" , "CTL_HDMI_5V" , "AON_PWM0" , "AON_GPCLK" , }, // 15 { "AON_CPU_STANDBYB", "GPCLK0" , "PM_LED_OUT" , "CTL_HDMI_5V" , "VC_PWM0_0" , "USB_PWRON" , "AUD_FS_CLK0" , }, // 16 // Pad out the bank to 32 entries { "" }, { "" }, { "" }, { "" }, { "" }, { "" }, { "" }, // 17-23 { "" }, { "" }, { "" }, { "" }, { "" }, { "" }, { "" }, { "" }, // 24-31 { "HDMI_TX0_BSC_SCL", "HDMI_TX0_AUTO_I2C_SCL", "BSC_M0_SCL", "VC_SCL0", }, // sgpio 0 { "HDMI_TX0_BSC_SDA", "HDMI_TX0_AUTO_I2C_SDA", "BSC_M0_SDA", "VC_SDA0", }, // sgpio 1 { "HDMI_TX1_BSC_SCL", "HDMI_TX1_AUTO_I2C_SCL", "BSC_M1_SCL", "VC_SCL4", "CTL_HDMI_5V", }, // sgpio 2 { "HDMI_TX1_BSC_SDA", "HDMI_TX1_AUTO_I2C_SDA", "BSC_M1_SDA", "VC_SDA4", }, // sgpio 3 { "AVS_PMU_BSC_SCL", "BSC_M2_SCL", "VC_SCL5", "CTL_HDMI_5V", }, // sgpio 4 { "AVS_PMU_BSC_SDA", "BSC_M2_SDA", "VC_SDA5", }, // sgpio 5 }; static const char *bcm2712_d0_aon_gpio_alt_names[][BCM2712_FSEL_COUNT - 1] = { { "IR_IN" , "VC_SPI0_CE1_N" , "VC_TXD0" , "VC_SDA3" , "UART_TXD_0" , "VC_SDA0" , }, // 0 { "VC_PWM0_0" , "VC_SPI0_CE0_N" , "VC_RXD0" , "VC_SCL3" , "UART_RXD_0" , "AON_PWM0" , "VC_SCL0" , "VC_PWM1_0" , }, // 1 { "VC_PWM0_1" , "VC_SPI0_MISO" , "VC_CTS0" , "CTL_HDMI_5V" , "UART_CTS_0" , "AON_PWM1" , "IR_IN" , "VC_PWM1_1" , }, // 2 { "IR_IN" , "VC_SPI0_MOSI" , "VC_RTS0" , "UART_RTS_0" , "SD_CARD_VOLT_G" , "AON_GPCLK" , }, // 3 { "GPCLK0" , "VC_SPI0_SCLK" , "PM_LED_OUT" , "AON_PWM0" , "SD_CARD_PWR0_G" , "VC_PWM0_0" , }, // 4 { "GPCLK1" , "IR_IN" , "AON_PWM1" , "SD_CARD_PRES_G" , "VC_PWM0_1" , }, // 5 { "UART_TXD_1" , "VC_TXD2" , "CTL_HDMI_5V" , "GPCLK2" , "VC_SPI3_CE0_N" , }, // 6 { "" }, // 7 { "UART_RTS_1" , "VC_RTS2" , "CTL_HDMI_5V" , "VC_SPI0_CE1_N" , "VC_SPI3_SCLK" , }, // 8 { "UART_CTS_1" , "VC_CTS2" , "VC_CTS0" , "AON_PWM1" , "VC_SPI0_CE0_N" , "VC_RTS2" , "VC_SPI3_MOSI" , }, // 9 { "" }, // 10 { "" }, // 11 { "UART_RXD_1" , "VC_RXD2" , "VC_RTS0" , "VC_SPI0_MISO" , "USB_PWRON" , "VC_CTS2" , "VC_SPI3_MISO" , }, // 12 { "BSC_M1_SDA" , "VC_TXD0" , "UUI_TXD" , "VC_SPI0_MOSI" , "ARM_TMS" , "VC_TXD2" , "VC_SDA3" , }, // 13 { "BSC_M1_SCL" , "AON_GPCLK" , "VC_RXD0" , "UUI_RXD" , "VC_SPI0_SCLK" , "ARM_TCK" , "VC_RXD2" , "VC_SCL3" , }, // 14 // Pad out the bank to 32 entries { "" }, // 15 { "" }, { "" }, { "" }, { "" }, { "" }, { "" }, { "" }, { "" }, // 16-23 { "" }, { "" }, { "" }, { "" }, { "" }, { "" }, { "" }, { "" }, // 24-31 { "HDMI_TX0_BSC_SCL", "HDMI_TX0_AUTO_I2C_SCL", "BSC_M0_SCL", "VC_SCL0", }, // sgpio 0 { "HDMI_TX0_BSC_SDA", "HDMI_TX0_AUTO_I2C_SDA", "BSC_M0_SDA", "VC_SDA0", }, // sgpio 1 { "HDMI_TX1_BSC_SCL", "HDMI_TX1_AUTO_I2C_SCL", "BSC_M1_SCL", "VC_SCL0", "CTL_HDMI_5V", }, // sgpio 2 { "HDMI_TX1_BSC_SDA", "HDMI_TX1_AUTO_I2C_SDA", "BSC_M1_SDA", "VC_SDA0", }, // sgpio 3 { "AVS_PMU_BSC_SCL", "BSC_M2_SCL", "VC_SCL3", "CTL_HDMI_5V", }, // sgpio 4 { "AVS_PMU_BSC_SDA", "BSC_M2_SDA", "VC_SDA3", }, // sgpio 5 }; static const int bcm2712_gpio_d0_to_c0[] = { -1, 0, 1, 2, 3, -1, -1, -1, -1, -1, 4, 5, 6, 7, 8, 9, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27 }; static const int bcm2712_gpio_aon_d0_to_c0[] = { 0, 1, 2, 3, 4, 5, 6, -1, 7, 8, -1, -1, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 32, 33, 34, 35, 36, 37 }; static volatile uint32_t *bcm2712_gpio_base(struct bcm2712_inst *inst, unsigned gpio, unsigned int *bit) { unsigned bank = gpio / 32; gpio %= 32; if ((bank >= inst->num_banks) || (gpio >= inst->bank_widths[bank]) || !inst->gpio_base) return NULL; *bit = gpio; return inst->gpio_base + bank * (0x20 / 4); } static volatile uint32_t *bcm2712_pinmux_base(struct bcm2712_inst *inst, unsigned gpio, unsigned int *bit) { unsigned bank, gpio_offset; if (gpio >= inst->num_gpios || !inst->pinmux_base) return NULL; if (inst->flags & FLAGS_D0) { if (inst->flags & FLAGS_AON) gpio = bcm2712_gpio_aon_d0_to_c0[gpio]; else gpio = bcm2712_gpio_d0_to_c0[gpio]; if ((int)gpio < 0) return NULL; } bank = gpio / 32; gpio_offset = gpio % 32; if ((bank >= inst->num_banks) || (gpio_offset >= inst->bank_widths[bank])) return NULL; if (inst->flags & FLAGS_AON) { if (bank == 1) { if (gpio_offset == 4) { *bit = 0; return inst->pinmux_base + 1; } else if (gpio_offset == 5) { *bit = 0; return inst->pinmux_base + 2; } else { *bit = gpio_offset * 4; return inst->pinmux_base; } } *bit = (gpio_offset % 8) * 4; return inst->pinmux_base + 3 + (gpio_offset / 8); } *bit = (gpio_offset % 8) * 4; return inst->pinmux_base + (bank * 4) + (gpio_offset / 8); } static volatile uint32_t *bcm2712_pad_base(struct bcm2712_inst *inst, unsigned gpio, unsigned int *bit) { unsigned bank, gpio_offset; if (gpio >= inst->num_gpios || !inst->pinmux_base) return NULL; if (inst->flags & FLAGS_D0) { if (inst->flags & FLAGS_AON) gpio = bcm2712_gpio_aon_d0_to_c0[gpio]; else gpio = bcm2712_gpio_d0_to_c0[gpio]; if ((int)gpio < 0) return NULL; } bank = gpio / 32; gpio_offset = gpio % 32; if ((bank >= inst->num_banks) || (gpio_offset >= inst->bank_widths[bank])) return NULL; if ((inst->flags & FLAGS_AON) && (bank > 0)) { /* There is no SGPIO pad control (that I know of) */ return NULL; } gpio = gpio_offset + inst->pad_offset; *bit = (gpio % 15) * 2; return inst->pinmux_base + (gpio / 15); } static int bcm2712_gpio_get_level(void *priv, unsigned gpio) { struct bcm2712_inst *inst = priv; unsigned int bit; volatile uint32_t *gpio_base = bcm2712_gpio_base(inst, gpio, &bit); if (!gpio_base) return -1; return !!(gpio_base[BCM2712_GIO_DATA / 4] & (1 << bit)); } static void bcm2712_gpio_set_drive(void *priv, unsigned gpio, GPIO_DRIVE_T drv) { struct bcm2712_inst *inst = priv; unsigned int bit; volatile uint32_t *gpio_base = bcm2712_gpio_base(inst, gpio, &bit); uint32_t gpio_val; if (!gpio_base) return; gpio_val = gpio_base[BCM2712_GIO_DATA / 4]; gpio_val = (gpio_val & ~(1U << bit)) | (drv << bit); gpio_base[BCM2712_GIO_DATA / 4] = gpio_val; } static GPIO_DRIVE_T bcm2712_gpio_get_drive(void *priv, unsigned gpio) { struct bcm2712_inst *inst = priv; unsigned int bit; volatile uint32_t *gpio_base = bcm2712_gpio_base(inst, gpio, &bit); uint32_t gpio_val; if (!gpio_base) return DRIVE_MAX; gpio_val = gpio_base[BCM2712_GIO_DATA / 4]; return (gpio_val & (1U << bit)) ? DRIVE_HIGH : DRIVE_LOW; } static void bcm2712_gpio_set_dir(void *priv, unsigned gpio, GPIO_DIR_T dir) { struct bcm2712_inst *inst = priv; unsigned int bit; volatile uint32_t *gpio_base = bcm2712_gpio_base(inst, gpio, &bit); uint32_t gpio_val; if (!gpio_base) return; gpio_val = gpio_base[BCM2712_GIO_IODIR / 4]; gpio_val &= ~(1U << bit); gpio_val |= ((dir == DIR_INPUT) << bit); gpio_base[BCM2712_GIO_IODIR / 4] = gpio_val; } static GPIO_DIR_T bcm2712_gpio_get_dir(void *priv, unsigned gpio) { struct bcm2712_inst *inst = priv; unsigned int bit; volatile uint32_t *gpio_base = bcm2712_gpio_base(inst, gpio, &bit); uint32_t gpio_val; if (!gpio_base) return DIR_MAX; gpio_val = gpio_base[BCM2712_GIO_IODIR / 4]; return (gpio_val & (1U << bit)) ? DIR_INPUT : DIR_OUTPUT; } static GPIO_FSEL_T bcm2712_pinctrl_get_fsel(void *priv, unsigned gpio) { struct bcm2712_inst *inst = priv; unsigned int pinmux_bit; volatile uint32_t *pinmux_base = bcm2712_pinmux_base(inst, gpio, &pinmux_bit); int fsel; if (!pinmux_base) return -1; fsel = ((*pinmux_base >> pinmux_bit) & 0xf); if (fsel == 0) return GPIO_FSEL_GPIO; else if (fsel < BCM2712_FSEL_COUNT) return GPIO_FSEL_FUNC1 + (fsel - 1); else if (fsel == 0xf) // Choose one value as a considered NONE return GPIO_FSEL_NONE; /* Unknown FSEL */ return -1; } static void bcm2712_pinctrl_set_fsel(void *priv, unsigned gpio, const GPIO_FSEL_T func) { struct bcm2712_inst *inst = priv; unsigned int pinmux_bit; volatile uint32_t *pinmux_base = bcm2712_pinmux_base(inst, gpio, &pinmux_bit); uint32_t pinmux_val; int fsel; if (!pinmux_base) return; if (func == GPIO_FSEL_INPUT || func == GPIO_FSEL_OUTPUT || func == GPIO_FSEL_GPIO) { // Set direction before switching // N.B. We explicitly interpret a request for FUNC_A0/GPIO as "last/current GPIO dir" fsel = 0; if (func == GPIO_FSEL_INPUT) bcm2712_gpio_set_dir(priv, gpio, DIR_INPUT); else if (func == GPIO_FSEL_OUTPUT) bcm2712_gpio_set_dir(priv, gpio, DIR_OUTPUT); } else if (func >= GPIO_FSEL_FUNC0 && func <= GPIO_FSEL_FUNC8) { fsel = func - GPIO_FSEL_FUNC0; } else { return; } pinmux_val = *pinmux_base; pinmux_val &= ~(0xf << pinmux_bit); pinmux_val |= (fsel << pinmux_bit); *pinmux_base = pinmux_val; } static GPIO_PULL_T bcm2712_pinctrl_get_pull(void *priv, unsigned gpio) { struct bcm2712_inst *inst = priv; unsigned int bit; volatile uint32_t *pad_base = bcm2712_pad_base(inst, gpio, &bit); uint32_t pad_val; if (!pad_base) return PULL_MAX; pad_val = (*pad_base >> bit) & 0x3; switch (pad_val) { case BCM2712_PAD_PULL_OFF: return PULL_NONE; case BCM2712_PAD_PULL_DOWN: return PULL_DOWN; case BCM2712_PAD_PULL_UP: return PULL_UP; default: return PULL_MAX; /* This is an error */ } } static void bcm2712_pinctrl_set_pull(void *priv, unsigned gpio, GPIO_PULL_T pull) { struct bcm2712_inst *inst = priv; unsigned int bit = 0; volatile uint32_t *pad_base = bcm2712_pad_base(inst, gpio, &bit); uint32_t padval; int val; if (!pad_base) return; switch (pull) { case PULL_NONE: val = BCM2712_PAD_PULL_OFF; break; case PULL_DOWN: val = BCM2712_PAD_PULL_DOWN; break; case PULL_UP: val = BCM2712_PAD_PULL_UP; break; default: assert(0); return; } padval = *pad_base; padval &= ~(3 << bit); padval |= (val << bit); *pad_base = padval; } static void *bcm2712_gpio_create_instance(const GPIO_CHIP_T *chip, const char *dtnode) { struct bcm2712_inst *inst = NULL; uint32_t *widths; unsigned num_banks, inst_gpios; unsigned flags = FLAGS_GPIO | chip->data; unsigned bank; unsigned i; widths = dt_read_cells(dtnode, "brcm,gpio-bank-widths", &num_banks); if (!widths) return NULL; inst_gpios = 0; for (bank = 0; bank < num_banks; bank++) { inst_gpios = ROUND_UP(inst_gpios, 32) + widths[bank]; } flags |= shared_flags; if (widths[0] < 32) { flags |= FLAGS_AON; if (widths[0] == 15) flags |= FLAGS_D0; else flags |= FLAGS_C0; } else if (!(flags & (FLAGS_C0 | FLAGS_D0))) { size_t names_len; char *names = dt_read_prop(dtnode, "gpio-line-names", &names_len); if (!names[0]) flags |= FLAGS_D0; dt_free(names); } shared_flags |= (flags & (FLAGS_C0 | FLAGS_D0)); /* look for a corresponding pinctrl instance */ for (i = 0; i < num_instances; i++) { struct bcm2712_inst *pinctrl_inst = &bcm2712_instances[i]; pinctrl_inst->flags |= shared_flags; if (!((pinctrl_inst->flags ^ flags) & FLAGS_AON)) { if (pinctrl_inst->flags & FLAGS_GPIO) { assert(!"duplicate gpio nodes?"); return NULL; } inst = pinctrl_inst; break; } } if (!inst) { if (num_instances == BCM2712_MAX_INSTANCES) return NULL; inst = &bcm2712_instances[num_instances++]; } inst->num_gpios = inst_gpios; inst->num_banks = num_banks; inst->bank_widths = widths; inst->flags |= flags; return (void *)inst; } static int bcm2712_gpio_count(void *priv) { struct bcm2712_inst *inst = priv; return inst->num_gpios; } static void *bcm2712_gpio_probe_instance(void *priv, volatile uint32_t *base) { struct bcm2712_inst *inst = priv; inst->gpio_base = base; return inst; } static void *bcm2712_pinctrl_create_instance(const GPIO_CHIP_T *chip, const char *dtnode) { struct bcm2712_inst *inst = NULL; unsigned flags = FLAGS_PINCTRL | chip->data; unsigned reg_cells, reg_size; uint32_t *reg; unsigned i; if (dtnode) { reg = dt_read_cells(dtnode, "reg", ®_cells); if (!reg || reg_cells < 2) return NULL; reg_size = (reg_cells > 1) ? reg[reg_cells - 1] : 0; dt_free(reg); switch (reg_size) { case 0x1c: assert((flags & FLAGS_AON) && (flags & FLAGS_D0)); break; case 0x20: assert(((flags & FLAGS_AON) && !(flags & FLAGS_D0)) || (!(flags & FLAGS_AON) && (flags & FLAGS_D0))); break; case 0x30: assert(!(flags & FLAGS_AON) && !(flags & FLAGS_D0)); break; default: assert(0); } } shared_flags |= (flags & (FLAGS_C0 | FLAGS_D0)); /* look for a corresponding gpio instance */ for (i = 0; i < num_instances; i++) { struct bcm2712_inst *gpio_inst = &bcm2712_instances[i]; gpio_inst->flags |= shared_flags; if (!((gpio_inst->flags ^ flags) & FLAGS_AON)) { if (gpio_inst->flags & FLAGS_PINCTRL) { assert(!"duplicate pinctrl nodes?"); return NULL; } inst = gpio_inst; break; } } if (!inst) { if (num_instances == BCM2712_MAX_INSTANCES) return NULL; inst = &bcm2712_instances[num_instances++]; } inst->flags |= flags; return (void *)inst; } static int bcm2712_pinctrl_count(void *priv) { struct bcm2712_inst *inst = priv; if (inst->flags & FLAGS_GPIO) return 0; /* Don't occupy any GPIO space */ if (!inst->num_gpios) { switch (inst->flags & (FLAGS_AON | FLAGS_C0 | FLAGS_D0)) { case 0: case FLAGS_C0: inst->num_gpios = 54; break; case FLAGS_D0: inst->num_gpios = 36; break; case FLAGS_AON: case FLAGS_AON | FLAGS_D0: case FLAGS_AON | FLAGS_C0: inst->num_gpios = 38; break; default: break; } } return inst->num_gpios; } static void *bcm2712_pinctrl_probe_instance(void *priv, volatile uint32_t *base) { struct bcm2712_inst *inst = priv; unsigned pad_offset; inst->pinmux_base = base; switch (inst->flags & (FLAGS_D0 | FLAGS_C0 | FLAGS_AON)) { case FLAGS_C0: default: pad_offset = 112; break; case FLAGS_D0: pad_offset = 65; break; case FLAGS_AON: case FLAGS_C0 | FLAGS_AON: pad_offset = 100; break; case FLAGS_D0 | FLAGS_AON: pad_offset = 84; break; } inst->pad_offset = pad_offset; return inst; } static const char *bcm2712_pinctrl_get_fsel_name(void *priv, unsigned gpio, GPIO_FSEL_T fsel) { struct bcm2712_inst *inst = priv; const char *name = NULL; switch (fsel) { case GPIO_FSEL_GPIO: case GPIO_FSEL_FUNC0: name = "gpio"; break; case GPIO_FSEL_INPUT: name = "input"; break; case GPIO_FSEL_OUTPUT: name = "output"; break; case GPIO_FSEL_NONE: name = "none"; break; case GPIO_FSEL_FUNC1: case GPIO_FSEL_FUNC2: case GPIO_FSEL_FUNC3: case GPIO_FSEL_FUNC4: case GPIO_FSEL_FUNC5: case GPIO_FSEL_FUNC6: case GPIO_FSEL_FUNC7: case GPIO_FSEL_FUNC8: if (gpio < inst->num_gpios) { switch (inst->flags & (FLAGS_AON | FLAGS_C0 | FLAGS_D0)) { case FLAGS_C0 | FLAGS_AON: case FLAGS_AON: name = bcm2712_c0_aon_gpio_alt_names[gpio][fsel - 1]; break; case FLAGS_C0: case 0: name = bcm2712_c0_gpio_alt_names[gpio][fsel - 1]; break; case FLAGS_D0 | FLAGS_AON: name = bcm2712_d0_aon_gpio_alt_names[gpio][fsel - 1]; break; case FLAGS_D0: name = bcm2712_d0_gpio_alt_names[gpio][fsel - 1]; break; } if (!name) name = "-"; } break; default: break; } return name; } static const char *bcm2712_gpio_get_name(void *priv, unsigned gpio) { struct bcm2712_inst *inst = priv; const char *fsel_name; static char name_buf[16]; unsigned gpio_offset; unsigned bank; fsel_name = bcm2712_pinctrl_get_fsel_name(priv, gpio, GPIO_FSEL_FUNC1); if (!fsel_name || !fsel_name[0]) return NULL; bank = gpio / 32; gpio_offset = gpio % 32; if ((inst->flags & FLAGS_GPIO) && ((bank >= inst->num_banks) || (gpio_offset >= inst->bank_widths[bank]))) return NULL; if (inst->flags & FLAGS_AON) { if (bank == 1) sprintf(name_buf, "AON_SGPIO%d", gpio_offset); else sprintf(name_buf, "AON_GPIO%d", gpio_offset); } else { sprintf(name_buf, "GPIO%d", gpio); } return name_buf; } static const GPIO_CHIP_INTERFACE_T bcm2712_gpio_interface = { .gpio_create_instance = bcm2712_gpio_create_instance, .gpio_count = bcm2712_gpio_count, .gpio_probe_instance = bcm2712_gpio_probe_instance, .gpio_get_fsel = bcm2712_pinctrl_get_fsel, .gpio_set_fsel = bcm2712_pinctrl_set_fsel, .gpio_set_drive = bcm2712_gpio_set_drive, .gpio_set_dir = bcm2712_gpio_set_dir, .gpio_get_dir = bcm2712_gpio_get_dir, .gpio_get_level = bcm2712_gpio_get_level, .gpio_get_drive = bcm2712_gpio_get_drive, .gpio_get_pull = bcm2712_pinctrl_get_pull, .gpio_set_pull = bcm2712_pinctrl_set_pull, .gpio_get_name = bcm2712_gpio_get_name, .gpio_get_fsel_name = bcm2712_pinctrl_get_fsel_name, }; DECLARE_GPIO_CHIP(brcmstb, "brcm,brcmstb-gpio", &bcm2712_gpio_interface, 0x40, 0); static const GPIO_CHIP_INTERFACE_T bcm2712_pinctrl_interface = { .gpio_create_instance = bcm2712_pinctrl_create_instance, .gpio_count = bcm2712_pinctrl_count, .gpio_probe_instance = bcm2712_pinctrl_probe_instance, .gpio_get_fsel = bcm2712_pinctrl_get_fsel, .gpio_set_fsel = bcm2712_pinctrl_set_fsel, .gpio_set_drive = bcm2712_gpio_set_drive, .gpio_set_dir = bcm2712_gpio_set_dir, .gpio_get_dir = bcm2712_gpio_get_dir, .gpio_get_level = bcm2712_gpio_get_level, .gpio_get_drive = bcm2712_gpio_get_drive, .gpio_get_pull = bcm2712_pinctrl_get_pull, .gpio_set_pull = bcm2712_pinctrl_set_pull, .gpio_get_name = bcm2712_gpio_get_name, .gpio_get_fsel_name = bcm2712_pinctrl_get_fsel_name, }; DECLARE_GPIO_CHIP(bcm2712, "brcm,bcm2712-pinctrl", &bcm2712_pinctrl_interface, 0x30, 0); DECLARE_GPIO_CHIP(bcm2712_aon, "brcm,bcm2712-aon-pinctrl", &bcm2712_pinctrl_interface, 0x20, FLAGS_AON); DECLARE_GPIO_CHIP(bcm2712c0, "brcm,bcm2712c0-pinctrl", &bcm2712_pinctrl_interface, 0x30, FLAGS_C0); DECLARE_GPIO_CHIP(bcm2712c0_aon, "brcm,bcm2712c0-aon-pinctrl", &bcm2712_pinctrl_interface, 0x20, FLAGS_C0 | FLAGS_AON); DECLARE_GPIO_CHIP(bcm2712d0, "brcm,bcm2712d0-pinctrl", &bcm2712_pinctrl_interface, 0x20, FLAGS_D0); DECLARE_GPIO_CHIP(bcm2712d0_aon, "brcm,bcm2712d0-aon-pinctrl", &bcm2712_pinctrl_interface, 0x1c, FLAGS_D0 | FLAGS_AON); raspi-utils-20240903/pinctrl/gpiochip_bcm2835.c000066400000000000000000000473511466562725600210540ustar00rootroot00000000000000#include #include #include #include #include #include #include "gpiochip.h" #include "util.h" #define ARRAY_SIZE(_a) (sizeof(_a)/sizeof(_a[0])) #define BCM2835_NUM_GPIOS 54 #define BCM2835_ALT_COUNT 6 #define BCM2711_NUM_GPIOS 54 #define BCM2711_ALT_COUNT 6 /* 2835 register offsets */ #define GPFSEL0 0 #define GPFSEL1 1 #define GPFSEL2 2 #define GPFSEL3 3 #define GPFSEL4 4 #define GPFSEL5 5 #define GPSET0 7 #define GPSET1 8 #define GPCLR0 10 #define GPCLR1 11 #define GPLEV0 13 #define GPLEV1 14 #define GPPUD 37 #define GPPUDCLK0 38 #define GPPUDCLK1 39 /* 2711 has a different mechanism for pin pull-up/down/enable */ #define GPPUPPDN0 57 /* Pin pull-up/down for pins 15:0 */ #define GPPUPPDN1 58 /* Pin pull-up/down for pins 31:16 */ #define GPPUPPDN2 59 /* Pin pull-up/down for pins 47:32 */ #define GPPUPPDN3 60 /* Pin pull-up/down for pins 57:48 */ static const char *bcm2835_gpio_alt_names[BCM2835_NUM_GPIOS][BCM2835_ALT_COUNT] = { { "SDA0" , "SA5" , "PCLK" , "AVEOUT_VCLK" , "AVEIN_VCLK" , 0 , }, { "SCL0" , "SA4" , "DE" , "AVEOUT_DSYNC" , "AVEIN_DSYNC", 0 , }, { "SDA1" , "SA3" , "LCD_VSYNC" , "AVEOUT_VSYNC" , "AVEIN_VSYNC", 0 , }, { "SCL1" , "SA2" , "LCD_HSYNC" , "AVEOUT_HSYNC" , "AVEIN_HSYNC", 0 , }, { "GPCLK0" , "SA1" , "DPI_D0" , "AVEOUT_VID0" , "AVEIN_VID0" , "ARM_TDI" , }, { "GPCLK1" , "SA0" , "DPI_D1" , "AVEOUT_VID1" , "AVEIN_VID1" , "ARM_TDO" , }, { "GPCLK2" , "SOE_N_SE" , "DPI_D2" , "AVEOUT_VID2" , "AVEIN_VID2" , "ARM_RTCK" , }, { "SPI0_CE1_N", "SWE_N_SRW_N", "DPI_D3" , "AVEOUT_VID3" , "AVEIN_VID3" , 0 , }, { "SPI0_CE0_N", "SD0" , "DPI_D4" , "AVEOUT_VID4" , "AVEIN_VID4" , 0 , }, { "SPI0_MISO" , "SD1" , "DPI_D5" , "AVEOUT_VID5" , "AVEIN_VID5" , 0 , }, { "SPI0_MOSI" , "SD2" , "DPI_D6" , "AVEOUT_VID6" , "AVEIN_VID6" , 0 , }, { "SPI0_SCLK" , "SD3" , "DPI_D7" , "AVEOUT_VID7" , "AVEIN_VID7" , 0 , }, { "PWM0" , "SD4" , "DPI_D8" , "AVEOUT_VID8" , "AVEIN_VID8" , "ARM_TMS" , }, { "PWM1" , "SD5" , "DPI_D9" , "AVEOUT_VID9" , "AVEIN_VID9" , "ARM_TCK" , }, { "TXD0" , "SD6" , "DPI_D10" , "AVEOUT_VID10" , "AVEIN_VID10", "TXD1" , }, { "RXD0" , "SD7" , "DPI_D11" , "AVEOUT_VID11" , "AVEIN_VID11", "RXD1" , }, { "FL0" , "SD8" , "DPI_D12" , "CTS0" , "SPI1_CE2_N" , "CTS1" , }, { "FL1" , "SD9" , "DPI_D13" , "RTS0" , "SPI1_CE1_N" , "RTS1" , }, { "PCM_CLK" , "SD10" , "DPI_D14" , "I2CSL_SDA_MOSI", "SPI1_CE0_N" , "PWM0" , }, { "PCM_FS" , "SD11" , "DPI_D15" , "I2CSL_SCL_SCLK", "SPI1_MISO" , "PWM1" , }, { "PCM_DIN" , "SD12" , "DPI_D16" , "I2CSL_MISO" , "SPI1_MOSI" , "GPCLK0" , }, { "PCM_DOUT" , "SD13" , "DPI_D17" , "I2CSL_CE_N" , "SPI1_SCLK" , "GPCLK1" , }, { "SD0_CLK" , "SD14" , "DPI_D18" , "SD1_CLK" , "ARM_TRST" , 0 , }, { "SD0_CMD" , "SD15" , "DPI_D19" , "SD1_CMD" , "ARM_RTCK" , 0 , }, { "SD0_DAT0" , "SD16" , "DPI_D20" , "SD1_DAT0" , "ARM_TDO" , 0 , }, { "SD0_DAT1" , "SD17" , "DPI_D21" , "SD1_DAT1" , "ARM_TCK" , 0 , }, { "SD0_DAT2" , "TE0" , "DPI_D22" , "SD1_DAT2" , "ARM_TDI" , 0 , }, { "SD0_DAT3" , "TE1" , "DPI_D23" , "SD1_DAT3" , "ARM_TMS" , 0 , }, { "SDA0" , "SA5" , "PCM_CLK" , "FL0" , 0 , 0 , }, { "SCL0" , "SA4" , "PCM_FS" , "FL1" , 0 , 0 , }, { "TE0" , "SA3" , "PCM_DIN" , "CTS0" , 0 , "CTS1" , }, { "FL0" , "SA2" , "PCM_DOUT" , "RTS0" , 0 , "RTS1" , }, { "GPCLK0" , "SA1" , "RING_OCLK" , "TXD0" , 0 , "TXD1" , }, { "FL1" , "SA0" , "TE1" , "RXD0" , 0 , "RXD1" , }, { "GPCLK0" , "SOE_N_SE" , "TE2" , "SD1_CLK" , 0 , 0 , }, { "SPI0_CE1_N", "SWE_N_SRW_N", 0 , "SD1_CMD" , 0 , 0 , }, { "SPI0_CE0_N", "SD0" , "TXD0" , "SD1_DAT0" , 0 , 0 , }, { "SPI0_MISO" , "SD1" , "RXD0" , "SD1_DAT1" , 0 , 0 , }, { "SPI0_MOSI" , "SD2" , "RTS0" , "SD1_DAT2" , 0 , 0 , }, { "SPI0_SCLK" , "SD3" , "CTS0" , "SD1_DAT3" , 0 , 0 , }, { "PWM0" , "SD4" , 0 , "SD1_DAT4" , "SPI2_MISO" , "TXD1" , }, { "PWM1" , "SD5" , "TE0" , "SD1_DAT5" , "SPI2_MOSI" , "RXD1" , }, { "GPCLK1" , "SD6" , "TE1" , "SD1_DAT6" , "SPI2_SCLK" , "RTS1" , }, { "GPCLK2" , "SD7" , "TE2" , "SD1_DAT7" , "SPI2_CE0_N" , "CTS1" , }, { "GPCLK1" , "SDA0" , "SDA1" , "TE0" , "SPI2_CE1_N" , 0 , }, { "PWM1" , "SCL0" , "SCL1" , "TE1" , "SPI2_CE2_N" , 0 , }, { "SDA0" , "SDA1" , "SPI0_CE0_N", 0 , 0 , "SPI2_CE1_N", }, { "SCL0" , "SCL1" , "SPI0_MISO" , 0 , 0 , "SPI2_CE0_N", }, { "SD0_CLK" , "FL0" , "SPI0_MOSI" , "SD1_CLK" , "ARM_TRST" , "SPI2_SCLK" , }, { "SD0_CMD" , "GPCLK0" , "SPI0_SCLK" , "SD1_CMD" , "ARM_RTCK" , "SPI2_MOSI" , }, { "SD0_DAT0" , "GPCLK1" , "PCM_CLK" , "SD1_DAT0" , "ARM_TDO" , 0 , }, { "SD0_DAT1" , "GPCLK2" , "PCM_FS" , "SD1_DAT1" , "ARM_TCK" , 0 , }, { "SD0_DAT2" , "PWM0" , "PCM_DIN" , "SD1_DAT2" , "ARM_TDI" , 0 , }, { "SD0_DAT3" , "PWM1" , "PCM_DOUT" , "SD1_DAT3" , "ARM_TMS" , 0 , }, }; static const char *bcm2711_gpio_alt_names[BCM2711_NUM_GPIOS][BCM2711_ALT_COUNT] = { { "SDA0" , "SA5" , "PCLK" , "SPI3_CE0_N" , "TXD2" , "SDA6" , }, { "SCL0" , "SA4" , "DE" , "SPI3_MISO" , "RXD2" , "SCL6" , }, { "SDA1" , "SA3" , "LCD_VSYNC" , "SPI3_MOSI" , "CTS2" , "SDA3" , }, { "SCL1" , "SA2" , "LCD_HSYNC" , "SPI3_SCLK" , "RTS2" , "SCL3" , }, { "GPCLK0" , "SA1" , "DPI_D0" , "SPI4_CE0_N" , "TXD3" , "SDA3" , }, { "GPCLK1" , "SA0" , "DPI_D1" , "SPI4_MISO" , "RXD3" , "SCL3" , }, { "GPCLK2" , "SOE_N_SE" , "DPI_D2" , "SPI4_MOSI" , "CTS3" , "SDA4" , }, { "SPI0_CE1_N", "SWE_N_SRW_N", "DPI_D3" , "SPI4_SCLK" , "RTS3" , "SCL4" , }, { "SPI0_CE0_N", "SD0" , "DPI_D4" , "I2CSL_CE_N" , "TXD4" , "SDA4" , }, { "SPI0_MISO" , "SD1" , "DPI_D5" , "I2CSL_SDI_MISO", "RXD4" , "SCL4" , }, { "SPI0_MOSI" , "SD2" , "DPI_D6" , "I2CSL_SDA_MOSI", "CTS4" , "SDA5" , }, { "SPI0_SCLK" , "SD3" , "DPI_D7" , "I2CSL_SCL_SCLK", "RTS4" , "SCL5" , }, { "PWM0_0" , "SD4" , "DPI_D8" , "SPI5_CE0_N" , "TXD5" , "SDA5" , }, { "PWM0_1" , "SD5" , "DPI_D9" , "SPI5_MISO" , "RXD5" , "SCL5" , }, { "TXD0" , "SD6" , "DPI_D10" , "SPI5_MOSI" , "CTS5" , "TXD1" , }, { "RXD0" , "SD7" , "DPI_D11" , "SPI5_SCLK" , "RTS5" , "RXD1" , }, { 0 , "SD8" , "DPI_D12" , "CTS0" , "SPI1_CE2_N" , "CTS1" , }, { 0 , "SD9" , "DPI_D13" , "RTS0" , "SPI1_CE1_N" , "RTS1" , }, { "PCM_CLK" , "SD10" , "DPI_D14" , "SPI6_CE0_N" , "SPI1_CE0_N" , "PWM0_0" , }, { "PCM_FS" , "SD11" , "DPI_D15" , "SPI6_MISO" , "SPI1_MISO" , "PWM0_1" , }, { "PCM_DIN" , "SD12" , "DPI_D16" , "SPI6_MOSI" , "SPI1_MOSI" , "GPCLK0" , }, { "PCM_DOUT" , "SD13" , "DPI_D17" , "SPI6_SCLK" , "SPI1_SCLK" , "GPCLK1" , }, { "SD0_CLK" , "SD14" , "DPI_D18" , "SD1_CLK" , "ARM_TRST" , "SDA6" , }, { "SD0_CMD" , "SD15" , "DPI_D19" , "SD1_CMD" , "ARM_RTCK" , "SCL6" , }, { "SD0_DAT0" , "SD16" , "DPI_D20" , "SD1_DAT0" , "ARM_TDO" , "SPI3_CE1_N" , }, { "SD0_DAT1" , "SD17" , "DPI_D21" , "SD1_DAT1" , "ARM_TCK" , "SPI4_CE1_N" , }, { "SD0_DAT2" , 0 , "DPI_D22" , "SD1_DAT2" , "ARM_TDI" , "SPI5_CE1_N" , }, { "SD0_DAT3" , 0 , "DPI_D23" , "SD1_DAT3" , "ARM_TMS" , "SPI6_CE1_N" , }, { "SDA0" , "SA5" , "PCM_CLK" , 0 , "MII_A_RX_ERR" , "RGMII_MDIO" , }, { "SCL0" , "SA4" , "PCM_FS" , 0 , "MII_A_TX_ERR" , "RGMII_MDC" , }, { 0 , "SA3" , "PCM_DIN" , "CTS0" , "MII_A_CRS" , "CTS1" , }, { 0 , "SA2" , "PCM_DOUT" , "RTS0" , "MII_A_COL" , "RTS1" , }, { "GPCLK0" , "SA1" , 0 , "TXD0" , "SD_CARD_PRES" , "TXD1" , }, { 0 , "SA0" , 0 , "RXD0" , "SD_CARD_WRPROT" , "RXD1" , }, { "GPCLK0" , "SOE_N_SE" , 0 , "SD1_CLK" , "SD_CARD_LED" , "RGMII_IRQ" , }, { "SPI0_CE1_N", "SWE_N_SRW_N", 0 , "SD1_CMD" , "RGMII_START_STOP", 0 , }, { "SPI0_CE0_N", "SD0" , "TXD0" , "SD1_DAT0" , "RGMII_RX_OK" , "MII_A_RX_ERR", }, { "SPI0_MISO" , "SD1" , "RXD0" , "SD1_DAT1" , "RGMII_MDIO" , "MII_A_TX_ERR", }, { "SPI0_MOSI" , "SD2" , "RTS0" , "SD1_DAT2" , "RGMII_MDC" , "MII_A_CRS" , }, { "SPI0_SCLK" , "SD3" , "CTS0" , "SD1_DAT3" , "RGMII_IRQ" , "MII_A_COL" , }, { "PWM1_0" , "SD4" , 0 , "SD1_DAT4" , "SPI0_MISO" , "TXD1" , }, { "PWM1_1" , "SD5" , 0 , "SD1_DAT5" , "SPI0_MOSI" , "RXD1" , }, { "GPCLK1" , "SD6" , 0 , "SD1_DAT6" , "SPI0_SCLK" , "RTS1" , }, { "GPCLK2" , "SD7" , 0 , "SD1_DAT7" , "SPI0_CE0_N" , "CTS1" , }, { "GPCLK1" , "SDA0" , "SDA1" , 0 , "SPI0_CE1_N" , "SD_CARD_VOLT", }, { "PWM0_1" , "SCL0" , "SCL1" , 0 , "SPI0_CE2_N" , "SD_CARD_PWR0", }, { "SDA0" , "SDA1" , "SPI0_CE0_N", 0 , 0 , "SPI2_CE1_N" , }, { "SCL0" , "SCL1" , "SPI0_MISO" , 0 , 0 , "SPI2_CE0_N" , }, { "SD0_CLK" , 0 , "SPI0_MOSI" , "SD1_CLK" , "ARM_TRST" , "SPI2_SCLK" , }, { "SD0_CMD" , "GPCLK0" , "SPI0_SCLK" , "SD1_CMD" , "ARM_RTCK" , "SPI2_MOSI" , }, { "SD0_DAT0" , "GPCLK1" , "PCM_CLK" , "SD1_DAT0" , "ARM_TDO" , "SPI2_MISO" , }, { "SD0_DAT1" , "GPCLK2" , "PCM_FS" , "SD1_DAT1" , "ARM_TCK" , "SD_CARD_LED" , }, { "SD0_DAT2" , "PWM0_0" , "PCM_DIN" , "SD1_DAT2" , "ARM_TDI" , 0 , }, { "SD0_DAT3" , "PWM0_1" , "PCM_DOUT" , "SD1_DAT3" , "ARM_TMS" , 0 , }, }; static GPIO_FSEL_T bcm2835_gpio_get_fsel(void *priv, unsigned gpio) { volatile uint32_t *base = priv; /* GPFSEL0-5 with 10 sels per reg, 3 bits per sel (so bits 0:29 used) */ uint32_t reg = GPFSEL0 + (gpio / 10); uint32_t lsb = (gpio % 10) * 3; if (gpio < BCM2835_NUM_GPIOS) { switch ((base[reg] >> lsb) & 7) { case 0: return GPIO_FSEL_INPUT; case 1: return GPIO_FSEL_OUTPUT; case 2: return GPIO_FSEL_FUNC5; case 3: return GPIO_FSEL_FUNC4; case 4: return GPIO_FSEL_FUNC0; case 5: return GPIO_FSEL_FUNC1; case 6: return GPIO_FSEL_FUNC2; case 7: return GPIO_FSEL_FUNC3; } } return GPIO_FSEL_MAX; } static void bcm2835_gpio_set_fsel(void *priv, unsigned gpio, const GPIO_FSEL_T func) { volatile uint32_t *base = priv; /* GPFSEL0-5 with 10 sels per reg, 3 bits per sel (so bits 0:29 used) */ uint32_t reg = GPFSEL0 + (gpio / 10); uint32_t lsb = (gpio % 10) * 3; int fsel; switch (func) { case GPIO_FSEL_INPUT: fsel = 0; break; case GPIO_FSEL_OUTPUT: fsel = 1; break; case GPIO_FSEL_FUNC0: fsel = 4; break; case GPIO_FSEL_FUNC1: fsel = 5; break; case GPIO_FSEL_FUNC2: fsel = 6; break; case GPIO_FSEL_FUNC3: fsel = 7; break; case GPIO_FSEL_FUNC4: fsel = 3; break; case GPIO_FSEL_FUNC5: fsel = 2; break; default: return; } if (gpio < BCM2835_NUM_GPIOS) base[reg] = (base[reg] & ~(0x7 << lsb)) | (fsel << lsb); } static GPIO_DIR_T bcm2835_gpio_get_dir(void *priv, unsigned gpio) { GPIO_FSEL_T fsel = bcm2835_gpio_get_fsel(priv, gpio); if (fsel == GPIO_FSEL_INPUT) return DIR_INPUT; else if (fsel == GPIO_FSEL_OUTPUT) return DIR_OUTPUT; else return DIR_MAX; } static void bcm2835_gpio_set_dir(void *priv, unsigned gpio, GPIO_DIR_T dir) { GPIO_FSEL_T fsel; if (dir == DIR_INPUT) fsel = GPIO_FSEL_INPUT; else if (dir == DIR_OUTPUT) fsel = GPIO_FSEL_OUTPUT; else return; bcm2835_gpio_set_fsel(priv, gpio, fsel); } static int bcm2835_gpio_get_level(void *priv, unsigned gpio) { volatile uint32_t *base = priv; if (gpio >= BCM2835_NUM_GPIOS) return -1; return (base[GPLEV0 + (gpio / 32)] >> (gpio % 32)) & 1; } GPIO_DRIVE_T bcm2835_gpio_get_drive(void *priv, unsigned gpio) { /* This is a write-only mechanism */ UNUSED(priv); UNUSED(gpio); return DRIVE_MAX; } static void bcm2835_gpio_set_drive(void *priv, unsigned gpio, GPIO_DRIVE_T drv) { volatile uint32_t *base = priv; if (gpio < BCM2835_NUM_GPIOS && drv <= DRIVE_HIGH) base[(drv ? GPSET0 : GPCLR0) + (gpio / 32)] = (1 << (gpio % 32)); } static GPIO_PULL_T bcm2835_gpio_get_pull(void *priv, unsigned gpio) { /* This is a write-only mechanism */ UNUSED(priv); UNUSED(gpio); return PULL_MAX; } static void bcm2835_gpio_set_pull(void *priv, unsigned gpio, GPIO_PULL_T pull) { volatile uint32_t *base = priv; int clkreg = GPPUDCLK0 + (gpio / 32); int clkbit = 1 << (gpio % 32); if (gpio >= BCM2835_NUM_GPIOS || pull < PULL_NONE || pull > PULL_UP) return; base[GPPUD] = pull; usleep(10); base[clkreg] = clkbit; usleep(10); base[GPPUD] = 0; usleep(10); base[clkreg] = 0; usleep(10); } static const char *bcm2835_gpio_get_name(void *priv, unsigned gpio) { static char name_buf[16]; UNUSED(priv); sprintf(name_buf, "GPIO%d", gpio); return name_buf; } static const char *bcm2835_gpio_get_fsel_name(void *priv, unsigned gpio, GPIO_FSEL_T fsel) { const char *name = NULL; UNUSED(priv); switch (fsel) { case GPIO_FSEL_INPUT: name = "input"; break; case GPIO_FSEL_OUTPUT: name = "output"; break; case GPIO_FSEL_FUNC0: case GPIO_FSEL_FUNC1: case GPIO_FSEL_FUNC2: case GPIO_FSEL_FUNC3: case GPIO_FSEL_FUNC4: case GPIO_FSEL_FUNC5: if (gpio < BCM2835_NUM_GPIOS) { name = bcm2835_gpio_alt_names[gpio][fsel]; if (!name) name = "-"; } break; default: break; } return name; } static GPIO_PULL_T bcm2711_gpio_get_pull(void *priv, unsigned gpio) { volatile uint32_t *base = priv; int reg = GPPUPPDN0 + (gpio / 16); int lsb = (gpio % 16) * 2; if (gpio < BCM2711_NUM_GPIOS) { switch ((base[reg] >> lsb) & 3) { case 0: return PULL_NONE; case 1: return PULL_UP; case 2: return PULL_DOWN; } } return PULL_MAX; } static void bcm2711_gpio_set_pull(void *priv, unsigned gpio, GPIO_PULL_T pull) { volatile uint32_t *base = priv; int reg = GPPUPPDN0 + (gpio / 16); int lsb = (gpio % 16) * 2; int pull_val; if (gpio >= BCM2711_NUM_GPIOS) return; switch (pull) { case PULL_NONE: pull_val = 0; break; case PULL_UP: pull_val = 1; break; case PULL_DOWN: pull_val = 2; break; default: return; } base[reg] = (base[reg] & ~(3 << lsb)) | (pull_val << lsb); } static const char *bcm2711_gpio_get_fsel_name(void *priv, unsigned gpio, GPIO_FSEL_T fsel) { const char *name = NULL; UNUSED(priv); switch (fsel) { case GPIO_FSEL_INPUT: name = "input"; break; case GPIO_FSEL_OUTPUT: name = "output"; break; case GPIO_FSEL_FUNC0: case GPIO_FSEL_FUNC1: case GPIO_FSEL_FUNC2: case GPIO_FSEL_FUNC3: case GPIO_FSEL_FUNC4: case GPIO_FSEL_FUNC5: if (gpio < BCM2711_NUM_GPIOS) { name = bcm2711_gpio_alt_names[gpio][fsel]; if (!name) name = "-"; } break; default: break; } return name; } static void *bcm2835_gpio_create_instance(const GPIO_CHIP_T *chip, const char *dtnode) { UNUSED(dtnode); return (void *)chip; } static int bcm2835_gpio_count(void *priv) { UNUSED(priv); return BCM2835_NUM_GPIOS; } static void *bcm2835_gpio_probe_instance(void *priv, volatile uint32_t *base) { UNUSED(priv); return (void *)base; } static const GPIO_CHIP_INTERFACE_T bcm2835_gpio_interface = { .gpio_create_instance = bcm2835_gpio_create_instance, .gpio_count = bcm2835_gpio_count, .gpio_probe_instance = bcm2835_gpio_probe_instance, .gpio_get_fsel = bcm2835_gpio_get_fsel, .gpio_set_fsel = bcm2835_gpio_set_fsel, .gpio_set_drive = bcm2835_gpio_set_drive, .gpio_set_dir = bcm2835_gpio_set_dir, .gpio_get_dir = bcm2835_gpio_get_dir, .gpio_get_level = bcm2835_gpio_get_level, .gpio_get_drive = bcm2835_gpio_get_drive, .gpio_get_pull = bcm2835_gpio_get_pull, .gpio_set_pull = bcm2835_gpio_set_pull, .gpio_get_name = bcm2835_gpio_get_name, .gpio_get_fsel_name = bcm2835_gpio_get_fsel_name, }; DECLARE_GPIO_CHIP(bcm2835, "brcm,bcm2835-gpio", &bcm2835_gpio_interface, 0x30000, 0); static const GPIO_CHIP_INTERFACE_T bcm2711_gpio_interface = { .gpio_create_instance = bcm2835_gpio_create_instance, .gpio_count = bcm2835_gpio_count, .gpio_probe_instance = bcm2835_gpio_probe_instance, .gpio_get_fsel = bcm2835_gpio_get_fsel, .gpio_set_fsel = bcm2835_gpio_set_fsel, .gpio_set_drive = bcm2835_gpio_set_drive, .gpio_set_dir = bcm2835_gpio_set_dir, .gpio_get_dir = bcm2835_gpio_get_dir, .gpio_get_level = bcm2835_gpio_get_level, .gpio_get_drive = bcm2835_gpio_get_drive, .gpio_get_pull = bcm2711_gpio_get_pull, .gpio_set_pull = bcm2711_gpio_set_pull, .gpio_get_name = bcm2835_gpio_get_name, .gpio_get_fsel_name = bcm2711_gpio_get_fsel_name, }; DECLARE_GPIO_CHIP(bcm2711, "brcm,bcm2711-gpio", &bcm2711_gpio_interface, 0x30000, 0); raspi-utils-20240903/pinctrl/gpiochip_rp1.c000066400000000000000000000466751466562725600205030ustar00rootroot00000000000000#include #include #include #include #include "gpiochip.h" #include "util.h" #define RP1_NUM_GPIOS 54 #define RP1_IO_BANK0_OFFSET 0x00000000 #define RP1_IO_BANK1_OFFSET 0x00004000 #define RP1_IO_BANK2_OFFSET 0x00008000 #define RP1_SYS_RIO_BANK0_OFFSET 0x00010000 #define RP1_SYS_RIO_BANK1_OFFSET 0x00014000 #define RP1_SYS_RIO_BANK2_OFFSET 0x00018000 #define RP1_PADS_BANK0_OFFSET 0x00020000 #define RP1_PADS_BANK1_OFFSET 0x00024000 #define RP1_PADS_BANK2_OFFSET 0x00028000 #define RP1_RW_OFFSET 0x0000 #define RP1_XOR_OFFSET 0x1000 #define RP1_SET_OFFSET 0x2000 #define RP1_CLR_OFFSET 0x3000 #define RP1_GPIO_CTRL_FSEL_LSB 0 #define RP1_GPIO_CTRL_FSEL_MASK (0x1f << RP1_GPIO_CTRL_FSEL_LSB) #define RP1_GPIO_CTRL_OUTOVER_LSB 12 #define RP1_GPIO_CTRL_OUTOVER_MASK (0x03 << RP1_GPIO_CTRL_OUTOVER_LSB) #define RP1_GPIO_CTRL_OEOVER_LSB 14 #define RP1_GPIO_CTRL_OEOVER_MASK (0x03 << RP1_GPIO_CTRL_OEOVER_LSB) #define RP1_PADS_OD_SET (1 << 7) #define RP1_PADS_IE_SET (1 << 6) #define RP1_PADS_PUE_SET (1 << 3) #define RP1_PADS_PDE_SET (1 << 2) #define RP1_GPIO_IO_REG_STATUS_OFFSET(offset) (((offset * 2) + 0) * sizeof(uint32_t)) #define RP1_GPIO_IO_REG_CTRL_OFFSET(offset) (((offset * 2) + 1) * sizeof(uint32_t)) #define RP1_GPIO_PADS_REG_OFFSET(offset) (sizeof(uint32_t) + (offset * sizeof(uint32_t))) #define RP1_GPIO_SYS_RIO_REG_OUT_OFFSET 0x0 #define RP1_GPIO_SYS_RIO_REG_OE_OFFSET 0x4 #define RP1_GPIO_SYS_RIO_REG_SYNC_IN_OFFSET 0x8 #define rp1_gpio_write32(base, peri_offset, reg_offset, value) \ base[(peri_offset + reg_offset)/4] = value #define rp1_gpio_read32(base, peri_offset, reg_offset) \ base[(peri_offset + reg_offset)/4] typedef struct { uint32_t io[3]; uint32_t pads[3]; uint32_t sys_rio[3]; } GPIO_STATE_T; typedef enum { RP1_FSEL_ALT0 = 0x0, RP1_FSEL_ALT1 = 0x1, RP1_FSEL_ALT2 = 0x2, RP1_FSEL_ALT3 = 0x3, RP1_FSEL_ALT4 = 0x4, RP1_FSEL_ALT5 = 0x5, RP1_FSEL_ALT6 = 0x6, RP1_FSEL_ALT7 = 0x7, RP1_FSEL_ALT8 = 0x8, RP1_FSEL_COUNT, RP1_FSEL_SYS_RIO = RP1_FSEL_ALT5, RP1_FSEL_NULL = 0x1f } RP1_FSEL_T; static const GPIO_STATE_T gpio_state = { .io = {RP1_IO_BANK0_OFFSET, RP1_IO_BANK1_OFFSET, RP1_IO_BANK2_OFFSET}, .pads = {RP1_PADS_BANK0_OFFSET, RP1_PADS_BANK1_OFFSET, RP1_PADS_BANK2_OFFSET}, .sys_rio = {RP1_SYS_RIO_BANK0_OFFSET, RP1_SYS_RIO_BANK1_OFFSET, RP1_SYS_RIO_BANK2_OFFSET}, }; static const int rp1_bank_base[] = {0, 28, 34}; static const char *rp1_gpio_fsel_names[RP1_NUM_GPIOS][RP1_FSEL_COUNT] = { { "SPI0_SIO3" , "DPI_PCLK" , "TXD1" , "SDA0" , 0 , "SYS_RIO00" , "PROC_RIO00" , "PIO0" , "SPI2_CE0" , }, { "SPI0_SIO2" , "DPI_DE" , "RXD1" , "SCL0" , 0 , "SYS_RIO01" , "PROC_RIO01" , "PIO1" , "SPI2_SIO1", }, { "SPI0_CE3" , "DPI_VSYNC" , "CTS1" , "SDA1" , "IR_RX0" , "SYS_RIO02" , "PROC_RIO02" , "PIO2" , "SPI2_SIO0", }, { "SPI0_CE2" , "DPI_HSYNC" , "RTS1" , "SCL1" , "IR_TX0" , "SYS_RIO03" , "PROC_RIO03" , "PIO3" , "SPI2_SCLK", }, { "GPCLK0" , "DPI_D0" , "TXD2" , "SDA2" , "RI0" , "SYS_RIO04" , "PROC_RIO04" , "PIO4" , "SPI3_CE0" , }, { "GPCLK1" , "DPI_D1" , "RXD2" , "SCL2" , "DTR0" , "SYS_RIO05" , "PROC_RIO05" , "PIO5" , "SPI3_SIO1", }, { "GPCLK2" , "DPI_D2" , "CTS2" , "SDA3" , "DCD0" , "SYS_RIO06" , "PROC_RIO06" , "PIO6" , "SPI3_SIO0", }, { "SPI0_CE1" , "DPI_D3" , "RTS2" , "SCL3" , "DSR0" , "SYS_RIO07" , "PROC_RIO07" , "PIO7" , "SPI3_SCLK", }, { "SPI0_CE0" , "DPI_D4" , "TXD3" , "SDA0" , 0 , "SYS_RIO08" , "PROC_RIO08" , "PIO8" , "SPI4_CE0" , }, { "SPI0_MISO" , "DPI_D5" , "RXD3" , "SCL0" , 0 , "SYS_RIO09" , "PROC_RIO09" , "PIO9" , "SPI4_SIO0", }, { "SPI0_MOSI" , "DPI_D6" , "CTS3" , "SDA1" , 0 , "SYS_RIO010", "PROC_RIO010", "PIO10" , "SPI4_SIO1", }, { "SPI0_SCLK" , "DPI_D7" , "RTS3" , "SCL1" , 0 , "SYS_RIO011", "PROC_RIO011", "PIO11" , "SPI4_SCLK", }, { "PWM0_CHAN0", "DPI_D8" , "TXD4" , "SDA2" , "AAUD_LEFT" , "SYS_RIO012", "PROC_RIO012", "PIO12" , "SPI5_CE0" , }, { "PWM0_CHAN1", "DPI_D9" , "RXD4" , "SCL2" , "AAUD_RIGHT" , "SYS_RIO013", "PROC_RIO013", "PIO13" , "SPI5_SIO1", }, { "PWM0_CHAN2", "DPI_D10" , "CTS4" , "SDA3" , "TXD0" , "SYS_RIO014", "PROC_RIO014", "PIO14" , "SPI5_SIO0", }, { "PWM0_CHAN3", "DPI_D11" , "RTS4" , "SCL3" , "RXD0" , "SYS_RIO015", "PROC_RIO015", "PIO15" , "SPI5_SCLK", }, { "SPI1_CE2" , "DPI_D12" , "DSI0_TE_EXT" , 0 , "CTS0" , "SYS_RIO016", "PROC_RIO016", "PIO16" , }, { "SPI1_CE1" , "DPI_D13" , "DSI1_TE_EXT" , 0 , "RTS0" , "SYS_RIO017", "PROC_RIO017", "PIO17" , }, { "SPI1_CE0" , "DPI_D14" , "I2S0_SCLK" , "PWM0_CHAN2" , "I2S1_SCLK" , "SYS_RIO018", "PROC_RIO018", "PIO18" , "GPCLK1", }, { "SPI1_MISO" , "DPI_D15" , "I2S0_WS" , "PWM0_CHAN3" , "I2S1_WS" , "SYS_RIO019", "PROC_RIO019", "PIO19" , }, { "SPI1_MOSI" , "DPI_D16" , "I2S0_SDI0" , "GPCLK0" , "I2S1_SDI0" , "SYS_RIO020", "PROC_RIO020", "PIO20" , }, { "SPI1_SCLK" , "DPI_D17" , "I2S0_SDO0" , "GPCLK1" , "I2S1_SDO0" , "SYS_RIO021", "PROC_RIO021", "PIO21" , }, { "SD0_CLK" , "DPI_D18" , "I2S0_SDI1" , "SDA3" , "I2S1_SDI1" , "SYS_RIO022", "PROC_RIO022", "PIO22" , }, { "SD0_CMD" , "DPI_D19" , "I2S0_SDO1" , "SCL3" , "I2S1_SDO1" , "SYS_RIO023", "PROC_RIO023", "PIO23" , }, { "SD0_DAT0" , "DPI_D20" , "I2S0_SDI2" , 0 , "I2S1_SDI2" , "SYS_RIO024", "PROC_RIO024", "PIO24" , "SPI2_CE1" , }, { "SD0_DAT1" , "DPI_D21" , "I2S0_SDO2" , "MIC_CLK" , "I2S1_SDO2" , "SYS_RIO025", "PROC_RIO025", "PIO25" , "SPI3_CE1" , }, { "SD0_DAT2" , "DPI_D22" , "I2S0_SDI3" , "MIC_DAT0" , "I2S1_SDI3" , "SYS_RIO026", "PROC_RIO026", "PIO26" , "SPI5_CE1" , }, { "SD0_DAT3" , "DPI_D23" , "I2S0_SDO3" , "MIC_DAT1" , "I2S1_SDO3" , "SYS_RIO027", "PROC_RIO027", "PIO27" , "SPI1_CE1" , }, { "SD1_CLK" , "SDA4" , "I2S2_SCLK" , "SPI6_MISO" , "VBUS_EN0" , "SYS_RIO10" , "PROC_RIO10" , }, { "SD1_CMD" , "SCL4" , "I2S2_WS" , "SPI6_MOSI" , "VBUS_OC0" , "SYS_RIO11" , "PROC_RIO11" , }, { "SD1_DAT0" , "SDA5" , "I2S2_SDI0" , "SPI6_SCLK" , "TXD5" , "SYS_RIO12" , "PROC_RIO12" , }, { "SD1_DAT1" , "SCL5" , "I2S2_SDO0" , "SPI6_CE0" , "RXD5" , "SYS_RIO13" , "PROC_RIO13" , }, { "SD1_DAT2" , "GPCLK3" , "I2S2_SDI1" , "SPI6_CE1" , "CTS5" , "SYS_RIO14" , "PROC_RIO14" , }, { "SD1_DAT3" , "GPCLK4" , "I2S2_SDO1" , "SPI6_CE2" , "RTS5" , "SYS_RIO15" , "PROC_RIO15" , }, { "PWM1_CHAN2", "GPCLK3" , "VBUS_EN0" , "SDA4" , "MIC_CLK" , "SYS_RIO20" , "PROC_RIO20" , }, { "SPI8_CE1" , "PWM1_CHAN0" , "VBUS_OC0" , "SCL4" , "MIC_DAT0" , "SYS_RIO21" , "PROC_RIO21" , }, { "SPI8_CE0" , "TXD5" , "PCIE_CLKREQ_N", "SDA5" , "MIC_DAT1" , "SYS_RIO22" , "PROC_RIO22" , }, { "SPI8_MISO" , "RXD5" , "MIC_CLK" , "SCL5" , "PCIE_CLKREQ_N", "SYS_RIO23" , "PROC_RIO23" , }, { "SPI8_MOSI" , "RTS5" , "MIC_DAT0" , "SDA6" , "AAUD_LEFT" , "SYS_RIO24" , "PROC_RIO24" , "DSI0_TE_EXT", }, { "SPI8_SCLK" , "CTS5" , "MIC_DAT1" , "SCL6" , "AAUD_RIGHT" , "SYS_RIO25" , "PROC_RIO25" , "DSI1_TE_EXT", }, { "PWM1_CHAN1", "TXD5" , "SDA4" , "SPI6_MISO" , "AAUD_LEFT" , "SYS_RIO26" , "PROC_RIO26" , }, { "PWM1_CHAN2", "RXD5" , "SCL4" , "SPI6_MOSI" , "AAUD_RIGHT" , "SYS_RIO27" , "PROC_RIO27" , }, { "GPCLK5" , "RTS5" , "VBUS_EN1" , "SPI6_SCLK" , "I2S2_SCLK" , "SYS_RIO28" , "PROC_RIO28" , }, { "GPCLK4" , "CTS5" , "VBUS_OC1" , "SPI6_CE0" , "I2S2_WS" , "SYS_RIO29" , "PROC_RIO29" , }, { "GPCLK5" , "SDA5" , "PWM1_CHAN0" , "SPI6_CE1" , "I2S2_SDI0" , "SYS_RIO210", "PROC_RIO210", }, { "PWM1_CHAN3", "SCL5" , "SPI7_CE0" , "SPI6_CE2" , "I2S2_SDO0" , "SYS_RIO211", "PROC_RIO211", }, { "GPCLK3" , "SDA4" , "SPI7_MOSI" , "MIC_CLK" , "I2S2_SDI1" , "SYS_RIO212", "PROC_RIO212", "DSI0_TE_EXT", }, { "GPCLK5" , "SCL4" , "SPI7_MISO" , "MIC_DAT0" , "I2S2_SDO1" , "SYS_RIO213", "PROC_RIO213", "DSI1_TE_EXT", }, { "PWM1_CHAN0", "PCIE_CLKREQ_N", "SPI7_SCLK" , "MIC_DAT1" , "TXD5" , "SYS_RIO214", "PROC_RIO214", }, { "SPI8_SCLK" , "SPI7_SCLK" , "SDA5" , "AAUD_LEFT" , "RXD5" , "SYS_RIO215", "PROC_RIO215", }, { "SPI8_MISO" , "SPI7_MOSI" , "SCL5" , "AAUD_RIGHT" , "VBUS_EN2" , "SYS_RIO216", "PROC_RIO216", }, { "SPI8_MOSI" , "SPI7_MISO" , "SDA6" , "AAUD_LEFT" , "VBUS_OC2" , "SYS_RIO217", "PROC_RIO217", }, { "SPI8_CE0" , 0 , "SCL6" , "AAUD_RIGHT" , "VBUS_EN3" , "SYS_RIO218", "PROC_RIO218", }, { "SPI8_CE1" , "SPI7_CE0" , 0 , "PCIE_CLKREQ_N", "VBUS_OC3" , "SYS_RIO219", "PROC_RIO219", }, }; static void rp1_gpio_get_bank(int num, int *bank, int *offset) { *bank = *offset = 0; if (num >= RP1_NUM_GPIOS) { assert(0); return; } if (num < rp1_bank_base[1]) *bank = 0; else if (num < rp1_bank_base[2]) *bank = 1; else *bank = 2; *offset = num - rp1_bank_base[*bank]; } static uint32_t rp1_gpio_ctrl_read(volatile uint32_t *base, int bank, int offset) { return rp1_gpio_read32(base, gpio_state.io[bank], RP1_GPIO_IO_REG_CTRL_OFFSET(offset)); } static void rp1_gpio_ctrl_write(volatile uint32_t *base, int bank, int offset, uint32_t value) { rp1_gpio_write32(base, gpio_state.io[bank], RP1_GPIO_IO_REG_CTRL_OFFSET(offset), value); } static uint32_t rp1_gpio_pads_read(volatile uint32_t *base, int bank, int offset) { return rp1_gpio_read32(base, gpio_state.pads[bank], RP1_GPIO_PADS_REG_OFFSET(offset)); } static void rp1_gpio_pads_write(volatile uint32_t *base, int bank, int offset, uint32_t value) { rp1_gpio_write32(base, gpio_state.pads[bank], RP1_GPIO_PADS_REG_OFFSET(offset), value); } static uint32_t rp1_gpio_sys_rio_out_read(volatile uint32_t *base, int bank, int offset) { UNUSED(offset); return rp1_gpio_read32(base, gpio_state.sys_rio[bank], RP1_GPIO_SYS_RIO_REG_OUT_OFFSET); } static uint32_t rp1_gpio_sys_rio_sync_in_read(volatile uint32_t *base, int bank, int offset) { UNUSED(offset); return rp1_gpio_read32(base, gpio_state.sys_rio[bank], RP1_GPIO_SYS_RIO_REG_SYNC_IN_OFFSET); } static void rp1_gpio_sys_rio_out_set(volatile uint32_t *base, int bank, int offset) { rp1_gpio_write32(base, gpio_state.sys_rio[bank], RP1_GPIO_SYS_RIO_REG_OUT_OFFSET + RP1_SET_OFFSET, 1U << offset); } static void rp1_gpio_sys_rio_out_clr(volatile uint32_t *base, int bank, int offset) { rp1_gpio_write32(base, gpio_state.sys_rio[bank], RP1_GPIO_SYS_RIO_REG_OUT_OFFSET + RP1_CLR_OFFSET, 1U << offset); } static uint32_t rp1_gpio_sys_rio_oe_read(volatile uint32_t *base, int bank) { return rp1_gpio_read32(base, gpio_state.sys_rio[bank], RP1_GPIO_SYS_RIO_REG_OE_OFFSET); } static void rp1_gpio_sys_rio_oe_clr(volatile uint32_t *base, int bank, int offset) { rp1_gpio_write32(base, gpio_state.sys_rio[bank], RP1_GPIO_SYS_RIO_REG_OE_OFFSET + RP1_CLR_OFFSET, 1U << offset); } static void rp1_gpio_sys_rio_oe_set(volatile uint32_t *base, int bank, int offset) { rp1_gpio_write32(base, gpio_state.sys_rio[bank], RP1_GPIO_SYS_RIO_REG_OE_OFFSET + RP1_SET_OFFSET, 1U << offset); } static void rp1_gpio_set_dir(void *priv, uint32_t gpio, GPIO_DIR_T dir) { volatile uint32_t *base = priv; int bank, offset; rp1_gpio_get_bank(gpio, &bank, &offset); if (dir == DIR_INPUT) rp1_gpio_sys_rio_oe_clr(base, bank, offset); else if (dir == DIR_OUTPUT) rp1_gpio_sys_rio_oe_set(base, bank, offset); else assert(0); } static GPIO_DIR_T rp1_gpio_get_dir(void *priv, unsigned gpio) { volatile uint32_t *base = priv; int bank, offset; GPIO_DIR_T dir; uint32_t reg; rp1_gpio_get_bank(gpio, &bank, &offset); reg = rp1_gpio_sys_rio_oe_read(base, bank); dir = (reg & (1U << offset)) ? DIR_OUTPUT : DIR_INPUT; return dir; } static GPIO_FSEL_T rp1_gpio_get_fsel(void *priv, unsigned gpio) { volatile uint32_t *base = priv; int bank, offset; uint32_t reg; GPIO_FSEL_T fsel; RP1_FSEL_T rsel; rp1_gpio_get_bank(gpio, &bank, &offset); reg = rp1_gpio_ctrl_read(base, bank, offset); rsel = ((reg & RP1_GPIO_CTRL_FSEL_MASK) >> RP1_GPIO_CTRL_FSEL_LSB); if (rsel == RP1_FSEL_SYS_RIO) fsel = GPIO_FSEL_GPIO; else if (rsel == RP1_FSEL_NULL) fsel = GPIO_FSEL_NONE; else if (rsel < RP1_FSEL_COUNT) fsel = (GPIO_FSEL_T)rsel; else fsel = GPIO_FSEL_MAX; return fsel; } static void rp1_gpio_set_fsel(void *priv, unsigned gpio, const GPIO_FSEL_T func) { volatile uint32_t *base = priv; int bank, offset; uint32_t ctrl_reg; uint32_t pad_reg; uint32_t old_pad_reg; RP1_FSEL_T rsel; if (func < (GPIO_FSEL_T)RP1_FSEL_COUNT) rsel = (RP1_FSEL_T)func; else if (func == GPIO_FSEL_INPUT || func == GPIO_FSEL_OUTPUT || func == GPIO_FSEL_GPIO) rsel = RP1_FSEL_SYS_RIO; else if (func == GPIO_FSEL_NONE) rsel = RP1_FSEL_NULL; else return; rp1_gpio_get_bank(gpio, &bank, &offset); if (func == GPIO_FSEL_INPUT) rp1_gpio_set_dir(priv, gpio, DIR_INPUT); else if (func == GPIO_FSEL_OUTPUT) rp1_gpio_set_dir(priv, gpio, DIR_OUTPUT); ctrl_reg = rp1_gpio_ctrl_read(base, bank, offset) & ~RP1_GPIO_CTRL_FSEL_MASK; ctrl_reg |= rsel << RP1_GPIO_CTRL_FSEL_LSB; rp1_gpio_ctrl_write(base, bank, offset, ctrl_reg); pad_reg = rp1_gpio_pads_read(base, bank, offset); old_pad_reg = pad_reg; if (rsel == RP1_FSEL_NULL) { // Disable input pad_reg &= ~RP1_PADS_IE_SET; } else { // Enable input pad_reg |= RP1_PADS_IE_SET; } if (rsel != RP1_FSEL_NULL) { // Enable peripheral func output pad_reg &= ~RP1_PADS_OD_SET; } else { // Disable peripheral func output pad_reg |= RP1_PADS_OD_SET; } if (pad_reg != old_pad_reg) rp1_gpio_pads_write(base, bank, offset, pad_reg); } static int rp1_gpio_get_level(void *priv, unsigned gpio) { volatile uint32_t *base = priv; int bank, offset; uint32_t pad_reg; uint32_t reg; int level; rp1_gpio_get_bank(gpio, &bank, &offset); pad_reg = rp1_gpio_pads_read(base, bank, offset); if (!(pad_reg & RP1_PADS_IE_SET)) return -1; reg = rp1_gpio_sys_rio_sync_in_read(base, bank, offset); level = (reg & (1U << offset)) ? 1 : 0; return level; } static void rp1_gpio_set_drive(void *priv, unsigned gpio, GPIO_DRIVE_T drv) { volatile uint32_t *base = priv; int bank, offset; rp1_gpio_get_bank(gpio, &bank, &offset); if (drv == DRIVE_HIGH) rp1_gpio_sys_rio_out_set(base, bank, offset); else if (drv == DRIVE_LOW) rp1_gpio_sys_rio_out_clr(base, bank, offset); } static void rp1_gpio_set_pull(void *priv, unsigned gpio, GPIO_PULL_T pull) { volatile uint32_t *base = priv; uint32_t reg; int bank, offset; rp1_gpio_get_bank(gpio, &bank, &offset); reg = rp1_gpio_pads_read(base, bank, offset); reg &= ~(RP1_PADS_PDE_SET | RP1_PADS_PUE_SET); if (pull == PULL_UP) reg |= RP1_PADS_PUE_SET; else if (pull == PULL_DOWN) reg |= RP1_PADS_PDE_SET; rp1_gpio_pads_write(base, bank, offset, reg); } static GPIO_PULL_T rp1_gpio_get_pull(void *priv, unsigned gpio) { volatile uint32_t *base = priv; uint32_t reg; GPIO_PULL_T pull = PULL_NONE; int bank, offset; rp1_gpio_get_bank(gpio, &bank, &offset); reg = rp1_gpio_pads_read(base, bank, offset); if (reg & RP1_PADS_PUE_SET) pull = PULL_UP; else if (reg & RP1_PADS_PDE_SET) pull = PULL_DOWN; return pull; } static GPIO_DRIVE_T rp1_gpio_get_drive(void *priv, unsigned gpio) { volatile uint32_t *base = priv; uint32_t reg; int bank, offset; rp1_gpio_get_bank(gpio, &bank, &offset); reg = rp1_gpio_sys_rio_out_read(base, bank, offset); return (reg & (1U << offset)) ? DRIVE_HIGH : DRIVE_LOW; } static const char *rp1_gpio_get_name(void *priv, unsigned gpio) { static char name_buf[16]; UNUSED(priv); if (gpio >= RP1_NUM_GPIOS) return NULL; sprintf(name_buf, "GPIO%d", gpio); return name_buf; } static const char *rp1_gpio_get_fsel_name(void *priv, unsigned gpio, GPIO_FSEL_T fsel) { const char *name = NULL; UNUSED(priv); switch (fsel) { case GPIO_FSEL_GPIO: name = "gpio"; break; case GPIO_FSEL_INPUT: name = "input"; break; case GPIO_FSEL_OUTPUT: name = "output"; break; case GPIO_FSEL_NONE: name = "none"; break; case GPIO_FSEL_FUNC0: case GPIO_FSEL_FUNC1: case GPIO_FSEL_FUNC2: case GPIO_FSEL_FUNC3: case GPIO_FSEL_FUNC4: case GPIO_FSEL_FUNC5: case GPIO_FSEL_FUNC6: case GPIO_FSEL_FUNC7: case GPIO_FSEL_FUNC8: if (gpio < RP1_NUM_GPIOS) { name = rp1_gpio_fsel_names[gpio][fsel - GPIO_FSEL_FUNC0]; if (!name) name = "-"; } break; default: return NULL; } return name; } static void *rp1_gpio_create_instance(const GPIO_CHIP_T *chip, const char *dtnode) { UNUSED(dtnode); return (void *)chip; } static int rp1_gpio_count(void *priv) { UNUSED(priv); return RP1_NUM_GPIOS; } static void *rp1_gpio_probe_instance(void *priv, volatile uint32_t *base) { UNUSED(priv); return (void *)base; } static const GPIO_CHIP_INTERFACE_T rp1_gpio_interface = { .gpio_create_instance = rp1_gpio_create_instance, .gpio_count = rp1_gpio_count, .gpio_probe_instance = rp1_gpio_probe_instance, .gpio_get_fsel = rp1_gpio_get_fsel, .gpio_set_fsel = rp1_gpio_set_fsel, .gpio_set_drive = rp1_gpio_set_drive, .gpio_set_dir = rp1_gpio_set_dir, .gpio_get_dir = rp1_gpio_get_dir, .gpio_get_level = rp1_gpio_get_level, .gpio_get_drive = rp1_gpio_get_drive, .gpio_get_pull = rp1_gpio_get_pull, .gpio_set_pull = rp1_gpio_set_pull, .gpio_get_name = rp1_gpio_get_name, .gpio_get_fsel_name = rp1_gpio_get_fsel_name, }; DECLARE_GPIO_CHIP(rp1, "raspberrypi,rp1-gpio", &rp1_gpio_interface, 0x30000, 0); raspi-utils-20240903/pinctrl/gpiolib.c000066400000000000000000000442161466562725600175310ustar00rootroot00000000000000#define _FILE_OFFSET_BITS 64 #include #include #include #include #include #include #include #include #include #include "gpiochip.h" #include "util.h" #define ARRAY_SIZE(_a) (sizeof(_a)/sizeof(_a[0])) #define MAX_GPIO_CHIPS 8 typedef struct GPIO_CHIP_INSTANCE_ { const GPIO_CHIP_T *chip; const char *name; const char *dtnode; int mem_fd; void *priv; uint64_t phys_addr; unsigned num_gpios; uint32_t base; } GPIO_CHIP_INSTANCE_T; static unsigned num_gpio_chips; static GPIO_CHIP_INSTANCE_T gpio_chips[MAX_GPIO_CHIPS]; static unsigned num_gpios; static unsigned first_hdr_pin = GPIO_INVALID; static unsigned last_hdr_pin = GPIO_INVALID; static const char *gpio_names[MAX_GPIO_PINS]; static unsigned hdr_gpios[NUM_HDR_PINS + 1]; const char *pull_names[] = { "pn", "pd", "pu", "--" }; const char *drive_names[] = { "dl", "dh", "--" }; const char *fsel_names[] = { "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "??", "??", "??", "??", "??", "??", "??", "ip", "op", "gp", "no" }; void (*verbose_callback)(const char *); static GPIO_CHIP_INSTANCE_T *gpio_create_instance(const GPIO_CHIP_T *chip, uint64_t phys_addr, const char *name, const char *dtnode) { GPIO_CHIP_INSTANCE_T *inst = &gpio_chips[num_gpio_chips]; if (num_gpio_chips >= MAX_GPIO_CHIPS) { assert(0); return NULL; } inst->chip = chip; inst->name = name ? name : chip->name; inst->dtnode = dtnode; inst->phys_addr = phys_addr; inst->priv = NULL; inst->base = 0; inst->priv = chip->interface->gpio_create_instance(chip, dtnode); if (!inst->priv) return NULL; num_gpio_chips++; return inst; } static int gpio_get_interface(unsigned gpio, const GPIO_CHIP_INTERFACE_T **iface_ptr, void **priv, unsigned *offset) { unsigned i; *iface_ptr = NULL; for (i = 0; i < num_gpio_chips; i++) { GPIO_CHIP_INSTANCE_T *inst = &gpio_chips[i]; const GPIO_CHIP_T *chip = inst->chip; if (gpio >= inst->base && gpio < (inst->base + inst->num_gpios)) { *iface_ptr = chip->interface; *priv = inst->priv; *offset = gpio - inst->base; return 0; } } return -1; } int gpio_num_is_valid(unsigned gpio) { return gpio < MAX_GPIO_PINS && !!gpio_names[gpio]; } GPIO_DIR_T gpio_get_dir(unsigned gpio) { const GPIO_CHIP_INTERFACE_T *iface = NULL; unsigned gpio_offset; void *priv; if (gpio_get_interface(gpio, &iface, &priv, &gpio_offset) == 0) return iface->gpio_get_dir(priv, gpio_offset); return DIR_MAX; } void gpio_set_dir(unsigned gpio, GPIO_DIR_T dir) { const GPIO_CHIP_INTERFACE_T *iface = NULL; unsigned gpio_offset; void *priv; if (gpio_get_interface(gpio, &iface, &priv, &gpio_offset) == 0) iface->gpio_set_dir(priv, gpio_offset, dir); } GPIO_FSEL_T gpio_get_fsel(unsigned gpio) { const GPIO_CHIP_INTERFACE_T *iface = NULL; GPIO_FSEL_T fsel = GPIO_FSEL_MAX; unsigned gpio_offset; void *priv; if (gpio_get_interface(gpio, &iface, &priv, &gpio_offset) == 0) fsel = iface->gpio_get_fsel(priv, gpio_offset); if (fsel == GPIO_FSEL_GPIO) { if (gpio_get_dir(gpio) == DIR_OUTPUT) fsel = GPIO_FSEL_OUTPUT; else fsel = GPIO_FSEL_INPUT; } return fsel; } void gpio_set_fsel(unsigned gpio, const GPIO_FSEL_T func) { const GPIO_CHIP_INTERFACE_T *iface = NULL; unsigned gpio_offset; void *priv; if (gpio_get_interface(gpio, &iface, &priv, &gpio_offset) == 0) iface->gpio_set_fsel(priv, gpio_offset, func); } void gpio_set_drive(unsigned gpio, GPIO_DRIVE_T drv) { const GPIO_CHIP_INTERFACE_T *iface = NULL; unsigned gpio_offset; void *priv; if (gpio_get_interface(gpio, &iface, &priv, &gpio_offset) == 0) iface->gpio_set_drive(priv, gpio_offset, drv); } void gpio_set(unsigned gpio) { const GPIO_CHIP_INTERFACE_T *iface = NULL; unsigned gpio_offset; void *priv; if (gpio_get_interface(gpio, &iface, &priv, &gpio_offset) == 0) { iface->gpio_set_drive(priv, gpio_offset, 1); iface->gpio_set_dir(priv, gpio_offset, DIR_OUTPUT); } } void gpio_clear(unsigned gpio) { const GPIO_CHIP_INTERFACE_T *iface = NULL; unsigned gpio_offset; void *priv; if (gpio_get_interface(gpio, &iface, &priv, &gpio_offset) == 0) { iface->gpio_set_drive(priv, gpio_offset, 0); iface->gpio_set_dir(priv, gpio_offset, DIR_OUTPUT); } } int gpio_get_level(unsigned gpio) { const GPIO_CHIP_INTERFACE_T *iface = NULL; unsigned gpio_offset; void *priv; if (gpio_get_interface(gpio, &iface, &priv, &gpio_offset) == 0) return iface->gpio_get_level(priv, gpio_offset); return 0; } GPIO_DRIVE_T gpio_get_drive(unsigned gpio) { const GPIO_CHIP_INTERFACE_T *iface = NULL; unsigned gpio_offset; void *priv; if (gpio_get_interface(gpio, &iface, &priv, &gpio_offset) == 0) return iface->gpio_get_drive(priv, gpio_offset); return DRIVE_MAX; } GPIO_PULL_T gpio_get_pull(unsigned gpio) { const GPIO_CHIP_INTERFACE_T *iface = NULL; unsigned gpio_offset; void *priv; if (gpio_get_interface(gpio, &iface, &priv, &gpio_offset) == 0) return iface->gpio_get_pull(priv, gpio_offset); return PULL_MAX; } void gpio_set_pull(unsigned gpio, GPIO_PULL_T pull) { const GPIO_CHIP_INTERFACE_T *iface = NULL; unsigned gpio_offset; void *priv; if (gpio_get_interface(gpio, &iface, &priv, &gpio_offset) == 0) iface->gpio_set_pull(priv, gpio_offset, pull); } void gpio_get_pin_range(unsigned *first, unsigned *last) { if (first_hdr_pin == GPIO_INVALID) { unsigned i; for (i = 0; i < num_gpio_chips; i++) { if (!strncmp(gpio_chips[i].name, "bcm2", 4) || !strcmp(gpio_chips[i].name, "rp1")) { // Assume it's the standard RPi 40-pin header layout uint32_t base = gpio_chips[i].base; hdr_gpios[3] = base + 2; hdr_gpios[5] = base + 3; hdr_gpios[7] = base + 4; hdr_gpios[8] = base + 14; hdr_gpios[10] = base + 15; hdr_gpios[11] = base + 17; hdr_gpios[12] = base + 18; hdr_gpios[13] = base + 27; hdr_gpios[15] = base + 22; hdr_gpios[16] = base + 23; hdr_gpios[18] = base + 24; hdr_gpios[19] = base + 10; hdr_gpios[21] = base + 9; hdr_gpios[18] = base + 24; hdr_gpios[22] = base + 25; hdr_gpios[23] = base + 11; hdr_gpios[24] = base + 8; hdr_gpios[26] = base + 7; hdr_gpios[27] = base + 0; hdr_gpios[28] = base + 1; hdr_gpios[29] = base + 5; hdr_gpios[31] = base + 6; hdr_gpios[32] = base + 12; hdr_gpios[33] = base + 13; hdr_gpios[35] = base + 19; hdr_gpios[36] = base + 16; hdr_gpios[37] = base + 26; hdr_gpios[38] = base + 20; hdr_gpios[40] = base + 21; first_hdr_pin = 1; last_hdr_pin = 40; break; } } } if (first) *first = first_hdr_pin; if (last) *last = last_hdr_pin; } unsigned gpio_for_pin(int pin) { if (pin >= 1 && pin <= NUM_HDR_PINS) return hdr_gpios[pin]; return GPIO_INVALID; } int gpio_to_pin(unsigned gpio) { int i; for (i = 1; i <= NUM_HDR_PINS; i++) { if (hdr_gpios[i] == gpio) return i; } return -1; } unsigned gpio_get_gpio_by_name(const char *name, int name_len) { unsigned gpio; if (!name_len) name_len = strlen(name); for (gpio = 0; gpio < num_gpios; gpio++) { const char *gpio_name = gpio_names[gpio]; const char *p; if (!gpio_name) continue; for (p = gpio_name; *p; ) { int len = strcspn(p, "/"); if (len == name_len && memcmp(name, p, name_len) == 0) return gpio; p += len; if (*p == '/') p++; } } return GPIO_INVALID; } const char *gpio_get_name(unsigned gpio) { if (gpio < num_gpios) return gpio_names[gpio]; switch (gpio) { case GPIO_INVALID: return "-"; case GPIO_GND: return "gnd"; case GPIO_5V: return "5v"; case GPIO_3V3: return "3v3"; case GPIO_1V8: return "1v8"; case GPIO_OTHER: default: return "???"; } } const char *gpio_get_gpio_fsel_name(unsigned gpio, GPIO_FSEL_T fsel) { const GPIO_CHIP_INTERFACE_T *iface = NULL; unsigned gpio_offset; void *priv; if (gpio_get_interface(gpio, &iface, &priv, &gpio_offset) == 0) return iface->gpio_get_fsel_name(priv, gpio_offset, fsel); return NULL; } const char *gpio_get_fsel_name(GPIO_FSEL_T fsel) { if ((unsigned)fsel < ARRAY_SIZE(fsel_names)) return fsel_names[fsel]; return NULL; } const char *gpio_get_pull_name(GPIO_PULL_T pull) { if ((unsigned)pull < ARRAY_SIZE(pull_names)) return pull_names[pull]; return NULL; } const char *gpio_get_drive_name(GPIO_DRIVE_T drive) { if ((unsigned)drive < ARRAY_SIZE(drive_names)) return drive_names[drive]; return NULL; } static const GPIO_CHIP_T *gpio_find_chip(const char *name) { const GPIO_CHIP_T *chip; for (chip = &__start_gpiochips; name && chip < &__stop_gpiochips; chip++) { if (!strcmp(name, chip->name) || !strcmp(name, chip->compatible)) return chip; } return NULL; } int gpiolib_init(void) { const GPIO_CHIP_T *chip; GPIO_CHIP_INSTANCE_T *inst; char pathbuf[FILENAME_MAX]; char gpiomem_idx[4]; const char *dtpath = "/proc/device-tree"; const char *p; char *alias = NULL, *names, *end, *compatible; uint64_t phys_addr; size_t names_len; unsigned gpio_base; unsigned pin, i; for (pin = 0; pin <= NUM_HDR_PINS; pin++) hdr_gpios[pin] = GPIO_INVALID; // There is currently only one header layout hdr_gpios[1] = GPIO_3V3; hdr_gpios[17] = GPIO_3V3; hdr_gpios[2] = GPIO_5V; hdr_gpios[4] = GPIO_5V; hdr_gpios[6] = GPIO_GND; hdr_gpios[9] = GPIO_GND; hdr_gpios[14] = GPIO_GND; hdr_gpios[20] = GPIO_GND; hdr_gpios[25] = GPIO_GND; hdr_gpios[30] = GPIO_GND; hdr_gpios[34] = GPIO_GND; hdr_gpios[39] = GPIO_GND; if (verbose_callback) (*verbose_callback)("GPIO chips:\n"); dt_set_path(dtpath); for (i = 0; ; i++) { sprintf(pathbuf, "gpio%d", i); sprintf(gpiomem_idx, "%d", i); alias = dt_read_prop("/aliases", pathbuf, NULL); if (!alias && i == 0) { alias = dt_read_prop("/aliases", "gpio", NULL); gpiomem_idx[0] = 0; } if (!alias) break; compatible = dt_read_prop(alias, "compatible", NULL); if (!compatible) { sprintf(pathbuf, "%s/..", alias); compatible = dt_read_prop(pathbuf, "compatible", NULL); } chip = gpio_find_chip(compatible); dt_free(compatible); if (!chip) { // Skip the unknown gpio chip dt_free(alias); continue; } phys_addr = dt_parse_addr(alias); if (phys_addr == INVALID_ADDRESS) { dt_free(alias); return -1; } inst = gpio_create_instance(chip, phys_addr, NULL, alias); if (!inst) { dt_free(alias); return -1; } sprintf(pathbuf, "/dev/gpiomem%s", gpiomem_idx); inst->mem_fd = open(pathbuf, O_RDWR|O_SYNC); } gpio_base = 0; num_gpios = 0; for (i = 0; i < num_gpio_chips; i++) { unsigned gpio; inst = &gpio_chips[i]; inst->base = gpio_base; chip = inst->chip; inst->num_gpios = chip->interface->gpio_count(inst->priv); if (verbose_callback) { char msg_buf[100]; snprintf(msg_buf, sizeof(msg_buf), " %" PRIx64 ": %s (%d gpios)\n", inst->phys_addr, chip->name, inst->num_gpios); (*verbose_callback)(msg_buf); } if (!inst->num_gpios) continue; num_gpios = gpio_base + inst->num_gpios; gpio_base = ROUND_UP(num_gpios, 100); if (num_gpios > MAX_GPIO_PINS) return -1; names = dt_read_prop(inst->dtnode, "gpio-line-names", &names_len); end = names + names_len; for (gpio = 0, p = names; gpio < inst->num_gpios; gpio++) { static const char *names[] = { "ID_SD", "ID_SC" }; static const int pins[] = { 27, 28 }; char name_buf[32]; const char *name = "-", *arch_name; if (p && p < end) { name = p; p += strlen(p) + 1; if (sscanf(name, "PIN%u", &pin) != 1 || pin < 1 || pin > NUM_HDR_PINS) { unsigned i; pin = 0; for (i = 0; i < ARRAY_SIZE(names); i++) { if (strcmp(name, names[i]) == 0) { pin = pins[i]; break; } } } if (pin >= 1) { hdr_gpios[pin] = inst->base + gpio; if ((pin < first_hdr_pin) || (first_hdr_pin == GPIO_INVALID)) first_hdr_pin = pin; if ((pin > last_hdr_pin) || (last_hdr_pin == GPIO_INVALID)) last_hdr_pin = pin; } } arch_name = chip->interface->gpio_get_name(inst->priv, gpio); if (!name[0] || !arch_name) { gpio_names[inst->base + gpio] = NULL; continue; } if (strcmp(name, arch_name) != 0) { if (strcmp(name, "-") == 0) { name = arch_name; } else { if (snprintf(name_buf, sizeof(name_buf), "%s/%s", name, arch_name) >= (int)sizeof(name_buf)) name_buf[sizeof(name_buf) - 1] = '\0'; name = name_buf; } } gpio_names[inst->base + gpio] = strdup(name); } dt_free(names); } // On a board with PINs, show pins 1-40 if (first_hdr_pin == 3) first_hdr_pin = 1; return (int)num_gpios; } int gpiolib_init_by_name(const char *name) { const GPIO_CHIP_T *chip; GPIO_CHIP_INSTANCE_T *inst; unsigned gpio; int pin; for (pin = 0; pin <= NUM_HDR_PINS; pin++) hdr_gpios[pin] = GPIO_INVALID; if (verbose_callback) (*verbose_callback)("GPIO chips:\n"); chip = gpio_find_chip(name); if (!chip) return 0; inst = gpio_create_instance(chip, 0, NULL, NULL); if (!inst) return -1; inst->num_gpios = chip->interface->gpio_count(inst->priv); num_gpios = inst->num_gpios; for (gpio = 0; gpio < inst->num_gpios; gpio++) { const char *name = chip->interface->gpio_get_name(inst->priv, gpio); if (!name) { gpio_names[inst->base + gpio] = NULL; continue; } gpio_names[gpio] = strdup(name); } if (inst->num_gpios && verbose_callback) { char msg_buf[100]; snprintf(msg_buf, sizeof(msg_buf), " %s (%d gpios)\n", chip->name, inst->num_gpios); (*verbose_callback)(msg_buf); } return (int)num_gpios; } int gpiolib_mmap(void) { int pagesize = getpagesize(); int mem_fd = -1; unsigned i; for (i = 0; i < num_gpio_chips; i++) { GPIO_CHIP_INSTANCE_T *inst; const GPIO_CHIP_T *chip; void *gpio_map; void *new_priv; unsigned align; inst = &gpio_chips[i]; chip = inst->chip; align = inst->phys_addr & (pagesize - 1); if (inst->mem_fd >= 0) { gpio_map = mmap( NULL, /* Any address in our space will do */ chip->size + align, /* Map length */ PROT_READ | PROT_WRITE, /* Enable reading & writing */ MAP_SHARED, /* Shared with other processes */ inst->mem_fd, /* File to map */ 0 /* Offset to GPIO peripheral */ ); } else { if (mem_fd < 0) { mem_fd = open("/dev/mem", O_RDWR|O_SYNC); if (mem_fd < 0) return errno; } gpio_map = mmap( NULL, /* Any address in our space will do */ chip->size + align, /* Map length */ PROT_READ | PROT_WRITE, /* Enable reading & writing */ MAP_SHARED, /* Shared with other processes */ mem_fd, /* File to map */ inst->phys_addr - align /* Offset to GPIO peripheral */ ); } if (gpio_map == MAP_FAILED) return errno; new_priv = chip->interface->gpio_probe_instance(inst->priv, (void *)((char *)gpio_map + align)); if (!new_priv) return -1; inst->priv = new_priv; } return 0; } void gpiolib_set_verbose(void (*callback)(const char *)) { verbose_callback = callback; } raspi-utils-20240903/pinctrl/gpiolib.h000066400000000000000000000040751466562725600175350ustar00rootroot00000000000000#ifndef GPIOLIB_H #define GPIOLIB_H #include #define NUM_HDR_PINS 40 #define MAX_GPIO_PINS 300 #define GPIO_INVALID (~0U) #define GPIO_GND (~1U) #define GPIO_5V (~2U) #define GPIO_3V3 (~3U) #define GPIO_1V8 (~4U) #define GPIO_OTHER (~5U) typedef enum { GPIO_FSEL_FUNC0, GPIO_FSEL_FUNC1, GPIO_FSEL_FUNC2, GPIO_FSEL_FUNC3, GPIO_FSEL_FUNC4, GPIO_FSEL_FUNC5, GPIO_FSEL_FUNC6, GPIO_FSEL_FUNC7, GPIO_FSEL_FUNC8, /* ... */ GPIO_FSEL_INPUT = 0x10, GPIO_FSEL_OUTPUT, GPIO_FSEL_GPIO, /* Preserves direction if possible, else input */ GPIO_FSEL_NONE, /* If possible, else input */ GPIO_FSEL_MAX } GPIO_FSEL_T; typedef enum { PULL_NONE, PULL_DOWN, PULL_UP, PULL_MAX } GPIO_PULL_T; typedef enum { DIR_INPUT, DIR_OUTPUT, DIR_MAX, } GPIO_DIR_T; typedef enum { DRIVE_LOW, DRIVE_HIGH, DRIVE_MAX } GPIO_DRIVE_T; int gpiolib_init(void); int gpiolib_init_by_name(const char *name); int gpiolib_mmap(void); void gpiolib_set_verbose(void (*callback)(const char *)); int gpio_num_is_valid(unsigned gpio); GPIO_DIR_T gpio_get_dir(unsigned gpio); void gpio_set_dir(unsigned gpio, GPIO_DIR_T dir); GPIO_FSEL_T gpio_get_fsel(unsigned gpio); void gpio_set_fsel(unsigned gpio, const GPIO_FSEL_T func); void gpio_set_drive(unsigned gpio, GPIO_DRIVE_T drv); void gpio_set(unsigned gpio); void gpio_clear(unsigned gpio); int gpio_get_level(unsigned gpio); /* The actual level observed */ GPIO_DRIVE_T gpio_get_drive(unsigned gpio); /* What it is being driven as */ GPIO_PULL_T gpio_get_pull(unsigned gpio); void gpio_set_pull(unsigned gpio, GPIO_PULL_T pull); void gpio_get_pin_range(unsigned *first, unsigned *last); unsigned gpio_for_pin(int pin); int gpio_to_pin(unsigned gpio); unsigned gpio_get_gpio_by_name(const char *name, int namelen); const char *gpio_get_name(unsigned gpio); const char *gpio_get_gpio_fsel_name(unsigned gpio, GPIO_FSEL_T fsel); const char *gpio_get_fsel_name(GPIO_FSEL_T fsel); const char *gpio_get_pull_name(GPIO_PULL_T pull); const char *gpio_get_drive_name(GPIO_DRIVE_T drive); #endif raspi-utils-20240903/pinctrl/pinctrl-completion.bash000066400000000000000000000052731466562725600224210ustar00rootroot00000000000000_pinctrl () { local cur prev words cword split cmd pins pincomp ALLPINS CHIPS chip pinmode=false prefix arg i func pull; local opts="no op ip a0 a1 a2 a3 a4 a5 a6 a7 a8 dh dl pu pd pn "; _init_completion -s || return; i=1 while [[ $i -lt $cword && ${COMP_WORDS[$i]} =~ ^- ]]; do arg=${COMP_WORDS[$i]} if [[ "$arg" == "-c" ]]; then chip=${COMP_WORDS[$((i + 1))]} i=$((i + 2)) else if [[ "$arg" == "-p" ]]; then pinmode=true fi i=$((i + 1)) fi done if [[ $i -lt $cword && ${COMP_WORDS[$i]} =~ ^(get|set|funcs|poll|help) ]]; then cmd=${COMP_WORDS[$i]} i=$((i + 1)) elif [[ ${COMP_WORDS[$i]} =~ ^[A-Z0-9] ]]; then cmd="set" fi if [[ "$cmd" != "" && $i -lt $cword ]]; then pins=${COMP_WORDS[$i]}; i=$((i + 1)) while [[ $i -lt $cword && ${COMP_WORDS[$i]} =~ ^[-,] ]]; do pins="$pins${COMP_WORDS[$i]}" i=$((i + 1)) done fi if [[ "$pins" != "" ]]; then while [[ $i -lt $cword ]]; do arg=${COMP_WORDS[$i]} if [[ $arg =~ ^(no|ip|op|a[0-8])$ ]]; then func=$arg opts=$(echo "$opts" | sed -E "s/(no|ip|op|a[0-8]) //g") if [[ $func != "op" ]]; then opts=$(echo "$opts" | sed -E "s/(dh|dl) //g") fi elif [[ $arg =~ ^(pu|pd|pn)$ ]]; then pull=$arg opts=$(echo "$opts" | sed -E "s/(pu|pd|pn) //g") fi i=$((i + 1)) done fi if [[ "$cmd" != "" && ( "$pins" == "" || "${cur}" =~ [-,] ) ]]; then prefix=$(echo $cur | sed 's/[^-,]*$//') if $pinmode; then ALLPINS={1..40} else ALLPINS=($(pinctrl get | cut -d'/' -f4 | cut -d' ' -f1) $(pinctrl get | cut -d'/' -f3 | cut -d' ' -f2)) fi pincomp="${ALLPINS[@]/#/$prefix}" COMPREPLY+=($(compgen -W "$pincomp" -- $cur)) elif [[ "$cmd" != "" ]]; then if [[ "$cmd" == "set" ]]; then COMPREPLY+=($(compgen -W "$opts" -- $cur)) fi else if [[ "$prev" == "-c" ]]; then CHIPS=($(pinctrl -v -p 0 | grep 'gpios)' | cut -d' ' -f4 | sort | uniq)) chips="${CHIPS[@]}" COMPREPLY+=($(compgen -W "$chips" -- $cur)) elif [[ "$cur" =~ ^- ]]; then COMPREPLY+=($(compgen -W "-p -h -v -c" -- $cur)) elif [[ "$chip" == "" ]]; then COMPREPLY+=($(compgen -W "get set poll funcs help" -- $cur)) else COMPREPLY+=($(compgen -W "funcs help" -- $cur)) fi fi } complete -F _pinctrl pinctrl raspi-utils-20240903/pinctrl/pinctrl.c000066400000000000000000000450621466562725600175570ustar00rootroot00000000000000#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "gpiolib.h" #define ARRAY_SIZE(_a) (sizeof(_a)/sizeof(_a[0])) const char *program_name = "pinctrl"; static int pin_mode = 0; static int verbose_mode = 0; static unsigned num_gpios; struct poll_gpio_state { unsigned int num; unsigned int gpio; const char *name; int level; }; int num_poll_gpios; struct poll_gpio_state *poll_gpios; static void print_gpio_alts_info(unsigned gpio) { const char *name; unsigned int num = gpio; int fsel; if (pin_mode) gpio = gpio_for_pin(num); if (!gpio_num_is_valid(gpio)) return; name = gpio_get_name(gpio); if (pin_mode && strchr(name, '/')) name = strchr(name, '/') + 1; printf("%d", num); if (name[0]) printf(", %s", name); for (fsel = GPIO_FSEL_FUNC0; ; fsel++) { name = gpio_get_gpio_fsel_name(gpio, fsel); if (!name) break; printf(", %s", name); } printf("\n"); } static void usage() { const char *name = program_name; printf("\n"); printf("WARNING! %s set writes directly to the GPIO control registers\n", name); printf("ignoring whatever else may be using them (such as Linux drivers) -\n"); printf("it is designed as a debug tool, only use it if you know what you\n"); printf("are doing and at your own risk!\n"); printf("\n"); printf("Running %s with the help argument prints this help.\n", name); printf("%s can get and print the state of a GPIO (or all GPIOs)\n", name); printf("and can be used to set the function, pulls and value of a GPIO.\n"); printf("%s must be run as root.\n", name); printf("Use:\n"); printf(" %s [-p] [-v] get [GPIO]\n", name); printf("OR\n"); printf(" %s [-p] [-v] [-e] set [options]\n", name); printf("OR\n"); printf(" %s [-p] [-v] poll \n", name); printf("OR\n"); printf(" %s [-p] [-v] funcs [GPIO]\n", name); printf("OR\n"); printf(" %s [-p] [-v] lev [GPIO]\n", name); printf("OR\n"); printf(" %s -c [funcs] [GPIO]\n", name); printf("\n"); printf("GPIO is a comma-separated list of GPIO names, numbers or ranges (without\n"); printf("spaces), e.g. 4 or 18-21 or BT_ON,9-11\n"); printf("\n"); printf("Note that omitting [GPIO] from \"%s get\" prints all GPIOs.\n", name); printf("If the -p option is given, GPIO numbers are replaced by pin numbers on the\n"); printf("40-way header. If the -v option is given, the output is more verbose. Including\n"); printf("the -e option in a \"set\" causes pinctrl to echo back the new pin states.\n"); printf("%s funcs will dump all the possible GPIO alt functions in CSV format\n", name); printf("or if [GPIO] is specified the alternate funcs just for that specific GPIO.\n"); printf("The -c option allows the alt functions (and only the alt function) for a named\n"); printf("chip to be displayed, even if that chip is not present in the current system.\n"); printf("\n"); printf("Valid [options] for %s set are:\n", name); printf(" ip set GPIO as input\n"); printf(" op set GPIO as output\n"); printf(" a0-a8 set GPIO to alt function in the range 0 to 8 (range varies by model)\n"); printf(" no set GPIO to no function (NONE)\n"); printf(" pu set GPIO in-pad pull up\n"); printf(" pd set GPIO in-pad pull down\n"); printf(" pn set GPIO pull none (no pull)\n"); printf(" dh set GPIO to drive high (1) level (only valid if set to be an output)\n"); printf(" dl set GPIO to drive low (0) level (only valid if set to be an output)\n"); printf("Examples:\n"); printf(" %s get Prints state of all GPIOs one per line\n", name); printf(" %s get 10 Prints state of GPIO10\n", name); printf(" %s get 10,11 Prints state of GPIO10 and GPIO11\n", name); printf(" %s set 10 a2 Set GPIO10 to fsel 2 function (nand_wen_clk)\n", name); printf(" %s -e set 10 pu Enable GPIO10 ~50k in-pad pull up, echoing the result\n", name); printf(" %s set 10 pd Enable GPIO10 ~50k in-pad pull down\n", name); printf(" %s set 10 op Set GPIO10 to be an output\n", name); printf(" %s set 10 dl Set GPIO10 to output low/zero (must already be set as an output)\n", name); printf(" %s set 10 ip pd Set GPIO10 to input with pull down\n", name); printf(" %s set 35 a1 pu Set GPIO35 to fsel 1 (jtag_2_clk) with pull up\n", name); printf(" %s set 20 op pn dh Set GPIO20 to output with no pull and driving high\n", name); printf(" %s lev 4 Prints the level (1 or 0) of GPIO4\n", name); printf(" %s -c bcm2835 9-11 Display the alt functions for GPIOs 9-11 on bcm2835\n", name); } static int do_gpio_get(unsigned int gpio) { unsigned int num = gpio; const char *name; int fsel; int level; if (pin_mode) { gpio = gpio_for_pin(num); switch (gpio) { case GPIO_INVALID: case GPIO_GND: case GPIO_5V: case GPIO_3V3: case GPIO_1V8: case GPIO_OTHER: name = gpio_get_name(gpio); printf("%2d: %s\n", num, name); return 0; } } if (!gpio_num_is_valid(gpio)) return 1; fsel = gpio_get_fsel(gpio); printf("%2d: %2s ", num, gpio_get_fsel_name(fsel)); if (fsel == GPIO_FSEL_OUTPUT) printf("%s", gpio_get_drive_name(gpio_get_drive(gpio))); else printf(" "); name = gpio_get_name(gpio); if (pin_mode && strchr(name, '/')) name = strchr(name, '/') + 1; level = gpio_get_level(gpio); printf(" %s | %s // %s%s%s\n", gpio_get_pull_name(gpio_get_pull(gpio)), (level == 1) ? "hi" : (level == 0) ? "lo" : "--", name ? name : "", name ? " = " : "", gpio_get_gpio_fsel_name(gpio, fsel)); return 0; } static int do_gpio_set(unsigned int gpio, int fsparam, int drive, int pull) { unsigned int num = gpio; if (pin_mode) { gpio = gpio_for_pin(num); if (gpio >= num_gpios) { printf("Pin %d cannot be set\n", num); return 1; } } if (!gpio_num_is_valid(gpio)) return 1; if (fsparam != GPIO_FSEL_MAX) gpio_set_fsel(gpio, fsparam); else fsparam = gpio_get_fsel(gpio); if (drive != DRIVE_MAX) { if (fsparam == GPIO_FSEL_OUTPUT) { gpio_set_drive(gpio, drive); } else { printf("Can't set pin value, not an output\n"); return 1; } } if (pull != PULL_MAX) gpio_set_pull(gpio, pull); return 0; } static int do_gpio_level(unsigned int gpio) { unsigned int num = gpio; int level; if (pin_mode) { gpio = gpio_for_pin(num); switch (gpio) { case GPIO_INVALID: return 1; case GPIO_GND: printf("G"); return 0; case GPIO_5V: printf("5"); return 0; case GPIO_3V3: printf("3"); return 0; case GPIO_1V8: printf("8"); return 0; case GPIO_OTHER: printf("?"); return 0; } } if (!gpio_num_is_valid(gpio)) return 1; level = gpio_get_level(gpio); if (level >= 0) printf("%d", level); else printf("-"); return 0; } static int do_gpio_poll_add(unsigned int gpio) { struct poll_gpio_state *new_gpio; unsigned int num = gpio; if (pin_mode) gpio = gpio_for_pin(num); if (!gpio_num_is_valid(gpio)) return 1; poll_gpios = reallocarray(poll_gpios, num_poll_gpios + 1, sizeof(*poll_gpios)); new_gpio = &poll_gpios[num_poll_gpios]; new_gpio->num = num; new_gpio->gpio = gpio; new_gpio->name = gpio_get_name(gpio); new_gpio->level = -1; /* Unknown */ num_poll_gpios++; return 0; } static void do_gpio_poll(void) { unsigned int idle_count = 0; struct timeval idle_start; int i; while (num_poll_gpios) { int changed = 0; for (i = 0; i < num_poll_gpios; i++) { struct poll_gpio_state *state = &poll_gpios[i]; int level = gpio_get_level(state->gpio); if (level != state->level) { if (idle_count) { struct timeval now; uint64_t interval_us; gettimeofday(&now, NULL); interval_us = (uint64_t)(now.tv_sec - idle_start.tv_sec) * 1000000 + (now.tv_usec - idle_start.tv_usec); printf("+%" PRIu64 "us\n", interval_us); idle_count = 0; } printf("%2d: %s // %s\n", state->num, level ? "hi" : "lo", state->name); state->level = level; changed = 1; fflush(stdout); } } if (!changed) { if (!idle_count) gettimeofday(&idle_start, NULL); idle_count++; } } } static void verbose_callback(const char *msg) { printf("%s", msg); } int main(int argc, char *argv[]) { int ret; /* arg parsing */ const char *named_chip = NULL; int set = 0; int get = 0; int level = 0; int poll = 0; int funcs = 0; int echo = 0; int pull = PULL_MAX; int infer_cmd = 0; int fsparam = GPIO_FSEL_MAX; int drive = DRIVE_MAX; uint32_t gpiomask[(MAX_GPIO_PINS + 31)/32] = { 0 }; unsigned start_pin = GPIO_INVALID, end_pin, pin; int first_pin = 1; int i; argv++; argc--; while (argc && (argv[0][0] == '-')) { const char *arg = *(argv++); argc--; if (strcmp(arg, "-h") == 0) { usage(); return 0; } else if (strcmp(arg, "-p") == 0) { pin_mode = 1; } else if (strcmp(arg, "-e") == 0) { echo = 1; } else if (strcmp(arg, "-v") == 0) { verbose_mode = 1; } else if (strcmp(arg, "-c") == 0) { if (!argc) { printf("* chip name expected - use 'pinctrl -h' for help\n"); return -1; } named_chip = *(argv++); argc--; } else { printf("Unknown option '%s' - try \"%s help\"\n", arg, program_name); exit(1); } } if (verbose_mode) gpiolib_set_verbose(&verbose_callback); if (named_chip) ret = gpiolib_init_by_name(named_chip); else ret = gpiolib_init(); if (ret < 0) { printf("Failed to initialise gpiolib - %d\n", ret); return -1; } num_gpios = ret; if (!num_gpios) { printf("No GPIO chips found\n"); return -1; } if (argc) { const char *cmd = *(argv++); argc--; if (strcmp(cmd, "help") == 0) { usage(); return 0; } get = strcmp(cmd, "get") == 0; set = strcmp(cmd, "set") == 0; level = strcmp(cmd, "level") == 0 || strcmp(cmd, "lev") == 0; poll = strcmp(cmd, "poll") == 0; funcs = strcmp(cmd, "funcs") == 0; if (!set && !get && !level && !poll && !funcs) { /* Back up in case we can decode this as a pin */ argv--; argc++; infer_cmd = 1; } } else if (named_chip) { funcs = 1; } else { get = 1; } if (pin_mode) { gpio_get_pin_range(&start_pin, &end_pin); if (start_pin == GPIO_INVALID) { printf("No PIN numbers declared in DT - pin mode disabled\n"); pin_mode = 0; } } if (start_pin == GPIO_INVALID) { start_pin = 0; end_pin = num_gpios - 1; } if (argc) /* expect pin number/name(s) next */ { char *p = *(argv++); argc--; while (p) { unsigned gpio, gpio2; int len, len2; len = strcspn(p, "-,"); ret = sscanf(p, "%u%n", &gpio, &len2); if (ret == 1 && len == len2 && gpio >= num_gpios) break; else if (ret != 1 || len != len2) { gpio = gpio_get_gpio_by_name(p, len); if (gpio == GPIO_INVALID) break; if (pin_mode) { int pin = gpio_to_pin(gpio); if (pin < 0) { printf("Signal \"%*s\" is not on a header pin\n", len, p); return 1; } gpio = (unsigned)pin; } } p += len; if (*p == '\0' && argc && (argv[0][0] == '-' || argv[0][0] == ',')) { p = *(argv++); argc--; } if (*p == '-') { p++; len = strcspn(p, "-,"); ret = sscanf(p, "%u%n", &gpio2, &len2); if (ret == 1 && len == len2 && gpio2 >= num_gpios) break; else if (ret != 1 || len != len2) { gpio2 = gpio_get_gpio_by_name(p, len); if (gpio2 == GPIO_INVALID) break; if (pin_mode) { int pin = gpio_to_pin(gpio2); if (pin < 0) { printf("Signal \"%*s\" is not on a header pin\n", len, p); return 1; } gpio2 = (unsigned)pin; } } if (gpio2 < gpio) { int tmp = gpio2; gpio2 = gpio; gpio = tmp; } p += len; } else { gpio2 = gpio; } while (gpio <= gpio2) { gpiomask[gpio/32] |= (1 << (gpio % 32)); gpio++; } if (*p == '\0' && argc && argv[0][0] == ',') { p = *(argv++); argc--; } if (*p == '\0') { p = NULL; } else { if (*p != ',') break; p++; } } if (p) { if (infer_cmd && p == argv[-1]) printf("Unknown command \"%s\"\n", p); else printf("Unknown GPIO \"%s\"\n", p); return 1; } } else if (set) { printf("Need GPIO number to set\n"); return 1; } else if (poll) { printf("Need GPIO number to poll\n"); return 1; } if (set && !argc) { printf("Need a function or pull to set\n"); return 1; } if ((get || funcs) && argc) { printf("Too many arguments\n"); return 1; } if (infer_cmd) { if (named_chip) funcs = 1; else if (argc) set = 1; else get = 1; } /* parse remaining args */ while (argc) { const char *arg = *(argv++); argc--; if (strcmp(arg, "dh") == 0) drive = DRIVE_HIGH; else if (strcmp(arg, "dl") == 0) drive = DRIVE_LOW; else if (strcmp(arg, "gp") == 0) fsparam = GPIO_FSEL_GPIO; else if (strcmp(arg, "ip") == 0) fsparam = GPIO_FSEL_INPUT; else if (strcmp(arg, "op") == 0) fsparam = GPIO_FSEL_OUTPUT; else if (strcmp(arg, "no") == 0) fsparam = GPIO_FSEL_NONE; else if (strcmp(arg, "a0") == 0) fsparam = GPIO_FSEL_FUNC0; else if (strcmp(arg, "a1") == 0) fsparam = GPIO_FSEL_FUNC1; else if (strcmp(arg, "a2") == 0) fsparam = GPIO_FSEL_FUNC2; else if (strcmp(arg, "a3") == 0) fsparam = GPIO_FSEL_FUNC3; else if (strcmp(arg, "a4") == 0) fsparam = GPIO_FSEL_FUNC4; else if (strcmp(arg, "a5") == 0) fsparam = GPIO_FSEL_FUNC5; else if (strcmp(arg, "a6") == 0) fsparam = GPIO_FSEL_FUNC6; else if (strcmp(arg, "a7") == 0) fsparam = GPIO_FSEL_FUNC7; else if (strcmp(arg, "a8") == 0) fsparam = GPIO_FSEL_FUNC8; else if (strcmp(arg, "pu") == 0) pull = PULL_UP; else if (strcmp(arg, "pd") == 0) pull = PULL_DOWN; else if (strcmp(arg, "pn") == 0) pull = PULL_NONE; else { printf("Unknown argument \"%s\"\n", arg); return 1; } } for (i = ARRAY_SIZE(gpiomask) - 1; i >= 0; i--) { if (gpiomask[i]) break; } if (i < 0) memset(gpiomask, 0xff, sizeof(gpiomask)); if (!funcs) { ret = gpiolib_mmap(); if (ret) { if (ret == EACCES && geteuid()) printf("Must be root\n"); else printf("Failed to mmap gpiolib - %s\n", strerror(ret)); return -1; } } for (pin = start_pin; pin < end_pin + 1; pin++) { if (!(gpiomask[pin / 32] & (1 << (pin % 32)))) continue; if (get) do_gpio_get(pin); if (set) do_gpio_set(pin, fsparam, drive, pull); if (level) { if (!first_pin) printf(" "); do_gpio_level(pin); first_pin = 0; } if (poll) do_gpio_poll_add(pin); if (funcs) print_gpio_alts_info(pin); } if (level) printf("\n"); if (set && echo) { for (pin = start_pin; pin < end_pin + 1; pin++) { if (!(gpiomask[pin / 32] & (1 << (pin % 32)))) continue; do_gpio_get(pin); } } if (poll) do_gpio_poll(); return 0; } raspi-utils-20240903/pinctrl/util.c000066400000000000000000000127061466562725600170600ustar00rootroot00000000000000#include #include #include #include #include #include #include "util.h" // We're actually going to cheat and cast the pointers, but define // a structure to keep the compiler happy. struct dt_subnode_iter { DIR *dh; }; const char *dtpath; static void *do_read_file(const char *fname, const char *mode, size_t *plen) { FILE *fp = fopen(fname, mode); void *buf; long len; if (fp == NULL) return NULL; fseek(fp, 0, SEEK_END); len = ftell(fp); if (plen) *plen = len; buf = malloc(len); fseek(fp, 0, SEEK_SET); if (buf) { if (fread(buf, 1, len, fp) != (size_t)len) { free(buf); buf = NULL; } } fclose(fp); return (char *)buf; } char *read_text_file(const char *fname, size_t *plen) { return do_read_file(fname, "rt", plen); } void *read_file(const char *fname, size_t *plen) { return do_read_file(fname, "rb", plen); } void dt_set_path(const char *path) { dtpath = path; } char *dt_read_prop(const char *node, const char *prop, size_t *plen) { char filename[FILENAME_MAX]; size_t len; len = snprintf(filename, sizeof(filename), "%s%s/%s", dtpath, node, prop); if (len >= sizeof(filename)) { assert(0); return NULL; } filename[sizeof(filename) - 1] = '\0'; return read_file(filename, plen); } uint32_t *dt_read_cells(const char *node, const char *prop, unsigned *num_cells) { uint8_t *buf; size_t len, i; buf = (uint8_t *)dt_read_prop(node, prop, &len); if (buf) { for (i = 0; i + 3 < len; i += 4) { *(uint32_t *)(buf + i) = (buf[i] << 24) + (buf[i + 1] << 16) + (buf[i + 2] << 8) + (buf[i + 3] << 0); } *num_cells = i >> 2; } return (uint32_t *)buf; } uint64_t dt_extract_num(const uint32_t *cells, int size) { uint64_t val = 0; int i; /* PCIe uses 3 cells for an address, but we can ignore the first cell. * In this case, the big-endian representation makes it easy because * the unwanted portion is shifted off the top. */ for (i = 0; i < size; i++) { val = (val << 32) | cells[i]; } return val; } uint64_t dt_read_num(const char *node, const char *prop, size_t size) { unsigned num_cells; uint32_t *cells = dt_read_cells(node, prop, &num_cells); uint64_t val = 0; if (cells) { if (size <= num_cells) val = dt_extract_num(cells, size); dt_free(cells); } return val; } uint32_t dt_read_u32(const char *node, const char *prop) { return dt_read_num(node, prop, 1); } static uint64_t dt_translate_addr(const uint32_t *ranges, unsigned ranges_cells, int npa, int nps, int nca, uint64_t addr) { /* Entries in the ranges table take the form: * * where the elements are big-endian numbers of lengths nca, npa and nps * respectively. */ unsigned pos = 0; while (pos + npa + nps + nca <= ranges_cells) { uint64_t ca, pa, ps; ca = dt_extract_num(ranges + pos, nca); pa = dt_extract_num(ranges + pos + nca, npa); ps = dt_extract_num(ranges + pos + nca + npa, nps); if (addr >= ca && addr <= ca + ps) { addr -= ca; addr += pa; break; } pos += npa + nps + nca; } return addr; } uint64_t dt_parse_addr(const char *node) { char buf1[FILENAME_MAX], buf2[FILENAME_MAX]; char *parent, *nextparent; uint32_t *ranges = NULL; unsigned ranges_cells = 0; uint64_t addr = INVALID_ADDRESS; unsigned npa, nps, nca = 0; parent = buf1; nextparent = buf2; while (1) { char *tmp, *p; strcpy(parent, node); p = strrchr(parent, '/'); if (!p) return INVALID_ADDRESS; if (p == parent) p[1] = '\0'; else p[0] = '\0'; npa = dt_read_u32(parent, "#address-cells"); nps = dt_read_u32(parent, "#size-cells"); if (!npa || !nps) { addr = INVALID_ADDRESS; break; } if (addr == INVALID_ADDRESS) { addr = dt_read_num(node, "reg", npa); } else if (ranges) { addr = dt_translate_addr(ranges, ranges_cells, npa, nps, nca, addr); dt_free(ranges); ranges = NULL; } if (parent[1] == '\0') break; ranges = dt_read_cells(parent, "ranges", &ranges_cells); nca = npa; node = parent; /* Swap parent and nextparent */ tmp = parent; parent = nextparent; nextparent = tmp; } dt_free(ranges); return addr; } void dt_free(void *value) { free(value); } DT_SUBNODE_HANDLE dt_open_subnodes(const char *node) { char dirpath[FILENAME_MAX]; size_t len; len = snprintf(dirpath, sizeof(dirpath), "%s%s", dtpath, node); if (len >= sizeof(dirpath)) { assert(0); return NULL; } return (DT_SUBNODE_HANDLE)opendir(dirpath); } const char *dt_next_subnode(DT_SUBNODE_HANDLE handle) { DIR *dirh = (DIR *)handle; struct dirent *dent; dent = readdir(dirh); return dent ? dent->d_name : NULL; } void dt_close_subnodes(DT_SUBNODE_HANDLE handle) { DIR *dirh = (DIR *)handle; closedir(dirh); } raspi-utils-20240903/pinctrl/util.h000066400000000000000000000016711466562725600170640ustar00rootroot00000000000000#ifndef _UTIL_H #define _UTIL_H #include #define INVALID_ADDRESS ((uint64_t)~0) #define ROUND_UP(n, d) ((((n) + (d) - 1) / (d)) * (d)) #define UNUSED(x) (void)(x) typedef struct dt_subnode_iter *DT_SUBNODE_HANDLE; char *read_text_file(const char *fname, size_t *plen); void *read_file(const char *fname, size_t *plen); void dt_set_path(const char *path); char *dt_read_prop(const char *node, const char *prop, size_t *len); uint32_t *dt_read_cells(const char *node, const char *prop, unsigned *num_cells); uint64_t dt_extract_num(const uint32_t *cells, int size); uint64_t dt_read_num(const char *node, const char *prop, size_t size); uint32_t dt_read_u32(const char *node, const char *prop); uint64_t dt_parse_addr(const char *node); void dt_free(void *value); DT_SUBNODE_HANDLE dt_open_subnodes(const char *node); const char *dt_next_subnode(DT_SUBNODE_HANDLE handle); void dt_close_subnodes(DT_SUBNODE_HANDLE handle); #endif raspi-utils-20240903/raspinfo/000077500000000000000000000000001466562725600160775ustar00rootroot00000000000000raspi-utils-20240903/raspinfo/CMakeLists.txt000066400000000000000000000002731466562725600206410ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.10...3.27) include(GNUInstallDirs) #set project name project(raspinfo) #add executables install(PROGRAMS raspinfo DESTINATION ${CMAKE_INSTALL_BINDIR}) raspi-utils-20240903/raspinfo/README.md000066400000000000000000000001351466562725600173550ustar00rootroot00000000000000# raspinfo Short script to dump information about the Pi. Use it for annotating bug reports. raspi-utils-20240903/raspinfo/raspinfo000077500000000000000000000134601466562725600176520ustar00rootroot00000000000000#!/bin/bash # Some of the regex's used in sed # Catch basic IP6 address "s/\([0-9a-fA-F]\{1,4\}:\)\{7,7\}[0-9a-fA-F]\{1,4\}/y.y.y.y.y.y.y.y/g" # Catch y::y.y.y.y "s/[0-9a-fA-F]\{1,4\}:\(:[0-9a-fA-F]\{1,4\}\)\{1,4\}/y::y.y.y.y/g" # IP4 d.d.d.d decimal "s/\([0-9]\{1,3\}\.\)\{3,3\}[0-9]\{1,3\}/x.x.x.x/g" # mac address "s/\([0-9a-fA-F]\{2,2\}\:\)\{5,5\}[0-9a-fA-F]\{2,2\}/m.m.m.m/g" display_info_drm() { # If running X then can use xrandr, otherwise # dump the /sys/class entries for the displays if command -v xrandr > /dev/null && DISPLAY=${DISPLAY:-:0} xrandr --listmonitors &>/dev/null; then echo "Running (F)KMS and X" echo DISPLAY=${DISPLAY:-:0} xrandr --verbose else echo "Running (F)KMS, console" echo for card in /sys/class/drm/card[0-9]-*; do echo $card # if kmsprint isn't installed print basic mode info if ! command -v kmsprint > /dev/null; then if [ -f $card/modes ]; then cat $card/modes else echo "No modes found" fi fi if [ -f $card/edid ]; then base64 $card/edid else echo "No EDID found" fi echo done fi # kmsprint is more useful, but not always installed echo if command -v kmsprint > /dev/null; then kmsprint echo kmsprint -m else echo "kmsprint is not installed. Install with: sudo apt install kms++-utils" fi echo # dump the /sys/class entries for the displays cardfound=0 for card in `seq 0 9`; do if sudo test -f "/sys/kernel/debug/dri/${card}/state"; then for hdmi in 0 1; do if sudo test -f "/sys/kernel/debug/dri/${card}/hdmi${hdmi}_regs"; then echo "HDMI${hdmi}: $(sudo cat /sys/kernel/debug/dri/$card/hdmi${hdmi}_regs | grep HOTPLUG)" fi done echo echo "/sys/kernel/debug/dri/$card/state:" sudo cat "/sys/kernel/debug/dri/$card/state" echo cardfound=1 fi done if [ "$cardfound" == "0" ]; then echo "kms state not found" fi echo } display_info_legacy() { # Legacy mode echo "Running Legacy framebuffer" echo for card in `seq 0 9`; do F="/dev/fb${card}" if test -e $F; then echo Framebuffer: $F fbset -s -fb $F fi done disps=`tvservice -l | awk '/Display Number/{print substr($3,1,1)}'` tmp=$(mktemp) for display in $disps do echo echo "Display: " $display tvservice -v $display -s tvservice -v $display -n tvservice -v $display -m CEA tvservice -v $display -m DMT echo tvservice -v $display -d $tmp > /dev/null base64 $tmp done rm $tmp } display_info() { # Check if we are running a KMS/DRM system if [ -d "/dev/dri" ]; then display_info_drm else display_info_legacy fi } audio_info() { aplay -l echo aplay -L echo systemctl --user status pipewire.socket pipewire.service pulseaudio.service pulseaudio.socket echo if command -v pactl > /dev/null; then pactl info else echo pactl not installed fi } OUT=raspinfo.txt rm -f $OUT exec > >(tee -ia $OUT) echo "System Information" echo "------------------" echo cat /sys/firmware/devicetree/base/model | sed 's/\x0//g' echo cat /etc/os-release | head -4 echo cat /etc/rpi-issue echo uname -a cat /proc/cpuinfo | tail -3 echo "Throttled flag : "`vcgencmd get_throttled` echo "Camera : "`vcgencmd get_camera` echo echo "Videocore information" echo "---------------------" echo vcgencmd version echo vcgencmd mem_reloc_stats echo echo "Filesystem information" echo "----------------------" df echo cat /proc/swaps echo echo "Package version information" echo "---------------------------" apt-cache policy raspberrypi-ui-mods | head -2 apt-cache policy raspberrypi-sys-mods | head -2 apt-cache policy openbox | head -2 apt-cache policy lxpanel | head -2 apt-cache policy pcmanfm | head -2 apt-cache policy rpd-plym-splash | head -2 echo echo "Networking Information" echo "----------------------" echo ifconfig | sed -e "s/\([0-9a-fA-F]\{1,4\}:\)\{7,7\}[0-9a-fA-F]\{1,4\}/y.y.y.y.y.y.y.y/g" | sed -e "s/[0-9a-fA-F]\{1,4\}:\(:[0-9a-fA-F]\{1,4\}\)\{1,4\}/y::y.y.y.y/g" | sed -e "s/\([0-9]\{1,3\}\.\)\{3,3\}[0-9]\{1,3\}/x.x.x.x/g" | sed -e "s/\([0-9a-fA-F]\{2,2\}\:\)\{5,5\}[0-9a-fA-F]\{2,2\}/m.m.m.m/g" echo echo "USB Information" echo "---------------" echo lsusb -t echo echo "Display Information" echo "-------------------" echo display_info echo echo "Audio Information" echo "-------------------" echo audio_info echo echo "config.txt" echo "----------" echo #cat /boot/config.txt | egrep -v "^\s*(#|^$)" vcgencmd get_config int vcgencmd get_config str echo echo "cmdline.txt" echo "-----------" cat /proc/cmdline echo echo "pin configuration" echo "-----------------" echo if command -v pinctrl > /dev/null; then sudo pinctrl 2>&1 elif command -v raspi-gpio > /dev/null; then raspi-gpio get 2>&1 else echo "pinctrl/raspi-gpio not found" fi echo echo "vcdbg log messages" echo "------------------" echo if command -v vcdbg > /dev/null; then sudo vcdbg log msg 2>&1 elif command -v vclog > /dev/null; then sudo vclog --msg 2>&1 else echo "vcdbg not found" fi echo echo "dmesg log" echo "---------" echo sudo dmesg | sed -e "s/\([0-9a-fA-F]\{1,4\}:\)\{7,7\}[0-9a-fA-F]\{1,4\}/y.y.y.y.y.y.y.y/g" | sed -e "s/[0-9a-fA-F]\{1,4\}:\(:[0-9a-fA-F]\{1,4\}\)\{1,4\}/y::y.y.y.y/g" | sed -e "s/\([0-9a-fA-F]\{2,2\}\:\)\{5,5\}[0-9a-fA-F]\{2,2\}/m.m.m.m/g" if grep -q "^Revision\s*:\s*[ 123][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]1[13457][0-9a-fA-F]$" /proc/cpuinfo then echo echo "EEPROM" echo "------" echo sudo rpi-eeprom-update fi raspi-utils-20240903/vcgencmd/000077500000000000000000000000001466562725600160445ustar00rootroot00000000000000raspi-utils-20240903/vcgencmd/CMakeLists.txt000066400000000000000000000006271466562725600206110ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.10...3.27) include(GNUInstallDirs) #set project name project(vcgencmd) add_executable(vcgencmd vcgencmd.c) install(TARGETS vcgencmd RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES vcgencmd.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) install(FILES vcgencmd-completion.bash RENAME vcgencmd DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/bash-completion/completions") raspi-utils-20240903/vcgencmd/vcgencmd-completion.bash000066400000000000000000000004311466562725600226360ustar00rootroot00000000000000_vcgencmd() { local cur prev words cword split _init_completion -s || return if ! ((cword == 1)); then return fi local cmds=$(vcgencmd commands | sed 's/^.*=//;s/[",]//g') COMPREPLY+=($(compgen -W "$cmds" -- $cur)) } complete -F _vcgencmd vcgencmd raspi-utils-20240903/vcgencmd/vcgencmd.1000066400000000000000000000142301466562725600177140ustar00rootroot00000000000000'\" t .TH VCGENCMD 1 . .SH NAME vcgencmd \- query the VideoCore for information . . .SH SYNOPSIS .SY vcgencmd .IR command \ [ params ] .YS . . .SH DESCRIPTION .B vcgencmd is a command line utility that can get various pieces of information from the VideoCore GPU on the Raspberry Pi. . . .SH COMMANDS To get a list of all the commands that .B vcgencmd supports, type .IR "vcgencmd\ commands" . Some of the more useful commands are described below. . .TP .BI vcos \ sub-command The .B vcos command has a number of sub-commands: .RS .TP .B version Displays the build date and version of the firmware on the VideoCore. .TP .B log status Displays the error log status of the various VideoCore software areas. .RE . .TP .B version Displays the build date and version of the firmware on the VideoCore. . .TP .B get_camera Displays the enabled and detected state of the official camera. 1 means yes, 0 means no. Whilst all firmware (except cutdown versions) will support the camera, this support needs to be enabled by using the .I start_x boot option .BR [BOOT] . . .TP .B get_throttled Returns the throttled state of the system. This is a bit pattern - a bit being set indicates the following meanings: .TS tab(|); l l . Bit|Meaning \_|\_ .T& n l . 0|Under-voltage detected 1|Arm frequency capped 2|Currently throttled 3|Soft temperature limit active 16|Under-voltage has occurred 17|Arm frequency capping has occurred 18|Throttling has occurred 19|Soft temperature limit has occurred .TE .IP A value of zero indicates that none of the above conditions is true. .IP To find if one of these bits has been set, convert the value returned to binary, then number each bit along the top. You can then see which bits are set. For example: .IP .EX 0x50000 = 0101 0000 0000 0000 0000 .EE .IP Adding the bit numbers along the top we get: .TS tab( ); n n n n n n n n n n n n n n n n n n n n . 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .TE .IP From this we can see that bits 18 and 16 are set, indicating that the Pi has previously been throttled due to under-voltage, but is not currently throttled for any reason. . .TP .B measure_temp Returns the temperature of the SoC as measured by the on-board temperature sensor. . .TP .BI measure_clock \ clock This returns the current frequency of the specified clock. The options are: .TS tab(|); l l . Clock|Description \_|\_ arm|ARM cores core|VC4 scaler cores h264|H.264 block isp|Image Signal Processor v3d|3D block uart|UART pwm|PWM block (analog audio output) emmc|SD card interface pixel|Pixel valve vec|Analog video encoder hdmi|HDMI dpi|Display Peripheral Interface .TE .IP For example, .IR "vcgencmd measure_clock arm" . . .TP .BI measure_volts \ block Displays the current voltages used by the specific block. .TS tab(|); l l . Block|Description \_|\_ core|VC4 core voltage sdram_c| sdram_i| sdram_p| .TE . .TP .B otp_dump Displays the content of the One Time Programmable (OTP) memory, which is part of the SoC. These are 32 bit values, indexed from 8 to 64. See the .BR raspi-otp (7) for more details. . .TP .BI get_mem \ type Reports on the amount of memory allocated to the ARM cores with .I vcgencmd get_mem arm or the VC4 with .IR "vcgencmd get_mem gpu" . .IP .B Note: On a Raspberry Pi 4 with greater than 1GB of RAM, the .I arm option is inaccurate. This is because the GPU firmware which implements this command is only aware of the first gigabyte of RAM on the system, so the .I arm setting will always return 1GB minus the .I gpu memory value. To get an accurate report of the amount of ARM memory, use one of the standard Linux commands, such as .I free or .IR "cat /proc/meminfo" . . .TP .BI codec_enabled \ type Reports whether the specified CODEC type is enabled. Possible options for type are AGIF, FLAC, H263, H264, MJPA, MJPB, MJPG, MPG2, MPG4, MVC0, PCM, THRA, VORB, VP6, VP8, WMV9, WVC1. .IP MPG2, WMV9, and WVC1 currently require a paid for licence (see the .B [FAQ] for more info), except on the Pi4, where these hardware codecs are disabled in preference to software decoding, which requires no licence. Note that because the H265 hardware block on the Raspberry Pi4 is not part of the VideoCore GPU, its status is not accessed via this command. . .TP .BI get_config \ type|name This returns all the configuration items of the specified type that have been set in config.txt, or a single configuration item. Possible values for type parameter are .IR int ", " str ", " or simply use the name of the configuration item. . .TP .B get_lcd_info Displays the resolution and colour depth of any attached display. . .TP .B mem_oom Displays statistics on any Out Of Memory events occuring in the VC4 memory space. . .TP .B mem_reloc_stats Displays statistics from the relocatable memory allocator on the VC4. . .TP .B read_ring_osc Returns the curent speed voltage and temperature of the ring oscillator. . .TP .B hdmi_timings Displays the current HDMI settings timings. See .B [VIDEO] for details of the values returned. . .TP .B dispmanx_list Dump a list of all dispmanx items currently being displayed. . .TP .BI display_power \ 0|1|-1 .TQ .BI display_power " 0|1|-1 display" Show current display power state, or set the display power state. .I vcgencmd display_power 0 will turn off power to the current display. .I vcgencmd display_power 1 will turn on power to the display. If no parameter is set, this will display the current power state. The final parameter is an optional display ID, as returned by .I tvservice -l or from the table below, which allows a specific display to be turned on or off. .IP .I vcgencmd display_power 0 7 will turn off power to display ID 7, which is HDMI 1 on a Raspberry Pi 4. .TS tab(|); l l . Display|ID \_|\_ .T& l n . Main LCD|0 Secondary LCD|1 HDMI 0|2 Composite|3 HDMI 1|7 .TE .IP To determine if a specific display ID is on or off, use -1 as the first parameter. .IP .I vcgencmd display_power -1 7 will return 0 if display ID 7 is off, 1 if display ID 7 is on, or -1 if display ID 7 is in an unknown state, for example undetected. . . .SH EXIT STATUS . .IP 0 Command completed successfully .IP -1 Problem with VHCI .IP -2 VideoCore returned an error . . .SH SEE ALSO .B [DOCS] https://www.raspberrypi.com/documentation/computers/os.html#vcgencmd . raspi-utils-20240903/vcgencmd/vcgencmd.c000066400000000000000000000113221466562725600177750ustar00rootroot00000000000000/* Copyright (c) 2012, Broadcom Europe Ltd All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* ---- Include Files ---------------------------------------------------- */ #include #include #include #include #include #include #include #include /* ioctl */ #define DEVICE_FILE_NAME "/dev/vcio" #define MAJOR_NUM 100 #define IOCTL_MBOX_PROPERTY _IOWR(MAJOR_NUM, 0, char *) #define MAX_STRING 1024 /* * use ioctl to send mbox property message */ static int mbox_property(int file_desc, void *buf) { int ret_val = ioctl(file_desc, IOCTL_MBOX_PROPERTY, buf); if (ret_val < 0) { printf("ioctl_set_msg failed:%d\n", ret_val); } return ret_val; } static int mbox_open() { int file_desc; // open a char device file used for communicating with kernel mbox driver file_desc = open(DEVICE_FILE_NAME, 0); if (file_desc < 0) { printf("Can't open device file: %s\n", DEVICE_FILE_NAME); printf("Try creating a device file with: sudo mknod %s c %d 0\n", DEVICE_FILE_NAME, MAJOR_NUM); exit(-1); } return file_desc; } static void mbox_close(int file_desc) { close(file_desc); } #define GET_GENCMD_RESULT 0x00030080 static unsigned gencmd(int file_desc, const char *command, char *result, int result_len) { int i=0; unsigned p[(MAX_STRING>>2) + 7]; int len = strlen(command); // maximum length for command or response if (len + 1 >= MAX_STRING) { fprintf(stderr, "gencmd length too long : %d\n", len); return -1; } p[i++] = 0; // size p[i++] = 0x00000000; // process request p[i++] = GET_GENCMD_RESULT; // (the tag id) p[i++] = MAX_STRING;// buffer_len p[i++] = 0; // request_len (set to response length) p[i++] = 0; // error repsonse memcpy(p+i, command, len + 1); i += MAX_STRING >> 2; p[i++] = 0x00000000; // end tag p[0] = i*sizeof *p; // actual size mbox_property(file_desc, p); result[0] = 0; strncat(result, (const char *)(p+6), result_len); return p[5]; } static void show_usage() { puts( "Usage: vcgencmd command [ params ]" ); puts( "Send a command to the VideoCore and print the result.\n" ); puts( "Without any argument this information is shown.\n" ); puts( "Use the command 'vcgencmd commands' to get a list of available commands\n" ); puts( "Exit status:" ); puts( " 0 command completed successfully" ); puts( " else VideoCore returned an error\n" ); puts( "For further documentation please see" ); puts( "https://www.raspberrypi.com/documentation/computers/os.html#vcgencmd\n" ); } int main(int argc, char *argv[]) { int mb = mbox_open(); int i; char command[MAX_STRING] = {}; char result[MAX_STRING] = {}; if ( argc == 1 ) { // no arguments passed, so show basic usage show_usage(); return 0; } for (i = 1; i < argc; i++) { char *c = command + strlen(command); if (c > command) { strncat(c, " ", command + sizeof command - c); c = command + strlen(command); } strncat(c, argv[i], command + sizeof command - c); } int ret = gencmd(mb, command, result, sizeof result); if (ret) printf( "vc_gencmd_read_response returned %d\n", ret ); mbox_close(mb); printf("%s\n", result); return ret; } raspi-utils-20240903/vclog/000077500000000000000000000000001466562725600153705ustar00rootroot00000000000000raspi-utils-20240903/vclog/CMakeLists.txt000066400000000000000000000004101466562725600201230ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.10...3.27) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror -pedantic") #set project name project(vclog) #add executables add_executable(vclog vclog.c) install(TARGETS vclog RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) raspi-utils-20240903/vclog/README.md000066400000000000000000000027451466562725600166570ustar00rootroot00000000000000 # vclog Tool to fetch VideoCore logs ( assert or msg ) and display them on the terminal. The addresses where logs can be found are fetched from the device tree and then using headers found inside that region of memory, the code accesses the log type of interest and parses it. **Build Instructions** Install cmake with "sudo apt install cmake" - you need at least version 3.10. After cloning this repo 'git clone https://github.com/raspberrypi/utils' it is advised to create a binary folder seperate from the source ie 'build' for where CMake can generate the build pipeline: - *mkdir build* - *cd build* - *cmake ..* - *make* **Usage** * sudo ./vclog [-f] <-m|-a> * sudo ./vclog [--follow] <--msg|--assert> **Notes** * Structs describing how VC arranges it's data within the regions of interest must keep the same size values. Since the VC might have a different memory model from the host (32-bit vs 64-bit) structs values need to be of the same size. * memcpy load pair on aarch64 requires the src address to be aligned with 8 bit boundaries but also at (src + size of memory to copy). memcpy was used so that we could capture the current state of the logs for parsing, as logs are written to a circular buffer. * Logs are written to a circular buffer - so VideoCore keeps track of: A) Oldest message (first message user wants to read) which gets wiped if circular buffer wraps around back to start B) Next region in circular buffer where VideoCore will write its next message raspi-utils-20240903/vclog/vclog.c000066400000000000000000000337071466562725600166600ustar00rootroot00000000000000/******************************************************************************* Summary: Command line program for printing VideoCore log messages or assertion logs messages Licensing: Copyright (c) 2022-2023, Raspberry Pi Ltd. All rights reserved. *******************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include enum { LOG_UNKNOWN, LOG_MSG, LOG_ASSERT, }; typedef struct { uint32_t padding[8]; uint32_t assert_type; uint32_t assert_addr; uint32_t msg_type; uint32_t msg_addr; uint32_t task_type; uint32_t task_addr; } log_toc_t; typedef struct { uint32_t id; uint32_t start; // circular buffer start for this type of message uint32_t end; // circular buffer end uint32_t write; // where VC will write the next message uint32_t read; // the oldest valid message } log_hdr_t; typedef struct { uint32_t time; // time log produced in millseconds uint16_t seq_num; // if two entries have same timestamp then // this seq num differentiates them uint16_t size; // size of entire log entry = this header + payload } msg_hdr_t; struct fb_dmacopy { void *dst; uint32_t src; uint32_t length; }; enum { LOG_ID = 0x564c4f47, MSG_ID = 0x5353454d, TASK_ID = 0x3b534154, ASSERT_ID = 0x54525341, }; #define MAX(x, y) (((x) > (y)) ? (x) : (y)) #define ARRAY_SIZE(_a) (sizeof(_a)/sizeof(_a[0])) #define FBIODMACOPY _IOW('z', 0x22, struct fb_dmacopy) #ifdef DEBUG #define DLOG(...) printf(__VA_ARGS__) #else #define DLOG(...) (void)0 #endif static int log_type = LOG_UNKNOWN; static int help = 0; static int follow_mode = 0; static const struct option long_options[] = { { "a", no_argument, &log_type, LOG_ASSERT }, { "assert", no_argument, &log_type, LOG_ASSERT }, { "m", no_argument, &log_type, LOG_MSG }, { "msg", no_argument, &log_type, LOG_MSG }, { "f", no_argument, &follow_mode, 1 }, { "follow", no_argument, &follow_mode, 1 }, { "h", no_argument, &help, 1 }, { "help", no_argument, &help, 1 }, { 0 } }; static uint32_t vc_map_base; static uint32_t vc_map_end; static int dma_fd = -1; static char *vc_map = MAP_FAILED; static bool find_logs(uint32_t *logs_start, uint32_t *logs_size); static bool prepare_vc_mapping(uint32_t vc_start, uint32_t vc_size); static void destroy_vc_mapping(void); static void read_vc_mem(uint32_t vc_addr, uint32_t size, void *dest); static uint32_t log_copy_wrap(const char *log_buffer, uint32_t log_size, uint32_t offset, uint32_t len, char *dest); static void die(const char *msg); int32_t main(int32_t argc, char *argv[]) { const struct timespec delay_10ms = {0, 10000000}; const struct timespec delay_1ms = {0, 1000000}; uint32_t logs_start_vc = 0; uint32_t logs_size = 0; char *payload_buffer = NULL; uint32_t payload_buffer_size = 0; uint32_t log_id; uint32_t log_addr; uint32_t log_size; char *log_buffer; log_toc_t toc; log_hdr_t log_hdr; uint32_t read_pos; uint32_t write_pos; uint32_t next_pos = 0; int corrupt_retries = 0; while (getopt_long_only(argc, argv, "", long_options, NULL) != -1) continue; if (help || log_type == LOG_UNKNOWN) { fprintf(stderr, "Usage:\n\t%s [-f] <-m|-a>\n\t%s [--follow] <--msg|--assert>\n", argv[0], argv[0]); return EXIT_FAILURE; } // find the address and size of the logs in VC memory if (!find_logs(&logs_start_vc, &logs_size)) die("Could not determine logs location from Device Tree"); if (!prepare_vc_mapping(logs_start_vc, logs_size)) die("Cannot access logs"); read_vc_mem(logs_start_vc, sizeof(toc), &toc); switch (log_type) { case LOG_ASSERT: log_addr = toc.assert_addr; log_id = ASSERT_ID; break; case LOG_MSG: log_addr = toc.msg_addr; log_id = MSG_ID; break; default: die("Invalid type"); } read_vc_mem(log_addr, sizeof(log_hdr), &log_hdr); if (log_hdr.id != log_id) die("Log ID incorrect"); log_size = log_hdr.end - log_hdr.start; log_buffer = malloc(log_size); if (!log_buffer) { fprintf(stderr, "Failed to allocate buffer for VC msgs\n"); goto cleanup; } read_vc_mem(log_hdr.start, log_size, log_buffer); read_pos = log_hdr.read - log_hdr.start; write_pos = log_hdr.write - log_hdr.start; if (read_pos >= log_size || write_pos >= log_size) die("Log pointers out of range"); while (1) { uint32_t new_write_pos; int new_data; // Print logs between read_pos and write_pos while (read_pos != write_pos) { msg_hdr_t msg; uint32_t payload_pos; uint32_t payload_len; msg.size = 0; payload_pos = log_copy_wrap(log_buffer, log_size, read_pos, sizeof(msg), (char *)&msg); payload_len = msg.size - sizeof(msg_hdr_t); if (payload_len > log_size) { DLOG("log corrupt 1\n"); corrupt_retries++; break; } if (payload_len > payload_buffer_size) { payload_buffer_size = MAX(payload_len, 100); // skip some churn payload_buffer = realloc(payload_buffer, payload_buffer_size); if (!payload_buffer) die("Out of memory"); } next_pos = log_copy_wrap(log_buffer, log_size, payload_pos, payload_len, payload_buffer); // Check read_pos hasn't overtaken write_pos if (next_pos > read_pos) { // Normal if (read_pos < write_pos && next_pos > write_pos) { DLOG("log corrupt 2\n"); corrupt_retries++; break; } } else { // This message wrapped if (read_pos < write_pos || next_pos > write_pos) { DLOG("log corrupt 3\n"); corrupt_retries++; break; } } if (log_type == LOG_MSG) { DLOG("%08x: ", read_pos); printf("%06i.%03i: %.*s\n", msg.time / 1000, msg.time % 1000, payload_len - 4, payload_buffer + 4); } else if (log_type == LOG_ASSERT) { size_t filename_len = strnlen(payload_buffer, payload_len); uint32_t cond_offset; uint32_t line_number; cond_offset = filename_len +1 + 4; if ((cond_offset + 1) >= payload_len) { DLOG("log corrupt 4\n"); corrupt_retries++; break; } memcpy(&line_number, payload_buffer + filename_len + 1, 4); printf("%06i.%03i: assert( %.*s ) failed; %s line %d\n", msg.time / 1000, msg.time % 1000, payload_len = cond_offset, payload_buffer + cond_offset, payload_buffer, line_number); printf("----------------\n"); } corrupt_retries = 0; read_pos = next_pos; } if (corrupt_retries) { // Apparent corruption is likely to be due to the effects of caches. // Rewind to re-copy the affected area a number of times before // giving up. if (corrupt_retries == 10) { DLOG("==== read %x, write %x, next %x, size %x ====\n", read_pos, write_pos, next_pos, log_size); die("Log corrupt"); } write_pos = read_pos; nanosleep(&delay_10ms, NULL); } if (!follow_mode) break; while (1) { // Refresh view of the log pointers read_vc_mem(log_addr, sizeof(log_hdr), &log_hdr); new_write_pos = log_hdr.write - log_hdr.start; if (new_write_pos != write_pos) break; nanosleep(&delay_1ms, NULL); } // More messages added new_data = new_write_pos - write_pos; if (new_data > 0) { read_vc_mem(log_hdr.start + write_pos, new_data, log_buffer + write_pos); } else { // The new data wraps around the end of the buffer read_vc_mem(log_hdr.start + write_pos, log_size - write_pos, log_buffer + write_pos); read_vc_mem(log_hdr.start, new_write_pos, log_buffer); } write_pos = new_write_pos; } free(payload_buffer); free(log_buffer); cleanup: destroy_vc_mapping(); return EXIT_SUCCESS; } static bool find_logs(uint32_t *logs_start, uint32_t *logs_size) { const char *const filename = "/proc/device-tree/chosen/log"; uint32_t vals[2]; FILE *fp; bool ret = false; if (!logs_start || !logs_size) goto exit; // VideoCore logs start and size can be found in the Device Tree fp = fopen(filename, "rb"); if (!fp) goto exit; if (fread(vals, sizeof(vals), 1, fp) != 1) goto cleanup; // Device Tree is stored in network order, i.e. big-endian *logs_start = ntohl(vals[0]); *logs_size = ntohl(vals[1]); ret = true; cleanup: fclose(fp); exit: return ret; } static bool prepare_vc_mapping(uint32_t vc_start, uint32_t vc_size) { const char *dma_filenames[] = { "/dev/vc-mem", "/dev/fb0" }; const char *mem_filename = "/dev/mem"; struct fb_dmacopy ioparam; uint32_t id; int err, fd, i; ioparam.dst = &id; ioparam.src = vc_start; ioparam.length = sizeof(id); for (i = 0; i < (int)ARRAY_SIZE(dma_filenames); i++) { if ((fd = open(dma_filenames[i], O_RDWR | O_SYNC)) >= 0) { err = ioctl(fd, FBIODMACOPY, &ioparam); if (err == 0 && id == LOG_ID) { dma_fd = fd; goto success; } close(fd); } } if ((fd = open(mem_filename, O_RDONLY)) >= 0) { long page_size = sysconf(_SC_PAGE_SIZE); /* find start and end addresses aligned down and up to pagesize respectively */ off_t mmap_start = (uintptr_t)vc_start & ~(page_size - 1); off_t mmap_end = ((uintptr_t)vc_start + vc_size + page_size - 1) & ~(page_size -1); vc_map = mmap(NULL, mmap_end - mmap_start, PROT_READ, MAP_PRIVATE, fd, (uintptr_t)mmap_start); close(fd); if (vc_map != MAP_FAILED) { id = *(uint32_t *)(vc_map + vc_start - mmap_start); if (id == LOG_ID) { vc_start = mmap_start; vc_size = mmap_end - mmap_start; goto success; } } } fprintf(stderr, "Could not map VC memory: %s\n", strerror(errno)); return false; success: vc_map_base = vc_start; vc_map_end = vc_start + vc_size; return true; } static void destroy_vc_mapping(void) { if (dma_fd != -1) { close(dma_fd); dma_fd = -1; } if (vc_map != MAP_FAILED) { munmap(vc_map, vc_map_end - vc_map_base); vc_map = MAP_FAILED; } } /********************************************************************************/ /* Note: gcc with -O2 or higher may replace most of this code with memcpy */ /* which causes a bus error when given an insufficiently aligned mmap-ed buffer */ /* Using volatile disables that optimisation */ /********************************************************************************/ static void memcpy_vc_memory(void *restrict dest, const volatile void *restrict src, size_t n) { if ((((uintptr_t)dest | (uintptr_t)src | n) & 3) == 0) { uint32_t *restrict d = (uint32_t *restrict )dest; const volatile uint32_t *restrict s = (const volatile uint32_t *restrict)src; while (n) *d++ = *s++, n -= 4; } else { uint8_t *restrict d = (uint8_t *restrict )dest; const volatile uint8_t *restrict s = (const volatile uint8_t *restrict)src; while (n--) *d++ = *s++; } } static void read_vc_mem(uint32_t vc_addr, uint32_t size, void *dest) { vc_addr &= 0x3fffffff; if (vc_addr < vc_map_base || vc_addr + size > vc_map_end) die("VC access out-of-bounds"); if (dma_fd != -1) { struct fb_dmacopy ioparam; ioparam.dst = dest; ioparam.src = vc_addr; ioparam.length = size; if (ioctl(dma_fd, FBIODMACOPY, &ioparam) != 0) die("Failed to copy VC memory"); } else { memcpy_vc_memory(dest, vc_map + vc_addr - vc_map_base, size); } } static uint32_t log_copy_wrap(const char *log_buffer, uint32_t log_size, uint32_t offset, uint32_t len, char *dest) { if (offset >= log_size) return ~0; if (offset + len < log_size) { memcpy(dest, log_buffer + offset, len); return offset + len; } else { uint32_t first_chunk = log_size - offset; memcpy(dest, log_buffer + offset, first_chunk); len -= first_chunk; if (len) memcpy(dest + first_chunk, log_buffer, len); return len; } } static void die(const char *msg) { fprintf(stderr, "Fatal error: %s\n", msg); exit(EXIT_FAILURE); } raspi-utils-20240903/vcmailbox/000077500000000000000000000000001466562725600162425ustar00rootroot00000000000000raspi-utils-20240903/vcmailbox/CMakeLists.txt000066400000000000000000000006311466562725600210020ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.10...3.27) include(GNUInstallDirs) #set project name project(vcmailbox) add_executable(vcmailbox vcmailbox.c) target_link_libraries(vcmailbox) install(TARGETS vcmailbox RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES vcmailbox.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) install(FILES vcmailbox.7 raspiotp.7 raspirev.7 DESTINATION ${CMAKE_INSTALL_MANDIR}/man7) raspi-utils-20240903/vcmailbox/raspiotp.7000066400000000000000000000117001466562725600201720ustar00rootroot00000000000000.TH RASPIOTP 7 . .SH NAME raspiotp \- the Raspberry Pi OTP register bits . . .SH DESCRIPTION . All SoCs used by the Raspberry Pi range have a inbuilt One-Time Programmable (OTP) memory block. It is 66 32-bit values long, although only a few locations have factory-programmed data. The .BR vcgencmd (1) utility can be used to display the contents of the OTP like so: .PP .EX $ \fBvcgencmd otp_dump\fR .EE . . .SH OTP REGISTERS This list contains the publicly available information on the registers. If a register or bit is not defined here, then it is not public. . .TP .B 17 bootmode register .RS .TP .B Bit 1 sets the oscillator frequency to 19.2MHz .TP .B Bit 3 enables pull ups on the SDIO pins .TP .B Bit 19 enables GPIO bootmode .TP .B Bit 20 sets the bank to check for GPIO bootmode .TP .B Bit 21 enables booting from SD card .TP .B Bit 22 sets the bank to boot from .TP .B Bit 28 enables USB device booting .TP .B Bit 29 enables USB host booting (ethernet and mass storage) .RE . .TP .B 18 copy of bootmode register . .TP .B 28 serial number . .TP .B 29 ~(serial number) . .TP .B 30 revision code; . .BR raspirev (7) .TP .B 36-43 customer OTP values (see .B INDUSTRIAL USE below) . .TP .B 45 MPG2 decode key . .TP .B 46 WVC1 decode key . .TP .B 64-65 MAC address; if set, system will use this in preference to the automatically generated address based on the serial number . .TP .B 66 advanced boot register .RS .TP .B Bits 0-6 GPIO for ETH_CLK output pin .TP .B Bit 7 enables ETH_CLK output .TP .B Bits 8-14 GPIO for LAN_RUN output pin .TP .B Bit 15 enables LAN_RUN output .TP .B Bit 24 extends USB HUB timeout parameter .TP .B Bit 25 ETH_CLK frequency: 0=25MHz, 1=24MHz .RE . . .SH INDUSTRIAL USE The Raspberry Pi is often used as part of another product. This section describes some extra facilities available to use other capabilities of the Pi. . .SS Customer OTP settings There are a number of OTP values that can be used. To see a list of all the OTP values, you can use: .PP .EX $ \fBvcgencmd otp_dump\fR .EE .PP In register locations 36 to 43 (inclusive), there are eight rows of 32 bits available for the customer (detailed in .B OTP REGISTERS above). .PP To program these bits, you will need to use the vcmailbox. This is a Linux driver interface to the firmware which will handle the programming of the rows. To do this, please refer to the documentation at [MAILBOX], and the .BR vcmailbox (1) example application. For example: .PP .EX $ \fBvcmailbox 0x00010004 8 8 0 0\fR 0x00000020 0x80000000 0x00010004 0x00000008 0x800000008 0xnnnnnnnn 0x00000000 0x00000000 .EE .PP The above uses the .B GET_BOARD_SERIAL tag (detailed under [MAILBOX]) with a request size of 8 bytes and response size of 8 bytes (sending two integers for the request 0, 0). The response to this will be two integers (0x00000020 and 0x80000000) followed by the tag code, the request length, the response length (with the 31st bit set to indicate that it is a response) then the 64 bit serial number (where the MS 32bits are always 0). .PP To set the customer OTP values you will need to use the .B SET_CUSTOMER_OTP (0x38021) tag as follows: .PP .EX $ \fBvcmailbox 0x00038021 \fI[8+rows*4] [8+rows*4] [start] [rows] [value]\fR ... .EE .TP .B start the first row to program from 0-7 .TP .B rows number of rows to program .TP .B value each value to program .PP So, to program OTP customer rows 4, 5, and 6 to 0x11111111, 0x22222222, 0x33333333 respectively, you would use: .PP .EX $ \fBvcmailbox 0x00038021 20 20 4 3 0x11111111 0x22222222 0x33333333\fR .EE .PP This will then program rows 40, 41, and 42. To read the values back, you can use: .PP .EX $ \fBvcmailbox 0x00030021 20 20 4 3 0 0 0\fR 0x0000002c 0x80000000 0x00030021 0x00000014 0x80000014 0x00000000 0x00000003 0x11111111 0x22222222 0x33333333 .EE .PP If you'd like to integrate this functionality into your own code, you should be able to achieve this by using the vcmailbox.c code as an example. . .SS Locking the OTP changes It is possible to lock the OTP changes to avoid them being edited again. This can be done using a special argument with the OTP write mailbox: .PP .EX $ \fBvcmailbox 0x00038021 8 8 0xffffffff 0xaffe0000\fR .EE .PP Once locked, the customer OTP values can no longer be altered. Note that this locking operation is irreversible. . .SS Making customer OTP bits unreadable It is possible to prevent the customer OTP bits from being read at all. This can be done using a special argument with the OTP write mailbox: .PP .EX $ \fBvcmailbox 0x00038021 8 8 0xffffffff 0xaffebabe\fR .EE .PP This operation is unlikely to be useful for the vast majority of users, and is irreversible. . . .SH SEE ALSO .BR vcgencmd (1), .BR raspirev (7), .B [SOURCE] . . .SH REFERENCES .TP .B [MAILBOX] https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface . .TP .B [SOURCE] https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#otp-register-and-bit-definitions and https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#industrial-use-of-the-raspberry-pi raspi-utils-20240903/vcmailbox/raspirev.7000066400000000000000000000100151466562725600201620ustar00rootroot00000000000000'\" t .TH RASPIREV 7 . .SH NAME raspirev \- Raspberry Pi revision codes . . .SH DESCRIPTION Each distinct Raspberry Pi model revision has a unique revision code. You can look up a Raspberry Pi's revision code by running: .PP .EX $ \fBcat /proc/cpuinfo\fR .EE .PP The last three lines show the hardware type, the revision code, and the Pi's unique serial number. For example: .PP .EX Hardware : BCM2835 Revision : a02082 Serial : 00000000765fc593 .EE .PP .B Note: As of the 4.9 kernel, all Pis report BCM2835, even those with BCM2836, BCM2837 and BCM2711 processors. You should not use this string to detect the processor. Decode the revision code using the information below, or read .I /sys/firmware/devicetree/base/model . . .SH OLD-STYLE REVISION CODES The first set of Raspberry Pi models were given sequential hex revision codes from 0002 to 0015: .TS tab(|); l l l l l . Code|Model|Revision|RAM|Manufacturer \_|\_|\_|\_|\_ 0002|B|1.0|256MB|Egoman 0003|B|1.0|256MB|Egoman 0004|B|2.0|256MB|Sony UK 0005|B|2.0|256MB|Qisda 0006|B|2.0|256MB|Egoman 0007|A|2.0|256MB|Egoman 0008|A|2.0|256MB|Sony UK 0009|A|2.0|256MB|Qisda 000d|B|2.0|512MB|Egoman 000e|B|2.0|512MB|Sony UK 000f|B|2.0|512MB|Egoman 0010|B+|1.2|512MB|Sony UK 0011|CM1|1.0|512MB|Sony UK 0012|A+|1.1|256MB|Sony UK 0013|B+|1.2|512MB|Embest 0014|CM1|1.0|512MB|Embest 0015|A+|1.1|256MB/512MB|Embest .TE . . .SH NEW-STYLE REVISION CODES With the launch of the Raspberry Pi 2, new-style revision codes were introduced. Rather than being sequential, each bit of the hex code represents a piece of information about the revision: .PP .EX NOQuuuWuFMMMCCCCPPPPTTTTTTTTRRRR .EE . .TP .B N Overvoltage .PD 0 .RS .TP .B 0 Overvoltage allowed .TP .B 1 Overvoltage disallowed .RE .PD . .TP .B O OTP Programming; see .BR raspiotp (7) .PD 0 .RS .TP .B 0 OTP programming allowed .TP .B 1 OTP programming disallowed .RE .PD . .TP .B Q OTP Reading; see .BR raspiotp (7) .PD 0 .RS .TP .B 0 OTP reading allowed .TP .B 1 OTP reading disallowed .RE .PD . .TP .BR uuu Unused . .TP .B W Warranty bit .PD 0 .RS .TP .B 0 Warranty is intact .TP .B 1 Warranty has been voided by overclocking .RE .PD . .TP .B u Unused . .TP .B F New flag .PD 0 .RS .TP .B 1 new-style revision .TP .B 0 old-style revision .RE .PD . .TP .B MMM Memory size .PD 0 .RS .TP .B 0 256MB .TP .B 1 512MB .TP .B 2 1GB .TP .B 3 2GB .TP .B 4 4GB .TP .B 5 8GB .RE .PD . .TP .B CCCC Manufacturer .PD 0 .RS .TP .B 0 Sony UK .TP .B 1 Egoman .TP .B 2 Embest .TP .B 3 Sony Japan .TP .B 4 Embest .TP .B 5 Stadium .RE .PD . .TP .B PPPP Processor .PD 0 .RS .TP .B 0 BCM2835 .TP .B 1 BCM2836 .TP .B 2 BCM2837 .TP .B 3 BCM2711 .RE .PD . .TP .B TTTTTTTT Type .PD 0 .RS .TP .B 0 A .TP .B 1 B .TP .B 2 A+ .TP .B 3 B+ .TP .B 4 2B .TP .B 5 Alpha (early prototype) .TP .B 6 CM1 .TP .B 8 3B .TP .B 9 Zero .TP .B a CM3 .TP .B c Zero W .TP .B d 3B+ .TP .B e 3A+ .TP .B f Internal use only .TP .B 10 CM3+ .TP .B 11 4B .TP .B 13 400 .TP .B 14 CM4 .RE .PD . .TP .B RRRR Revision (0, 1, 2, etc.) .PP New-style revision codes in use at the time of writing: .TS tab(|); l l l l l . Code|Model|Revision|RAM|Manufacturer \_|\_|\_|\_|\_ 900021|A+|1.1|512MB|Sony UK 900032|B+|1.2|512MB|Sony UK 900092|Zero|1.2|512MB|Sony UK 900093|Zero|1.3|512MB|Sony UK 9000c1|Zero W|1.1|512MB|Sony UK 9020e0|3A+|1.0|512MB|Sony UK 920092|Zero|1.2|512MB|Embest 920093|Zero|1.3|512MB|Embest 900061|CM|1.1|512MB|Sony UK a01040|2B|1.0|1GB|Sony UK a01041|2B|1.1|1GB|Sony UK a02082|3B|1.2|1GB|Sony UK a020a0|CM3|1.0|1GB|Sony UK a020d3|3B+|1.3|1GB|Sony UK a02042|2B (BCM2837)|1.2|1GB|Sony UK a21041|2B|1.1|1GB|Embest a22042|2B (BCM2837)|1.2|1GB|Embest a22082|3B|1.2|1GB|Embest a220a0|CM3|1.0|1GB|Embest a32082|3B|1.2|1GB|Sony Japan a52082|3B|1.2|1GB|Stadium a22083|3B|1.3|1GB|Embest a02100|CM3+|1.0|1GB|Sony UK a03111|4B|1.1|1GB|Sony UK b03111|4B|1.1|2GB|Sony UK b03112|4B|1.2|2GB|Sony UK c03111|4B|1.1|4GB|Sony UK c03112|4B|1.2|4GB|Sony UK d03114|4B|1.4|8GB|Sony UK .TE . . .SH SEE ALSO .BR raspiotp (7), .B [SOURCE] . . .SH REFERENCES .TP .B [SOURCE] https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#raspberry-pi-revision-codes raspi-utils-20240903/vcmailbox/vcmailbox.1000066400000000000000000000011351466562725600203100ustar00rootroot00000000000000.TH VCMAILBOX 1 . .SH NAME vcmailbox \- send messages to the VideoCore via the mailbox . . .SH SYNOPSIS .SY vcmailbox .RI [ word0 ] .RI [ word1 ] \|.\|.\|. . . .SH DESCRIPTION .B vcmailbox is a low-level utility for sending mailbox messages to the VideoCore. See .B [MAILBOX] or .BR vcmailbox (7) for more information on the available messages and their respective formats. and .BR raspiotp (7) for some examples of typical usage. . . .SH SEE ALSO .BR vcgencmd (1), .BR raspiotp (7), .B [MAILBOX] . . .SH REFERENCES .TP .B [MAILBOX] https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface raspi-utils-20240903/vcmailbox/vcmailbox.7000066400000000000000000000642651466562725600203330ustar00rootroot00000000000000'\" t .TH VCMAILBOX 7 . .SH NAME vcmailbox \- the VideoCore mailbox property interface . . .SH DESCRIPTION . The mailbox can be used by the ARM on the Raspberry Pi to communicate with the VideoCore processor. From the command line, the .BR vcmailbox (1) can be used for this purpose. The mailbox operates a simple request / response protocol: . .IP \(bu 3 The response overwrites the request. .RS .IP \(bu 3 The callee is not allowed to return a different buffer address, this allows the caller to make independent asynchronous requests. .RE . .IP \(bu The buffer itself is 16-byte aligned as only the upper 28 bits of the address can be passed via the mailbox. . .IP \(bu All u64/u32/u16 values are in host CPU endian order. . .IP \(bu Unknown tags are ignored (the response bit will not be set). . .IP \(bu Response may include unsolicited tags. . .IP \(bu Response tag lengths may be longer than expected if additional information is provided in a future format. .RS .IP \(bu 3 The response is truncated to the provided buffer length. .IP \(bu Incompatible changes require a new tag so that where the buffer is the size required by a previous version the truncated part should be readable as per the previous version. .IP \(bu Response length indicates the desired length even when it is longer than the buffer size filled. .IP \(bu Tag value length can be used to infer the version of the request/response. .RE . .IP \(bu Tags should be processed in order except where an interface requires multiple tags for a single operation (like the frame buffer). . . .SS Mailbox messages . .IP \(bu 3 The mailbox interface has 28 bits (MSB) available for the value and 4 bits (LSB) for the channel. .RS .IP \(bu 3 Request message: 28 bits (MSB) buffer address .IP \(bu Response message: 28 bits (MSB) buffer address .RE . .IP \(bu Channels 8 and 9 are used. .RS .IP \(bu 3 Channel 8: Request from ARM for response by VC .IP \(bu Channel 9: Request from VC for response by ARM (none currently defined) .RE . . .SS Buffer contents . . .TP .B u32 buffer size in bytes (including the header values, the end tag and padding) . .TP .B u32 buffer request/response code .RS .PP Request codes: .PD 0 .TP 12 .B 0x00000000 process request .TP .B \& All other values reserved .PD .PP Response codes: .PD 0 .TP 12 .B 0x80000000 request successful .TP .B 0x80000001 error parsing request buffer (partial response) .TP .B \& All other values reserved .PD .RE . .TP .B u8\|.\|.\|. sequence of concatenated tags . .TP .B u32 0x0 (end tag) . .TP .B u8\|.\|.\|. padding . . .SS Tag format . .TP .B u32 tag identifier . .TP .B u32 value buffer size in bytes . .TP .B u32 Request codes: .PD 0 .RS .TP 12 .B b31 clear request .TP .B b30-b0 reserved .RE .PD .IP Response codes: .PD 0 .RS .TP 12 .B b31 set response .TP .B b30-b0 value length in bytes .RE .PD . .TP .B u8\|.\|.\|. value buffer . .TP .B u8\|.\|.\|. padding to align the tag to 32 bits. . . .SH VIDEOCORE TAGS . . .SS Get firmware revision .PD 0 .TP 12 .B Tag: 0x00000001 .TP .B Request: 0 bytes .TP .B Response: 4 bytes .RS .TP .B u32 firmware revision .RE .PD . . .SH HARDWARE TAGS . .SS Get board model .PD 0 .TP 12 .B Tag: 0x00010001 .TP .B Request: 0 bytes .TP .B Response: 4 bytes .RS .TP .B u32 board model .RE .PD . .SS Get board revision .PD 0 .TP 12 .B Tag: 0x00010002 .TP .B Request: 0 bytes .TP .B Response: 4 bytes .RS .TP .B u32 board revision .RE .PD . .SS Get board MAC address .PD 0 .TP 12 .B Tag: 0x00010003 .TP .B Request: 0 bytes .TP .B Response: 6 bytes .RS .TP .B u8... MAC address in network byte order .RE .PD . .SS Get board serial .PD 0 .TP 12 .B Tag: 0x00010004 .TP .B Request: 0 bytes .TP .B Response: 8 bytes .RS .TP .B u64 board serial .RE .PD . .SS Get ARM memory .PD 0 .TP 12 .B Tag: 0x00010005 .TP .B Request: 0 bytes .TP .B Response: 8 bytes .RS .TP .B u32 base address in bytes .TP .B u32 size in bytes .RE .PD . .PP Future formats may specify multiple base+size combinations. . .SS Get VC memory .PD 0 .TP 12 .B Tag: 0x00010006 .TP .B Request: 0 bytes .TP .B Response: 8 bytes .RS .TP .B u32 base address in bytes .TP .B u32 size in bytes .RE .PD . .PP Future formats may specify multiple base+size combinations. . .SS Get clocks .PD 0 .TP 12 .B Tag: 0x00010007 .TP .B Request: 0 bytes .TP .B Response: variable bytes (multiple of 8) .RS .TP .B u32 parent clock id (0 for a root clock) .TP .B u32 clock id .TP .B (repeated) .RE .PD . .PP Returns all clocks that exist .IR "in top-down, breadth-first order" . Clocks that depend on another clock should be defined as children of that clock. Clocks that depend on no other clocks should have no parent. Clock IDs are as in the .B CLOCK TAGS section below. . . .SH CONFIG TAGS . .SS Get command line .PD 0 .TP 12 .B Tag: 0x00050001 .TP .B Request: 0 bytes .TP .B Response: variable bytes .RS .TP .B u8... ASCII command line string .RE .PD . .PP Caller should not assume the string is null terminated. . . .SH SHARED RESOURCE MANAGEMENT TAGS . .SS Get DMA channels .PD 0 .TP 12 .B Tag: 0x00060001 .TP .B Request: 0 bytes .TP .B Response: 4 bytes .RS .TP .B u32 mask .RS .TP 12 .B Bits 0-15 DMA channels 0-15 (0=do not use, 1=usable) .TP .B Bits 16-31 reserved for future use .RE .RE .PD . .PP Caller assumes that the VC has enabled all the usable DMA channels. . . .SH POWER TAGS . .SS Unique device IDs .PD 0 .TP 12 .B 0x00000000 SD Card .TP .B 0x00000001 UART0 .TP .B 0x00000002 UART1 .TP .B 0x00000003 USB HCD .TP .B 0x00000004 I2C0 .TP .B 0x00000005 I2C1 .TP .B 0x00000006 I2C2 .TP .B 0x00000007 SPI .TP .B 0x00000008 CCP2TX .TP .B 0x00000009 Unknown (RPi4) .TP .B 0x0000000a Unknown (RPi4) .PD . .SS Get power state .PD 0 .TP 12 .B Tag: 0x00020001 .TP .B Request: 4 bytes .RS .TP .B u32 device id .RE .TP .B Response: 8 bytes .RS .TP .B u32 device id .TP .B u32 state .RS .TP 12 .B Bit 0 0=off, 1=on .TP .B Bit 1 0=device exists, 1=device does not exist .TP .B Bits 2-31 reserved for future use .RE .RE .PD . .PP Response indicates current state. . .SS Get timing .PD 0 .TP 12 .B Tag: 0x00020002 .TP .B Request: 4 bytes .RS .TP .B u32 device id .RE .TP .B Response: 8 bytes .RS .TP .B u32 device id .TP .B u32 enable wait time in microseconds .RE .PD . .PP Response indicates wait time required after turning a device on before power is stable. Returns 0 wait time if the device does not exist. . .SS Set power state .PD 0 .TP 12 .B Tag: 0x00028001 .TP .B Request: 8 bytes .RS .TP .B u32 device id .TP .B u32 state .RS .TP 12 .B Bit 0 0=off, 1=on .TP .B Bit 1 0=do not wait, 1=wait .TP .B Bits 2-31 reserved for future use (set to 0) .RE .RE .TP .B Response: 8 bytes .RS .TP .B u32 device id .TP .B u32 state .RS .TP 12 .B Bit 0 0=off, 1=on .TP .B Bit 1 0=device exists, 1=device does not exist .TP .B Bits 2-31 reserved for future use .RE .RE .PD . .PP Response indicates new state, with/without waiting for the power to become stable. . . .SH CLOCK TAGS . .SS Unique clock IDs .PD 0 .TP 12 .B 0x00000000 reserved .TP .B 0x00000001 EMMC .TP .B 0x00000002 UART .TP .B 0x00000003 ARM .TP .B 0x00000004 CORE .TP .B 0x00000005 V3D .TP .B 0x00000006 H264 .TP .B 0x00000007 ISP .TP .B 0x00000008 SDRAM .TP .B 0x00000009 PIXEL .TP .B 0x0000000a PWM .TP .B 0x0000000b HEVC .TP .B 0x0000000c EMMC2 .TP .B 0x0000000d M2MC .TP .B 0x0000000e PIXEL_BVB .PD . .PP All clocks are the .I base clocks for those peripherals, e.g. 3MHz for UART, 50/100MHz for EMMC, not the dividers applied using the peripheral. . .SS Get clock state .PD 0 .TP 12 .B Tag: 0x00030001 .TP .B Request: 4 bytes .RS .TP .B u32 clock id .RE .TP .B Response: 8 bytes .RS .TP .B u32 clock id .TP .B u32 state .RS .TP 12 .B Bit 0 0=off, 1=on .TP .B Bit 1 0=clock exists, 1=clock does not exist .TP .B Bits 2-31 reserved for future use .RE .RE .PD . .SS Set clock state .PD 0 .TP 12 .B Tag: 0x00038001 .TP .B Request: 8 bytes .RS .TP .B u32 clock id .TP .B u32 state .RS .TP 12 .B Bit 0 0=off, 1=on .TP .B Bit 1 0=clock exists, 1=clock does not exist .TP .B Bits 2-31 reserved for future use (set to 0) .RE .RE .TP .B Response: 8 bytes .RS .TP .B u32 clock id .TP .B u32 state .RS .TP 12 .B Bit 0 0=off, 1=on .TP .B Bit 1 0=clock exists, 1=clock does not exist .TP .B Bits 2-31 reserved for future use .RE .RE .PD . .SS Get clock rate .PD 0 .TP 12 .B Tag: 0x00030002 .TP .B Request: 4 bytes .RS .TP .B u32 clock id .RE .TP .B Response: 8 bytes .RS .TP .B u32 clock id .TP .B u32 rate (in Hz) .RE .PD . .PP Next enable rate should be returned even if the clock is not running. A rate of 0 is returned if the clock does not exist. . .SS Set clock rate .PD 0 .TP 12 .B Tag: 0x00038002 .TP .B Request: 12 bytes .RS .TP .B u32 clock id .TP .B u32 rate (in Hz) .TP .B u32 skip setting turbo .RE .TP .B Response: 8 bytes .RS .TP .B u32 clock id .TP .B u32 rate (in Hz) .RE .PD . .PP Next supported enable rate should be returned even if the clock is not running. A rate of 0 is returned if the clock does not exist. The clock rate may be clamped to the supported range. . .PP By default when setting arm freq above default, other turbo settings will be enabled (e.g. voltage, sdram and gpu frequencies). You can disable this effect by setting "skip setting turbo" to 1. . .SS Get max clock rate .PD 0 .TP 12 .B Tag: 0x00030004 .TP .B Request: 4 bytes .RS .TP .B u32 clock id .RE .TP .B Response: 8 bytes .RS .TP .B u32 clock id .TP .B u32 rate (in Hz) .RE .PD . .PP Return the maximum supported clock rate for the given clock. Clocks should not be set higher than this. . .SS Get min clock rate .PD 0 .TP 12 .B Tag: 0x00030007 .TP .B Request: 4 bytes .RS .TP .B u32 clock id .RE .TP .B Response: 8 bytes .RS .TP .B u32 clock id .TP .B u32 rate (in Hz) .RE .PD . .PP Return the minimum supported clock rate for the given clock. This may be used when idle. . .SS Get turbo .PD 0 .TP 12 .B Tag: 0x00030009 .TP .B Request: 4 bytes .RS .TP .B u32 id .RE .TP .B Response: 8 bytes .RS .TP .B u32 id .TP .B u32 level .RE .PD . .PP Get the turbo state for index id. id should be 0. level will be zero for non-turbo and one for turbo. . .SS Set turbo .PD 0 .TP 12 .B Tag: 0x00038009 .TP .B Request: 8 bytes .RS .TP .B u32 id .TP .B u32 level .RE .TP .B Response: 8 bytes .RS .TP .B u32 id .TP .B u32 level .RE .PD . .PP Set the turbo state for index id. id should be zero. level will be zero for non-turbo and one for turbo. This will cause GPU clocks to be set to maximum when enabled and minimum when disabled. . . .SH VOLTAGE TAGS . .SS Unique voltage IDs .PD 0 .TP 12 .B 0x00000000 reserved .TP .B 0x00000001 Core .TP .B 0x00000002 SDRAM_C .TP .B 0x00000003 SDRAM_P .TP .B 0x00000004 SDRAM_I .PD . .SS Get voltage .PD 0 .TP 12 .B Tag: 0x00030003 .TP .B Request: 4 bytes .RS .TP .B u32 voltage id .RE .TP .B Response: 8 bytes .RS .TP .B u32 voltage id .TP .B u32 value (offset from 1.2V in units of 0.025V) .RE .PD . .PP The voltage value may be clamped to the supported range. A value of 0x80000000 means the id was not valid. . .SS Set voltage .PD 0 .TP 12 .B Tag: 0x00038003 .TP .B Request: 8 bytes .RS .TP .B u32 voltage id .TP .B u32 value (offset from 1.2V in units of 0.025V) .RE .TP .B Response: 8 bytes .RS .TP .B u32 voltage id .TP .B u32 value (offset from 1.2V in units of 0.025V) .RE .PD . .PP The voltage value may be clamped to the supported range. A value of 0x80000000 means the id was not valid. . .SS Get max voltage .PD 0 .TP 12 .B Tag: 0x00030005 .TP .B Request: 4 bytes .RS .TP .B u32 voltage id .RE .TP .B Response: 8 bytes .RS .TP .B u32 voltage id .TP .B u32 value (offset from 1.2V in units of 0.025V) .RE .PD . .PP Return the maximum supported voltage rate for the given id. Voltages should not be set higher than this. . .SS Get min voltage .PD 0 .TP 12 .B Tag: 0x00030008 .TP .B Request: 4 bytes .RS .TP .B u32 voltage id .RE .TP .B Response: 8 bytes .RS .TP .B u32 voltage id .TP .B u32 value (offset from 1.2V in units of 0.025V) .RE .PD . .PP Return the minimum supported voltage rate for the given id. This may be used when idle. . .SS Get temperature .PD 0 .TP 12 .B Tag: 0x00030006 .TP .B Request: 4 bytes .RS .TP .B u32 temperature id .RE .TP .B Response: 8 bytes .RS .TP .B u32 temperature id .TP .B u32 value .RE .PD . .PP Return the temperature of the SoC in thousandths of a degree C. id should be zero. . .SS Get max temperature .PD 0 .TP 12 .B Tag: 0x0003000a .TP .B Request: 4 bytes .RS .TP .B u32 temperature id .RE .TP .B Response: 8 bytes .RS .TP .B u32 temperature id .TP .B u32 value .RE .PD . .PP Return the maximum safe temperature of the SoC in thousandths of a degree C. id should be zero. Overclock may be disabled above this temperature. . .SS Allocate memory .PD 0 .TP 12 .B Tag: 0x0003000c .TP .B Request: 12 bytes .RS .TP .B u32 size .TP .B u32 alignment .TP .B u32 flags .RE .TP .B Response: 4 bytes .RS .TP .B u32 handle .RE .PD . .PP Allocates contiguous memory on the GPU. size and alignment are in bytes. flags contain: . .PP .TS l l l . Name Value Comments \_ \_ \_ MEM_FLAG_NORMAL 0 normal allocating alias; don't use from ARM MEM_FLAG_DISCARDABLE 1 << 0 can be resized to 0 at any time; use for cached data MEM_FLAG_DIRECT 1 << 2 0xC alias uncached MEM_FLAG_COHERENT 1 << 3 0x8 alias; non-allocating in L2 but coherent MEM_FLAG_L1_NONALLOCATING 3 << 2 Allocating in L2 MEM_FLAG_ZERO 1 << 4 Initialise buffer to all zeros MEM_FLAG_NO_INIT 1 << 5 Don't initialise; default is initialise to all ones MEM_FLAG_HINT_PERMALOCK 1 << 6 Likely to be locked for long periods of time .TE . .SS Lock memory .PD 0 .TP 12 .B Tag: 0x0003000d .TP .B Request: 4 bytes .RS .TP .B u32 handle .RE .TP .B Response: 4 bytes .RS .TP .B u32 bus address .RE .PD . .PP Lock buffer in place, and return a bus address. Must be done before memory can be accessed . .SS Unlock memory .PD 0 .TP 12 .B Tag: 0x0003000e .TP .B Request: 4 bytes .RS .TP .B u32 handle .RE .TP .B Response: 4 bytes .RS .TP .B u32 status .RE .PD . .PP Unlock buffer. It retains contents, but may move. Needs to be locked before next use. status=0 is success. . .SS Release Memory .PD 0 .TP 12 .B Tag: 0x0003000f .TP .B Request: 4 bytes .RS .TP .B u32 handle .RE .TP .B Response: 4 bytes .RS .TP .B u32 status .RE .PD . .PP Free the memory buffer. status=0 is success. . .SS Execute Code .PD 0 .TP 12 .B Tag: 0x00030010 .TP .B Request: 28 bytes .RS .TP .B u32 function pointer .TP .B u32 r0 .TP .B u32 r1 .TP .B u32 r2 .TP .B u32 r3 .TP .B u32 r4 .TP .B u32 r5 .RE .TP .B Response: 4 bytes .RS .TP .B u32 r0 .RE .PD . .PP Calls the function at given (bus) address and with arguments given. E.g. r0 = fn(r0, r1, r2, r3, r4, r5); It blocks until call completes. The (GPU) instruction cache is implicitly flushed. Setting the lsb of function pointer address will suppress the instruction cache flush if you know the buffer hasn't changed since last execution. . .SS Get Dispmanx Resource mem handle .PD 0 .TP 12 .B Tag: 0x00030014 .TP .B Request: 4 bytes .RS .TP .B u32 dispmanx resource handle .RE .TP .B Response: 8 bytes .RS .TP .B u32 0 is successful .TP .B u32 mem handle for resource .RE .PD . .PP Gets the mem_handle associated with a created dispmanx resource. This can be locked and the memory directly written from the arm to avoid having to copy the image data to GPU. . .SS Get EDID block .PD 0 .TP 12 .B Tag: 0x00030020 .TP .B Request: 4 bytes .RS .TP .B u32 block number .RE .TP .B Response: 136 bytes .RS .TP .B u32 block number .TP .B u32 status .TP .B u8\|.\|.\|. EDID block .RE .PD . .PP This reads the specified EDID block from attached HDMI/DVI device. There will always be at least one block of 128 bytes, but there may be additional blocks. You should keep requesting blocks (starting from 0) until the status returned is non-zero. . . .SH FRAME BUFFER TAGS . .IP \(bu 3 All tags in the request are processed in one operation. .IP \(bu It is not valid to mix Test tags with Get/Set tags in the same operation and no tags will be returned. .IP \(bu Get tags will be processed after all Set tags. .IP \(bu If an allocate buffer tag is omitted when setting parameters, then no change occurs unless it can be accommodated without changing the buffer base or size. .IP \(bu When an allocate buffer response is returned, the old buffer area (if the base or size has changed) is implicitly freed. . .PP For example: . .IP 1. 3 The current values/defaults are loaded into a temporary struct . .IP 2. The tags are used to overwrite some or all of the values . .IP 3. Validation of Test/Set tags occurs . .IP 4. The Set changes are applied and responses based on the requested Get/Test/Set tags are written to the buffer . Duplicating the same tag in one request/response is prohibited. The expected result is either an error or implementation specified undefined behaviour (such as only using the last instance of the tag). .PD . .SS Allocate buffer .PD 0 .TP 12 .B Tag: 0x00040001 .TP .B Request: 4 bytes .RS .TP .B u32 alignment in bytes .RE .TP .B Response: 8 bytes .RS .TP .B u32 frame buffer base address in bytes .TP .B u32 frame buffer size in bytes .RE .PD . .PP If the requested alignment is unsupported then the current base and size (which may be 0 if not allocated) is returned and no change occurs. . .SS Release buffer .PD 0 .TP 12 .B Tag: 0x00048001 .TP .B Request: 0 bytes .TP .B Response: 0 bytes .PD . .PP Releases and disables the frame buffer. . .SS Blank screen .PD 0 .TP 12 .B Tag: 0x00040002 .TP .B Request: 4 bytes .RS .TP .B u32 state .RS .TP 12 .B Bit 0 0=off, 1=on .TP .B Bits 1-31 reserved for future use (set to 0) .RE .RE .TP .B Response: 4 bytes .RS .TP .B u32 state .RS .TP 12 .B Bit 0 0=off, 1=on .TP .B Bits 1-31 reserved for future use .RE .RE .PD . .SS Get physical (display) width/height .PD 0 .TP 12 .B Tag: 0x00040003 .TP .B Request: 0 bytes .TP .B Response: 8 bytes .RS .TP .B u32 width in pixels .TP .B u32 height in pixels .RE .PD . .PP Note that the "physical (display)" size is the size of the allocated buffer in memory, not the resolution of the video signal sent to the display device. . .SS Test physical (display) width/height .PD 0 .TP 12 .B Tag: 0x00044003 .TP .B Request: 8 bytes .RS .TP .B u32 width in pixels .TP .B u32 height in pixels .RE .TP .B Response: 8 bytes .RS .TP .B u32 width in pixels .TP .B u32 height in pixels .RE .PD . .PP Response is the same as the request (or modified), to indicate if this configuration is supported (in combination with all the other settings). Does not modify the current hardware or frame buffer state. . .SS Set physical (display) width/height .PD 0 .TP 12 .B Tag: 0x00048003 .TP .B Request: 8 bytes .RS .TP .B u32 width in pixels .TP .B u32 height in pixels .RE .TP .B Response: 8 bytes .RS .TP .B u32 width in pixels .TP .B u32 height in pixels .RE .PD . .PP The response may not be the same as the request so it must be checked. May be the previous width/height or 0 for unsupported. . . .SS Get virtual (buffer) width/height .PD 0 .TP 12 .B Tag: 0x00040004 .TP .B Request: 0 bytes .TP .B Response: 8 bytes .RS .TP .B u32 width in pixels .TP .B u32 height in pixels .RE .PD . .PP Note that the "virtual (buffer)" size is the portion of buffer that is sent to the display device, not the resolution the buffer itself. This may be smaller than the allocated buffer size in order to implement panning. . .SS Test virtual (buffer) width/height .PD 0 .TP 12 .B Tag: 0x00044004 .TP .B Request: 8 bytes .RS .TP .B u32 width in pixels .TP .B u32 height in pixels .RE .TP .B Response: 8 bytes .RS .TP .B u32 width in pixels .TP .B u32 height in pixels .RE .PD . .PP Response is the same as the request (or modified), to indicate if this configuration is supported (in combination with all the other settings). Does not modify the current hardware or frame buffer state. . .SS Set virtual (buffer) width/height .PD 0 .TP 12 .B Tag: 0x00048004 .TP .B Request: 8 bytes .RS .TP .B u32 width in pixels .TP .B u32 height in pixels .RE .TP .B Response: 8 bytes .RS .TP .B u32 width in pixels .TP .B u32 height in pixels .RE .PD . .PP The response may not be the same as the request so it must be checked. May be the previous width/height or 0 for unsupported. . .SS Get depth .PD 0 .TP 12 .B Tag: 0x00040005 .TP .B Request: 0 bytes .TP .B Response: 4 bytes .RS .TP .B u32 bits per pixel .RE .PD . .PP Test depth .PD 0 .TP 12 .B Tag: 0x00044005 .TP .B Request: 4 bytes .RS .TP .B u32 bits per pixel .RE .TP .B Response: 4 bytes .RS .TP .B u32 bits per pixel .RE .PD . .PP Response is the same as the request (or modified), to indicate if this configuration is supported (in combination with all the other settings). Does not modify the current hardware or frame buffer state. . .SS Set depth .PD 0 .TP 12 .B Tag: 0x00048005 .TP .B Request: 4 bytes .RS .TP .B u32 bits per pixel .RE .TP .B Response: 4 bytes .RS .TP .B u32 bits per pixel .RE .PD . .PP The response may not be the same as the request so it must be checked. May be the previous depth or 0 for unsupported. . .SS Get pixel order .PD 0 .TP 12 .B Tag: 0x00040006 .TP .B Request: 0 bytes .TP .B Response: 4 bytes .RS .TP .B u32 state .RS .TP .B 0x0 BGR .TP .B 0x1 RGB .RE .RE .PD . .SS Test pixel order .PD 0 .TP 12 .B Tag: 0x00044006 .TP .B Request: 4 bytes .RS .TP .B u32 state (as above) .RE .TP .B Response: 4 bytes .RS .TP .B u32 state (as above) .RE .PD . .PP Response is the same as the request (or modified), to indicate if this configuration is supported (in combination with all the other settings). Does not modify the current hardware or frame buffer state. . .SS Set pixel order .PD 0 .TP 12 .B Tag: 0x00048006 .TP .B Request: 4 bytes .RS .TP .B u32 state (as above) .RE .TP .B Response: 4 bytes .RS .TP .B u32 state (as above) .RE .PD . .PP The response may not be the same as the request so it must be checked. . .SS Get alpha mode .PD 0 .TP 12 .B Tag: 0x00040007 .TP .B Request: 0 bytes .TP .B Response: 4 bytes .RS .TP .B u32 state .RS .TP .B 0x0 Alpha channel enabled (0 = fully opaque) .TP .B 0x1 Alpha channel reversed (0 = fully transparent) .TP .B 0x2 Alpha channel ignored .RE .RE .PD . .SS Test alpha mode .PD 0 .TP 12 .B Tag: 0x00044007 .TP .B Request: 4 bytes .RS .TP .B u32 state (as above) .RE .TP .B Response: 4 bytes .RS .TP .B u32 state (as above) .RE .PD . .PP Response is the same as the request (or modified), to indicate if this configuration is supported (in combination with all the other settings). Does not modify the current hardware or frame buffer state. . .SS Set alpha mode .PD 0 .TP 12 .B Tag: 0x00048007 .TP .B Request: 4 bytes .RS .TP .B u32 state (as above) .RE .TP .B Response: 4 bytes .RS .TP .B u32 state (as above) .RE .PD . .PP The response may not be the same as the request so it must be checked. . .SS Get pitch .PD 0 .TP 12 .B Tag: 0x00040008 .TP .B Request: 0 bytes .TP .B Response: 4 bytes .RS .TP .B u32 bytes per line .RE .PD . .SS Get virtual offset .PD 0 .TP 12 .B Tag: 0x00040009 .TP .B Request: 0 bytes .TP .B Response: 8 bytes .RS .TP .B u32 X in pixels .TP .B u32 Y in pixels .RE .PD . .SS Test virtual offset .PD 0 .TP 12 .B Tag: 0x00044009 .TP .B Request: 8 bytes .RS .TP .B u32 X in pixels .TP .B u32 Y in pixels .RE .TP .B Response: 8 bytes .RS .TP .B u32 X in pixels .TP .B u32 Y in pixels .RE .PD . .PP Response is the same as the request (or modified), to indicate if this configuration is supported (in combination with all the other settings). Does not modify the current hardware or frame buffer state. . .SS Set virtual offset .PD 0 .TP 12 .B Tag: 0x00048009 .TP .B Request: 8 bytes .RS .TP .B u32 X in pixels .TP .B u32 Y in pixels .RE .TP .B Response: 8 bytes .RS .TP .B u32 X in pixels .TP .B u32 Y in pixels .RE .PD . .PP The response may not be the same as the request so it must be checked. May be the previous offset or 0 for unsupported. . .SS Get overscan .PD 0 .TP 12 .B Tag: 0x0004000a .TP .B Request: 0 bytes .TP .B Response: 16 bytes .RS .TP .B u32 top in pixels .TP .B u32 bottom in pixels .TP .B u32 left in pixels .TP .B u32 right in pixels .RE .PD . .SS Test overscan .PD 0 .TP 12 .B Tag: 0x0004400a .TP .B Request: 16 bytes .RS .TP .B u32 top in pixels .TP .B u32 bottom in pixels .TP .B u32 left in pixels .TP .B u32 right in pixels .RE .TP .B Response: 16 bytes .RS .TP .B u32 top in pixels .TP .B u32 bottom in pixels .TP .B u32 left in pixels .TP .B u32 right in pixels .RE .PD . .PP Response is the same as the request (or modified), to indicate if this configuration is supported (in combination with all the other settings). Does not modify the current hardware or frame buffer state. . .SS Set overscan .PD 0 .TP 12 .B Tag: 0x0004800a .TP .B Request: 16 bytes .RS .TP .B u32 top in pixels .TP .B u32 bottom in pixels .TP .B u32 left in pixels .TP .B u32 right in pixels .RE .TP .B Response: 16 bytes .RS .TP .B u32 top in pixels .TP .B u32 bottom in pixels .TP .B u32 left in pixels .TP .B u32 right in pixels .RE .PD . .PP The response may not be the same as the request so it must be checked. May be the previous overscan or 0 for unsupported. . .SS Get palette .PD 0 .TP 12 .B Tag: 0x0004000b .TP .B Request: 0 bytes .TP .B Response: 1024 bytes .RS .TP .B u32... RGBA palette values (index 0 to 255) .RE .PD . .SS Test palette .PD 0 .TP 12 .B Tag: 0x0004400b .TP .B Request: 24 bytes..1032 bytes .RS .TP .B u32 offset: first palette index to set (0-255) .TP .B u32 length: number of palette entries to set (1-256) .TP .B u32... RGBA palette values (offset to offset+length-1) .RE .TP .B Response: 4 bytes .RS .TP .B u32 0=valid, 1=invalid .RE .PD . .PP Response is the same as the request (or modified), to indicate if this configuration is supported (in combination with all the other settings). Does not modify the current hardware or frame buffer state. . .SS Set palette .PD 0 .TP 12 .B Tag: 0x0004800b .TP .B Request: 24 bytes..1032 bytes .RS .TP .B u32 offset: first palette index to set (0-255) .TP .B u32 length: number of palette entries to set (1-256) .TP .B u32... RGBA palette values (offset to offset+length-1) .RE .TP .B Response: 4 bytes .RS .TP .B u32 0=valid, 1=invalid .RE .PD . .PP The response may not be the same as the request so it must be checked. Palette changes should not be partially applied. . .SS Set Cursor Info .PD 0 .TP 12 .B Tag: 0x00008010 .TP .B Request: 24 bytes .RS .TP .B u32 width .TP .B u32 height .TP .B u32 (unused) .TP .B u32 pointer to pixels .TP .B u32 hotspotX .TP .B u32 hotspotY .RE .TP .B Response: 4 bytes .RS .TP .B u32 0=valid, 1=invalid .RE .PD . .PP Format is 32bpp (ARGB). Width and height should be >= 16 and (width * height) <= 64. . .SS Set Cursor State .PD 0 .TP 12 .B Tag: 0x00008011 .TP .B Request: 16 bytes .RS .TP .B u32 enable (1=visible, 0=invisible) .TP .B u32 x .TP .B u32 y .TP .B u32 flags; 0=display coords, 1=framebuffer coords .RE .TP .B Response: 4 bytes .RS .TP .B u32 0=valid, 1=invalid .RE .PD . .PP if Set Cursor Info hasn't been called a default cursor will be used (64x64 with hotspot at 0,0). raspi-utils-20240903/vcmailbox/vcmailbox.c000077500000000000000000000062041466562725600203770ustar00rootroot00000000000000/* Copyright (c) 2015 Raspberry Pi (Trading) Ltd. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // Utility for driving mailbox interface to VideoCore #include #include #include #include #include #include #include #include #include /* ioctl */ #define DEVICE_FILE_NAME "/dev/vcio" #define MAJOR_NUM 100 #define IOCTL_MBOX_PROPERTY _IOWR(MAJOR_NUM, 0, char *) /* * use ioctl to send mbox property message */ static int mbox_property(int file_desc, void *buf) { int ret_val = ioctl(file_desc, IOCTL_MBOX_PROPERTY, buf); if (ret_val < 0) { printf("ioctl_set_msg failed:%d\n", ret_val); } return ret_val; } static int mbox_open() { int file_desc; // open a char device file used for communicating with kernel mbox driver file_desc = open(DEVICE_FILE_NAME, 0); if (file_desc < 0) { printf("Can't open device file: %s\n", DEVICE_FILE_NAME); printf("Try creating a device file with: sudo mknod %s c %d 0\n", DEVICE_FILE_NAME, MAJOR_NUM); exit(-1); } return file_desc; } static void mbox_close(int file_desc) { close(file_desc); } #define MAX_WORDS 256 int main(int argc, char *argv[]) { int mb = mbox_open(); unsigned words[MAX_WORDS] = {}; int i; if (argc < 2 || argc+1 >= MAX_WORDS) { printf("Usage %s [word0] [word1] ...\n", argv[0]); return 1; } words[0] = (argc+2) * sizeof words[0]; words[1] = 0; // process request for (i=1; i