diff --git a/CMakeLists.txt b/CMakeLists.txt index 88b2d77c3a4d5..97af4dce94576 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,37 @@ 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 + ) + +set(DEVICE_API_LINKER_SECTIONS_CMAKE + ${CMAKE_CURRENT_BINARY_DIR}/include/generated/device-api-sections.cmake +) + +add_custom_command( + 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} + ${DEVICE_API_LINKER_SECTIONS_CMAKE} +) + # Add a pseudo-target that is up-to-date when all generated headers # are up-to-date. @@ -912,6 +944,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/cmake/linker/armlink/target.cmake b/cmake/linker/armlink/target.cmake index b2e1e867f904a..be2bb8e89778f 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,21 +29,29 @@ 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 ${DEVICE_API_LINKER_SECTIONS_CMAKE} + -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} -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 5e0a117c01436..bdeecd0b0b312 100644 --- a/cmake/linker/ld/target.cmake +++ b/cmake/linker/ld/target.cmake @@ -18,22 +18,31 @@ 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 ${DEVICE_API_LINKER_SECTIONS_CMAKE} + -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 - ) + DEPENDS ${DEVICE_API_LD_TARGET} + ) else() set(template_script_defines ${linker_pass_define}) list(TRANSFORM template_script_defines PREPEND "-D") 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/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 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 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..cca0ea11eb97e --- /dev/null +++ b/scripts/build/gen_iter_sections.py @@ -0,0 +1,73 @@ +#!/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, alignment: int): + with open(filepath, "w") as fp: + for item in items: + 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: + 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("-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() + + +def main(): + args = parse_args() + + items = get_tagged_items(args.input, args.tag) + + gen_ld(args.ld_output, items, args.alignment) + gen_cmake(args.cmake_output, items, args.alignment) + + +if __name__ == "__main__": + main() 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;