-
Notifications
You must be signed in to change notification settings - Fork 7.8k
drivers: Move API driver structs to an iterable section #71773
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b493b66
b9a1bf9
423972f
c111c4e
2165649
9b943d3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 \"$<TARGET_PROPERTY:linker,FORMAT>\" CACHE INTERNAL \"\")\n | ||
set(ENTRY \"$<TARGET_PROPERTY:linker,ENTRY>\" CACHE INTERNAL \"\")\n | ||
set(MEMORY_REGIONS \"$<TARGET_PROPERTY:linker,MEMORY_REGIONS>\" CACHE INTERNAL \"\")\n | ||
set(GROUPS \"$<TARGET_PROPERTY:linker,GROUPS>\" CACHE INTERNAL \"\")\n | ||
set(SECTIONS \"$<TARGET_PROPERTY:linker,SECTIONS>\" CACHE INTERNAL \"\")\n | ||
set(SECTION_SETTINGS \"$<TARGET_PROPERTY:linker,SECTION_SETTINGS>\" CACHE INTERNAL \"\")\n | ||
set(SYMBOLS \"$<TARGET_PROPERTY:linker,SYMBOLS>\" CACHE INTERNAL \"\")\n | ||
" | ||
) | ||
add_custom_command( | ||
OUTPUT ${linker_script_gen} | ||
COMMAND ${CMAKE_COMMAND} | ||
-C ${DEVICE_API_LINKER_SECTIONS_CMAKE} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in principle not fond of relying on outer scoped CMake variables inside macros or functions. However the Also relying on |
||
-C ${cmake_linker_script_settings} | ||
-DPASS="${linker_pass_define}" | ||
-DFORMAT="$<TARGET_PROPERTY:linker,FORMAT>" | ||
-DENTRY="$<TARGET_PROPERTY:linker,ENTRY>" | ||
-DMEMORY_REGIONS="$<TARGET_PROPERTY:linker,MEMORY_REGIONS>" | ||
-DGROUPS="$<TARGET_PROPERTY:linker,GROUPS>" | ||
-DSECTIONS="$<TARGET_PROPERTY:linker,SECTIONS>" | ||
-DSECTION_SETTINGS="$<TARGET_PROPERTY:linker,SECTION_SETTINGS>" | ||
-DSYMBOLS="$<TARGET_PROPERTY:linker,SYMBOLS>" | ||
-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") | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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()` | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
See the inconsistency with the explanatory text, too. |
||||||
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,36 +125,38 @@ 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 | ||||||
|
||||||
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 { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any chance this could be
Side notes:
UPDATED to account for the proper definition(s) of subsystem, see the related discussion in #71090. |
||||||
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); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is just hiding that everything is a void*, which again wouldn't actually be a void* if we had proper typing of the devices There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't necessarily type checking per-se (any pointer can be cast to one of the expected type to get around type checking at the compiler level). It's more like ensuring that a Thing is in fact a Thing by checking that a given Thing pointer is a member of the array of all Things, which is pretty hard to argue with (in terms of static RTOS behaviour). Drivers loaded at runtime really should also get the same kind of check as a kind of minimal security practice. Currently with userspace, the kernel validates all device API pointer arguments as being readable or writeable memory regions. A similar check is done for permissions on kernel objects. Somehow we completely bypass any checks whatsoever for (arguably) the most important pointer in a device API call (the Do we already support drivers loaded at runtime? If so, I think that means they implicitly rely on this (IMHO) hole in security. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Right, which could be done by the compiler if we had proper types instead of void*'s, which is the right place to do this. Unless your talking about userspace where you have untrusted code perhaps? In that case sure, but then this sort of thing should be done in z_vrfy? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Definitely userspace, and agree about z_vrfy. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Regarding runtime loadable drivers, we are already boxed in quite substantionally. We don't have a way of storing the devicetree in ROM, so drivers have to be compiled in, and "switched between" at runtime. Given that these drivers are compiled in, their APIs would be placed in the correct API sections as well :) Unless by runtime loadable you mean "truly" runtime loadable, I don't see this PR as any more of a box :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be clear here, my main complaint is this is kicking the can down the road of using proper types a compiler will yell about and instead leaving this to a runtime check that I guess might blow up at any random time while firmware is running. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see this PR as an improvement to the status quo, not really a new feature. Kicking the can down the road would IMO imply we are breaking the status quo with an incomplete, short-term solution, which I just don't think describes this PR. |
||||||
} | ||||||
|
||||||
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 | ||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please avoid the TYPE suffix if anyhow possible. Type is so overloaded with meaning...
Suggested change
|
||||||
|
||||||
/** @endcond */ | ||||||
|
||||||
/** | ||||||
* @brief Wrapper macro for declaring device API structs inside iterable sections. | ||||||
* | ||||||
* @param _class The device API class. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
@gmarull It turns out that this is one thing where we have differing opinions. Can you see my point why we cannot always equate device classes and APIs precisely although they are of course very closely related? I assume you don't have really strong feelings about placing the macros inside devices vs. drivers? I do - so it would be great if we could go that way. |
||||||
* @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) \ | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still find this macro misleading, maybe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tend to prefer the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. None of both? I propose to call this "DRIVER_API" or "DRIVER_SUBSYSTEM_API" consistently. And we must not refer to IMO we have:
Explanation:
Given these definitions, what we're trying to name here would be a "driver subsystem API" if I'm not mistaken. I'm fine shortening this to "driver API" as long as we keep in mind that a "driver API" is a (as opposed to has a) subsystem API. UPDATED to account for the proper definition of subsystem. See #71090. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually thinking more about this, there is a 1-to-1 relation between a device and its driver API. It's an opaque type (which I'm trying to figure out at runtime with this PR) but can only serve one purpose. You're not allowed to pass the same device instance to different API layers, multi-purpose drivers (your n-to-m ?) need to instantiate multiple devices each with a different API. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moreover, every driver implementation is creating devices instances ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup, I've not questioned that. That's beside the point, though. |
||||||
({ \ | ||||||
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 | ||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -102,3 +102,5 @@ | |||||
#include <zephyr/linker/device-deps.ld> | ||||||
} GROUP_ROM_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) | ||||||
#endif /* !CONFIG_DEVICE_DEPS_DYNAMIC */ | ||||||
|
||||||
#include <device-api-sections.ld> | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
This conversation was marked as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
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() |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this needed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, otherwise the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should be redundant when using zephyr_syscall_header There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Trying to build without it results in:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I could remove the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 I think it's a good idea to decouple syscall-specific logic from the generic "tag parser" python script. See my proposal to rename that script to what it is actually used for - part of the same problem. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here and in all other places:
Just an example - see detailed comment for rationale.