From b493b664b00dab0393d74b8217bda429ebdd6a5b Mon Sep 17 00:00:00 2001 From: Bjarki Arge Andreasen Date: Sun, 21 May 2023 20:54:45 +0200 Subject: [PATCH 1/6] device: Add device subsystem enumeration script The device enumeration feature requires all devices to place their API implementation in linker sections by api type. This commit adds a script which uses the tag __subsystem to identify all existing driver API types and generate iterable sections for them. The script is invoked from the top CMakeLists.txt Signed-off-by: Bjarki Arge Andreasen Co-authored-by: Pieter De Gendt Signed-off-by: Pieter De Gendt --- CMakeLists.txt | 24 ++++++++++ .../common-rom/common-rom-kernel-devices.ld | 2 + scripts/build/gen_iter_sections.py | 48 +++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 scripts/build/gen_iter_sections.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 88b2d77c3a4d5..7728b2bc14f82 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,6 +88,7 @@ set(SYSCALL_LIST_H_TARGET syscall_list_h_target) set(DRIVER_VALIDATION_H_TARGET driver_validation_h_target) set(KOBJ_TYPES_H_TARGET kobj_types_h_target) set(PARSE_SYSCALLS_TARGET parse_syscalls_target) +set(DEVICE_API_LD_TARGET device_api_ld_target) define_property(GLOBAL PROPERTY PROPERTY_OUTPUT_FORMAT BRIEF_DOCS " " FULL_DOCS " ") set_property( GLOBAL PROPERTY PROPERTY_OUTPUT_FORMAT elf32-little${ARCH}) # BFD format @@ -882,6 +883,28 @@ add_custom_target(${DRIVER_VALIDATION_H_TARGET} DEPENDS ${DRV_VALIDATION}) include(${ZEPHYR_BASE}/cmake/kobj.cmake) gen_kobj(KOBJ_INCLUDE_PATH) +# Generate sections for kernel device subsystems +set( + DEVICE_API_LD_SECTIONS + ${CMAKE_CURRENT_BINARY_DIR}/include/generated/device-api-sections.ld + ) + +add_custom_command( + OUTPUT ${DEVICE_API_LD_SECTIONS} + COMMAND + ${PYTHON_EXECUTABLE} + ${ZEPHYR_BASE}/scripts/build/gen_iter_sections.py + --input ${struct_tags_json} + --tag __subsystem + --ld-output ${DEVICE_API_LD_SECTIONS} + DEPENDS + ${ZEPHYR_BASE}/scripts/build/gen_iter_sections.py + ${struct_tags_json} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + +add_custom_target(${DEVICE_API_LD_TARGET} DEPENDS ${DEVICE_API_LD_SECTIONS}) + # Add a pseudo-target that is up-to-date when all generated headers # are up-to-date. @@ -912,6 +935,7 @@ add_dependencies(zephyr_interface ${SYSCALL_LIST_H_TARGET} ${DRIVER_VALIDATION_H_TARGET} ${KOBJ_TYPES_H_TARGET} + ${DEVICE_API_LD_TARGET} ) add_custom_command( diff --git a/include/zephyr/linker/common-rom/common-rom-kernel-devices.ld b/include/zephyr/linker/common-rom/common-rom-kernel-devices.ld index b073fcdab85b9..c6920b5bec0fa 100644 --- a/include/zephyr/linker/common-rom/common-rom-kernel-devices.ld +++ b/include/zephyr/linker/common-rom/common-rom-kernel-devices.ld @@ -102,3 +102,5 @@ #include } GROUP_ROM_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) #endif /* !CONFIG_DEVICE_DEPS_DYNAMIC */ + +#include diff --git a/scripts/build/gen_iter_sections.py b/scripts/build/gen_iter_sections.py new file mode 100644 index 0000000000000..98ce5c72d1e2a --- /dev/null +++ b/scripts/build/gen_iter_sections.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2023 Bjarki Arge Andreasen +# +# SPDX-License-Identifier: Apache-2.0 +""" +Script to generate iterable sections from JSON encoded dictionary containing lists of items. +""" + +import argparse +import json + + +def get_tagged_items(filepath: str, tag: str) -> list: + with open(filepath) as fp: + return json.load(fp)[tag] + + +def gen_ld(filepath: str, items: list): + with open(filepath, "w") as fp: + for item in items: + fp.write(f"ITERABLE_SECTION_ROM({item}, Z_LINK_ITERABLE_SUBALIGN)\n") + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + allow_abbrev=False, + ) + + parser.add_argument("-i", "--input", required=True, help="Path to input list of tags") + parser.add_argument("-t", "--tag", required=True, help="Tag to generate iterable sections for") + parser.add_argument("-l", "--ld-output", required=True, help="Path to output linker file") + + return parser.parse_args() + + +def main(): + args = parse_args() + + items = get_tagged_items(args.input, args.tag) + + gen_ld(args.ld_output, items) + + +if __name__ == "__main__": + main() From b9a1bf97fb62ce7516dd4d46eb546fe09b220fd6 Mon Sep 17 00:00:00 2001 From: Pieter De Gendt Date: Tue, 23 Apr 2024 08:32:08 +0200 Subject: [PATCH 2/6] device: Add DEVICE_API macros Add macro definitions for device drivers to instantiate API structs into iterable sections and to evaluate them. Signed-off-by: Pieter De Gendt --- include/zephyr/device.h | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/include/zephyr/device.h b/include/zephyr/device.h index f9e134111ebae..13cd31f326f9b 100644 --- a/include/zephyr/device.h +++ b/include/zephyr/device.h @@ -1195,8 +1195,47 @@ device_get_dt_nodelabels(const struct device *dev) DT_FOREACH_STATUS_OKAY_NODE(Z_MAYBE_DEVICE_DECLARE_INTERNAL) +/** @brief Expands to the full type. */ +#define Z_DEVICE_API_TYPE(_class) _CONCAT(_class, _driver_api) + /** @endcond */ +/** + * @brief Wrapper macro for declaring device API structs inside iterable sections. + * + * @param _class The device API class. + * @param _name The API instance name. + */ +#define DEVICE_API(_class, _name) const STRUCT_SECTION_ITERABLE(Z_DEVICE_API_TYPE(_class), _name) + +/** + * @brief Expands to the pointer of a device's API for a given class. + * + * @param _class The device API class. + * @param _dev The device instance pointer. + * + * @return the pointer to the device API. + */ +#define DEVICE_API_GET(_class, _dev) ((const struct Z_DEVICE_API_TYPE(_class) *)_dev->api) + +/** + * @brief Macro that evaluates to a boolean that can be used to check if + * a device is of a particular class. + * + * @param _class The device API class. + * @param _dev The device instance pointer. + * + * @retval true If the device is of the given class + * @retval false If the device is not of the given class + */ +#define DEVICE_API_IS(_class, _dev) \ + ({ \ + STRUCT_SECTION_START_EXTERN(Z_DEVICE_API_TYPE(_class)); \ + STRUCT_SECTION_END_EXTERN(Z_DEVICE_API_TYPE(_class)); \ + (DEVICE_API_GET(_class, _dev) < STRUCT_SECTION_END(Z_DEVICE_API_TYPE(_class)) && \ + DEVICE_API_GET(_class, _dev) >= STRUCT_SECTION_START(Z_DEVICE_API_TYPE(_class))); \ + }) + #ifdef __cplusplus } #endif From 423972f6e292a4bb0c7ca7451fd46a0cef2f2b42 Mon Sep 17 00:00:00 2001 From: Torsten Rasmussen Date: Mon, 18 Nov 2024 15:00:47 +0100 Subject: [PATCH 3/6] cmake: create CMake pre-load file for calling linker script generator Create a CMake preload file with linker settings which is then passed to the linker script generator as preload file. This removes the need for command invocation with long arguments. Signed-off-by: Torsten Rasmussen --- cmake/linker/armlink/target.cmake | 19 ++++++++++++++----- cmake/linker/ld/target.cmake | 23 +++++++++++++++-------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/cmake/linker/armlink/target.cmake b/cmake/linker/armlink/target.cmake index b2e1e867f904a..17f61eec8ffba 100644 --- a/cmake/linker/armlink/target.cmake +++ b/cmake/linker/armlink/target.cmake @@ -18,6 +18,9 @@ macro(configure_linker_script linker_script_gen linker_pass_define) set(STEERING_FILE_ARG) set(STEERING_C_ARG) set(linker_pass_define_list ${linker_pass_define}) + set(cmake_linker_script_settings + ${PROJECT_BINARY_DIR}/include/generated/ld_script_settings_${linker_pass_define}.cmake + ) if("LINKER_ZEPHYR_FINAL" IN_LIST linker_pass_define_list) set(STEERING_FILE ${CMAKE_CURRENT_BINARY_DIR}/armlink_symbol_steering.steer) @@ -26,17 +29,23 @@ macro(configure_linker_script linker_script_gen linker_pass_define) set(STEERING_C_ARG "-DSTEERING_C=${STEERING_C}") endif() + file(GENERATE OUTPUT ${cmake_linker_script_settings} CONTENT + "set(FORMAT \"$\" CACHE INTERNAL \"\")\n + set(ENTRY \"$\" CACHE INTERNAL \"\")\n + set(MEMORY_REGIONS \"$\" CACHE INTERNAL \"\")\n + set(GROUPS \"$\" CACHE INTERNAL \"\")\n + set(SECTIONS \"$\" CACHE INTERNAL \"\")\n + set(SECTION_SETTINGS \"$\" CACHE INTERNAL \"\")\n + set(SYMBOLS \"$\" CACHE INTERNAL \"\")\n + " + ) add_custom_command( OUTPUT ${linker_script_gen} ${STEERING_FILE} ${STEERING_C} COMMAND ${CMAKE_COMMAND} + -C ${cmake_linker_script_settings} -DPASS="${linker_pass_define}" - -DMEMORY_REGIONS="$" - -DGROUPS="$" - -DSECTIONS="$" - -DSECTION_SETTINGS="$" - -DSYMBOLS="$" ${STEERING_FILE_ARG} ${STEERING_C_ARG} -DOUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/${linker_script_gen} diff --git a/cmake/linker/ld/target.cmake b/cmake/linker/ld/target.cmake index 5e0a117c01436..47a605cf1dac7 100644 --- a/cmake/linker/ld/target.cmake +++ b/cmake/linker/ld/target.cmake @@ -18,22 +18,29 @@ endif() # NOTE: ${linker_script_gen} will be produced at build-time; not at configure-time macro(configure_linker_script linker_script_gen linker_pass_define) set(extra_dependencies ${ARGN}) + set(cmake_linker_script_settings + ${PROJECT_BINARY_DIR}/include/generated/ld_script_settings_${linker_pass_define}.cmake + ) if(CONFIG_CMAKE_LINKER_GENERATOR) + file(GENERATE OUTPUT ${cmake_linker_script_settings} CONTENT + "set(FORMAT \"$\" CACHE INTERNAL \"\")\n + set(ENTRY \"$\" CACHE INTERNAL \"\")\n + set(MEMORY_REGIONS \"$\" CACHE INTERNAL \"\")\n + set(GROUPS \"$\" CACHE INTERNAL \"\")\n + set(SECTIONS \"$\" CACHE INTERNAL \"\")\n + set(SECTION_SETTINGS \"$\" CACHE INTERNAL \"\")\n + set(SYMBOLS \"$\" CACHE INTERNAL \"\")\n + " + ) add_custom_command( OUTPUT ${linker_script_gen} COMMAND ${CMAKE_COMMAND} + -C ${cmake_linker_script_settings} -DPASS="${linker_pass_define}" - -DFORMAT="$" - -DENTRY="$" - -DMEMORY_REGIONS="$" - -DGROUPS="$" - -DSECTIONS="$" - -DSECTION_SETTINGS="$" - -DSYMBOLS="$" -DOUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/${linker_script_gen} -P ${ZEPHYR_BASE}/cmake/linker/ld/ld_script.cmake - ) + ) else() set(template_script_defines ${linker_pass_define}) list(TRANSFORM template_script_defines PREPEND "-D") From c111c4eff8492124b2e72a77049aae45a273347b Mon Sep 17 00:00:00 2001 From: Torsten Rasmussen Date: Mon, 18 Nov 2024 15:42:30 +0100 Subject: [PATCH 4/6] device: export CMake pre-load from device subsystem enumeration script Extend the device subsystem enumeration script to produce a CMake pre-load script. This allow CMake linker generator scripts to create iterable sections based on output from device subsystem enumeration. This ensures that same functionality is available in both ld linker templates and the linker generator. Update linker generators to support the use of the device subsystem enumeration CMake pre-load script. Signed-off-by: Torsten Rasmussen --- CMakeLists.txt | 13 +++++++++-- cmake/linker/armlink/target.cmake | 2 ++ cmake/linker/ld/target.cmake | 2 ++ cmake/linker/linker_script_common.cmake | 4 ++-- scripts/build/gen_iter_sections.py | 31 ++++++++++++++++++++++--- 5 files changed, 45 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7728b2bc14f82..97af4dce94576 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -889,21 +889,30 @@ set( ${CMAKE_CURRENT_BINARY_DIR}/include/generated/device-api-sections.ld ) +set(DEVICE_API_LINKER_SECTIONS_CMAKE + ${CMAKE_CURRENT_BINARY_DIR}/include/generated/device-api-sections.cmake +) + add_custom_command( - OUTPUT ${DEVICE_API_LD_SECTIONS} + OUTPUT ${DEVICE_API_LD_SECTIONS} ${DEVICE_API_LINKER_SECTIONS_CMAKE} COMMAND ${PYTHON_EXECUTABLE} ${ZEPHYR_BASE}/scripts/build/gen_iter_sections.py + --alignment ${CONFIG_LINKER_ITERABLE_SUBALIGN} --input ${struct_tags_json} --tag __subsystem --ld-output ${DEVICE_API_LD_SECTIONS} + --cmake-output ${DEVICE_API_LINKER_SECTIONS_CMAKE} DEPENDS ${ZEPHYR_BASE}/scripts/build/gen_iter_sections.py ${struct_tags_json} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) -add_custom_target(${DEVICE_API_LD_TARGET} DEPENDS ${DEVICE_API_LD_SECTIONS}) +add_custom_target(${DEVICE_API_LD_TARGET} + DEPENDS ${DEVICE_API_LD_SECTIONS} + ${DEVICE_API_LINKER_SECTIONS_CMAKE} +) # Add a pseudo-target that is up-to-date when all generated headers # are up-to-date. diff --git a/cmake/linker/armlink/target.cmake b/cmake/linker/armlink/target.cmake index 17f61eec8ffba..be2bb8e89778f 100644 --- a/cmake/linker/armlink/target.cmake +++ b/cmake/linker/armlink/target.cmake @@ -44,12 +44,14 @@ macro(configure_linker_script linker_script_gen linker_pass_define) ${STEERING_FILE} ${STEERING_C} COMMAND ${CMAKE_COMMAND} + -C ${DEVICE_API_LINKER_SECTIONS_CMAKE} -C ${cmake_linker_script_settings} -DPASS="${linker_pass_define}" ${STEERING_FILE_ARG} ${STEERING_C_ARG} -DOUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/${linker_script_gen} -P ${ZEPHYR_BASE}/cmake/linker/armlink/scatter_script.cmake + DEPENDS ${DEVICE_API_LD_TARGET} ) if("LINKER_ZEPHYR_FINAL" IN_LIST linker_pass_define_list) diff --git a/cmake/linker/ld/target.cmake b/cmake/linker/ld/target.cmake index 47a605cf1dac7..bdeecd0b0b312 100644 --- a/cmake/linker/ld/target.cmake +++ b/cmake/linker/ld/target.cmake @@ -36,10 +36,12 @@ macro(configure_linker_script linker_script_gen linker_pass_define) add_custom_command( OUTPUT ${linker_script_gen} COMMAND ${CMAKE_COMMAND} + -C ${DEVICE_API_LINKER_SECTIONS_CMAKE} -C ${cmake_linker_script_settings} -DPASS="${linker_pass_define}" -DOUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/${linker_script_gen} -P ${ZEPHYR_BASE}/cmake/linker/ld/ld_script.cmake + DEPENDS ${DEVICE_API_LD_TARGET} ) else() set(template_script_defines ${linker_pass_define}) diff --git a/cmake/linker/linker_script_common.cmake b/cmake/linker/linker_script_common.cmake index ef5ded66fdd30..017de14cf6bf4 100644 --- a/cmake/linker/linker_script_common.cmake +++ b/cmake/linker/linker_script_common.cmake @@ -132,7 +132,7 @@ function(create_section) set(INDEX 100) set(settings_single "ALIGN;ANY;FIRST;KEEP;OFFSET;PRIO;SECTION;SORT") set(settings_multi "FLAGS;INPUT;PASS;SYMBOLS") - foreach(settings ${SECTION_SETTINGS}) + foreach(settings ${SECTION_SETTINGS} ${DEVICE_API_SECTION_SETTINGS}) if("${settings}" MATCHES "^{(.*)}$") cmake_parse_arguments(SETTINGS "" "${settings_single}" "${settings_multi}" ${CMAKE_MATCH_1}) @@ -652,7 +652,7 @@ foreach(group ${GROUPS}) endif() endforeach() -foreach(section ${SECTIONS}) +foreach(section ${SECTIONS} ${DEVICE_API_SECTIONS}) if("${section}" MATCHES "^{(.*)}$") create_section(${CMAKE_MATCH_1} SYSTEM ${new_system}) endif() diff --git a/scripts/build/gen_iter_sections.py b/scripts/build/gen_iter_sections.py index 98ce5c72d1e2a..cca0ea11eb97e 100644 --- a/scripts/build/gen_iter_sections.py +++ b/scripts/build/gen_iter_sections.py @@ -16,10 +16,30 @@ def get_tagged_items(filepath: str, tag: str) -> list: return json.load(fp)[tag] -def gen_ld(filepath: str, items: list): +def gen_ld(filepath: str, items: list, alignment: int): with open(filepath, "w") as fp: for item in items: - fp.write(f"ITERABLE_SECTION_ROM({item}, Z_LINK_ITERABLE_SUBALIGN)\n") + fp.write(f"ITERABLE_SECTION_ROM({item}, {alignment})\n") + + +def gen_cmake(filepath: str, items: list, alignment: int): + with open(filepath, "w") as fp: + for item in items: + fp.write( + f'list(APPEND sections "{{NAME\\;{item}_area\\;' + + 'GROUP\\;RODATA_REGION\\;' + + f'SUBALIGN\\;{alignment}\\;' + + 'NOINPUT\\;TRUE}")\n' + ) + fp.write( + f'list(APPEND section_settings "{{SECTION\\;{item}_area\\;' + + 'SORT\\;NAME\\;' + + 'KEEP\\;TRUE\\;' + + f'INPUT\\;._{item}.static.*\\;' + + f'SYMBOLS\\;_{item}_list_start\\;_{item}_list_end}}")\n' + ) + fp.write('set(DEVICE_API_SECTIONS "${sections}" CACHE INTERNAL "")\n') + fp.write('set(DEVICE_API_SECTION_SETTINGS "${section_settings}" CACHE INTERNAL "")\n') def parse_args() -> argparse.Namespace: @@ -30,8 +50,12 @@ def parse_args() -> argparse.Namespace: ) parser.add_argument("-i", "--input", required=True, help="Path to input list of tags") + parser.add_argument("-a", "--alignment", required=True, help="Iterable section alignment") parser.add_argument("-t", "--tag", required=True, help="Tag to generate iterable sections for") parser.add_argument("-l", "--ld-output", required=True, help="Path to output linker file") + parser.add_argument( + "-c", "--cmake-output", required=True, help="Path to CMake linker script inclusion file" + ) return parser.parse_args() @@ -41,7 +65,8 @@ def main(): items = get_tagged_items(args.input, args.tag) - gen_ld(args.ld_output, items) + gen_ld(args.ld_output, items, args.alignment) + gen_cmake(args.cmake_output, items, args.alignment) if __name__ == "__main__": From 2165649b6ea6ea49eabfac4f75190c2d4d8f1c66 Mon Sep 17 00:00:00 2001 From: Pieter De Gendt Date: Wed, 24 Apr 2024 14:43:45 +0200 Subject: [PATCH 5/6] tests: kernel: device: Add API sections tests Add tests for the subsystem_api to validate runtime API section checks. Signed-off-by: Pieter De Gendt --- tests/kernel/device/prj.conf | 1 + tests/kernel/device/src/abstract_driver.c | 18 +++++----- tests/kernel/device/src/abstract_driver.h | 42 +++++++++++++---------- tests/kernel/device/src/main.c | 22 +++++++++--- 4 files changed, 51 insertions(+), 32 deletions(-) diff --git a/tests/kernel/device/prj.conf b/tests/kernel/device/prj.conf index 4818bf0f5f9ce..8ad433f3dd0ce 100644 --- a/tests/kernel/device/prj.conf +++ b/tests/kernel/device/prj.conf @@ -2,3 +2,4 @@ CONFIG_ZTEST=y CONFIG_TEST_USERSPACE=y CONFIG_PM_DEVICE=y CONFIG_PM_DEVICE_RUNTIME=y +CONFIG_APPLICATION_DEFINED_SYSCALL=y diff --git a/tests/kernel/device/src/abstract_driver.c b/tests/kernel/device/src/abstract_driver.c index 1fccec62b8869..5086fbaf24f65 100644 --- a/tests/kernel/device/src/abstract_driver.c +++ b/tests/kernel/device/src/abstract_driver.c @@ -12,20 +12,19 @@ #define MY_DRIVER_B "my_driver_B" /* define individual driver A */ -static int my_driver_A_do_this(const struct device *device, int foo, int bar) +static int my_driver_A_do_this(const struct device *dev, int foo, int bar) { return foo + bar; } -static void my_driver_A_do_that(const struct device *device, - unsigned int *baz) +static void my_driver_A_do_that(const struct device *dev, unsigned int *baz) { *baz = 1; } -static struct subsystem_api my_driver_A_api_funcs = { +static DEVICE_API(abstract, my_driver_A_api_funcs) = { .do_this = my_driver_A_do_this, - .do_that = my_driver_A_do_that + .do_that = my_driver_A_do_that, }; int common_driver_init(const struct device *dev) @@ -34,20 +33,19 @@ int common_driver_init(const struct device *dev) } /* define individual driver B */ -static int my_driver_B_do_this(const struct device *device, int foo, int bar) +static int my_driver_B_do_this(const struct device *dev, int foo, int bar) { return foo - bar; } -static void my_driver_B_do_that(const struct device *device, - unsigned int *baz) +static void my_driver_B_do_that(const struct device *dev, unsigned int *baz) { *baz = 2; } -static struct subsystem_api my_driver_B_api_funcs = { +static DEVICE_API(abstract, my_driver_B_api_funcs) = { .do_this = my_driver_B_do_this, - .do_that = my_driver_B_do_that + .do_that = my_driver_B_do_that, }; /** diff --git a/tests/kernel/device/src/abstract_driver.h b/tests/kernel/device/src/abstract_driver.h index f14b3bb4da0b4..fc06b1db9704d 100644 --- a/tests/kernel/device/src/abstract_driver.h +++ b/tests/kernel/device/src/abstract_driver.h @@ -4,34 +4,40 @@ * SPDX-License-Identifier: Apache-2.0 */ +#ifndef _ABSTRACT_DRIVER_H_ +#define _ABSTRACT_DRIVER_H_ + #include #include +#include /* define subsystem common API for drivers */ -typedef int (*subsystem_do_this_t)(const struct device *device, int foo, - int bar); -typedef void (*subsystem_do_that_t)(const struct device *device, - unsigned int *baz); - -struct subsystem_api { - subsystem_do_this_t do_this; - subsystem_do_that_t do_that; +typedef int (*abstract_do_this_t)(const struct device *dev, int foo, int bar); +typedef void (*abstract_do_that_t)(const struct device *dev, unsigned int *baz); + +__subsystem struct abstract_driver_api { + abstract_do_this_t do_this; + abstract_do_that_t do_that; }; -static inline int subsystem_do_this(const struct device *device, int foo, - int bar) +__syscall int abstract_do_this(const struct device *dev, int foo, int bar); + +static inline int z_impl_abstract_do_this(const struct device *dev, int foo, int bar) { - struct subsystem_api *api; + __ASSERT_NO_MSG(DEVICE_API_IS(abstract, dev)); - api = (struct subsystem_api *)device->api; - return api->do_this(device, foo, bar); + return DEVICE_API_GET(abstract, dev)->do_this(dev, foo, bar); } -static inline void subsystem_do_that(const struct device *device, - unsigned int *baz) +__syscall void abstract_do_that(const struct device *dev, unsigned int *baz); + +static inline void z_impl_abstract_do_that(const struct device *dev, unsigned int *baz) { - struct subsystem_api *api; + __ASSERT_NO_MSG(DEVICE_API_IS(abstract, dev)); - api = (struct subsystem_api *)device->api; - api->do_that(device, baz); + DEVICE_API_GET(abstract, dev)->do_that(dev, baz); } + +#include + +#endif /* _ABSTRACT_DRIVER_H_ */ diff --git a/tests/kernel/device/src/main.c b/tests/kernel/device/src/main.c index 437f2ae056c74..c70d2e2cd8764 100644 --- a/tests/kernel/device/src/main.c +++ b/tests/kernel/device/src/main.c @@ -384,20 +384,20 @@ ZTEST(device, test_abstraction_driver_common) dev = device_get_binding(MY_DRIVER_A); zassert_false((dev == NULL)); - ret = subsystem_do_this(dev, foo, bar); + ret = abstract_do_this(dev, foo, bar); zassert_true(ret == (foo + bar), "common API do_this fail"); - subsystem_do_that(dev, &baz); + abstract_do_that(dev, &baz); zassert_true(baz == 1, "common API do_that fail"); /* verify driver B API has called */ dev = device_get_binding(MY_DRIVER_B); zassert_false((dev == NULL)); - ret = subsystem_do_this(dev, foo, bar); + ret = abstract_do_this(dev, foo, bar); zassert_true(ret == (foo - bar), "common API do_this fail"); - subsystem_do_that(dev, &baz); + abstract_do_that(dev, &baz); zassert_true(baz == 2, "common API do_that fail"); } @@ -413,6 +413,20 @@ ZTEST(device, test_deferred_init) zassert_true(device_is_ready(FAKEDEFERDRIVER0)); } +ZTEST(device, test_device_api) +{ + const struct device *dev; + + dev = device_get_binding(MY_DRIVER_A); + zexpect_true(DEVICE_API_IS(abstract, dev)); + + dev = device_get_binding(MY_DRIVER_B); + zexpect_true(DEVICE_API_IS(abstract, dev)); + + dev = device_get_binding(DUMMY_NOINIT); + zexpect_false(DEVICE_API_IS(abstract, dev)); +} + ZTEST_USER(device, test_deferred_init_user) { int ret; From 9b943d39ca10a420190965fb3aefbb19a235917e Mon Sep 17 00:00:00 2001 From: Pieter De Gendt Date: Fri, 3 May 2024 16:26:03 +0200 Subject: [PATCH 6/6] doc: kernel: drivers: Update with DEVICE_API macros Update the device driver API documentation with the new DEVICE_API macro definitions and how to use them. Signed-off-by: Pieter De Gendt --- doc/kernel/drivers/index.rst | 37 ++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/doc/kernel/drivers/index.rst b/doc/kernel/drivers/index.rst index 96574e0899538..c1571edff32e3 100644 --- a/doc/kernel/drivers/index.rst +++ b/doc/kernel/drivers/index.rst @@ -68,21 +68,24 @@ The following APIs for device drivers are provided by :file:`device.h`. The APIs are intended for use in device drivers only and should not be used in applications. -:c:func:`DEVICE_DEFINE()` +:c:macro:`DEVICE_DEFINE()` Create device object and related data structures including setting it up for boot-time initialization. -:c:func:`DEVICE_NAME_GET()` +:c:macro:`DEVICE_NAME_GET()` Converts a device identifier to the global identifier for a device object. -:c:func:`DEVICE_GET()` +:c:macro:`DEVICE_GET()` Obtain a pointer to a device object by name. -:c:func:`DEVICE_DECLARE()` +:c:macro:`DEVICE_DECLARE()` Declare a device object. Use this when you need a forward reference to a device that has not yet been defined. +:c:macro:`DEVICE_API()` + Wrap a driver API declaration to assign it to its respective linker section. + .. _device_struct: Driver Data Structures @@ -97,8 +100,8 @@ split into read-only and runtime-mutable parts. At a high level we have: struct device { const char *name; const void *config; - const void *api; - void * const data; + const void *api; + void * const data; }; The ``config`` member is for read-only configuration data set at build time. For @@ -122,6 +125,9 @@ Most drivers will be implementing a device-independent subsystem API. Applications can simply program to that generic API, and application code is not specific to any particular driver implementation. +If all driver API instances are assigned to their respective API linker section +use :c:macro:`DEVICE_API_IS()` to verify the API's type. + A subsystem API definition typically looks like this: .. code-block:: C @@ -129,29 +135,28 @@ A subsystem API definition typically looks like this: typedef int (*subsystem_do_this_t)(const struct device *dev, int foo, int bar); typedef void (*subsystem_do_that_t)(const struct device *dev, void *baz); - struct subsystem_api { + __subsystem struct subsystem_driver_api { subsystem_do_this_t do_this; subsystem_do_that_t do_that; }; static inline int subsystem_do_this(const struct device *dev, int foo, int bar) { - struct subsystem_api *api; + __ASSERT_NO_MSG(DEVICE_API_IS(subsystem, dev)); - api = (struct subsystem_api *)dev->api; - return api->do_this(dev, foo, bar); + return DEVICE_API_GET(subsystem, dev)->do_this(dev, foo, bar); } static inline void subsystem_do_that(const struct device *dev, void *baz) { - struct subsystem_api *api; + __ASSERT_NO_MSG(DEVICE_API_IS(subsystem, dev)); - api = (struct subsystem_api *)dev->api; - api->do_that(dev, baz); + DEVICE_API_GET(subsystem, dev)->do_that(dev, baz); } A driver implementing a particular subsystem will define the real implementation -of these APIs, and populate an instance of subsystem_api structure: +of these APIs, and populate an instance of subsystem_driver_api structure using +the :c:macro:`DEVICE_API()` wrapper: .. code-block:: C @@ -165,9 +170,9 @@ of these APIs, and populate an instance of subsystem_api structure: ... } - static struct subsystem_api my_driver_api_funcs = { + static DEVICE_API(subsystem, my_driver_api_funcs) = { .do_this = my_driver_do_this, - .do_that = my_driver_do_that + .do_that = my_driver_do_that, }; The driver would then pass ``my_driver_api_funcs`` as the ``api`` argument to