Skip to content
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

drivers: Move API driver structs to an iterable section #71773

Merged
merged 6 commits into from
Nov 27, 2024

Conversation

pdgendt
Copy link
Collaborator

@pdgendt pdgendt commented Apr 22, 2024

Introduction

This PR demonstrates how iterable sections can be used to validate type safety of device API calls.

Problem description

Type safety using void * typically requires us to either perform additional build steps, or add overhead data.

Proposed change

Putting each device API in their dedicated linker section allows us to check if calling an API using a void * is located within that section.
Calling invalid device API functions with CONFIG_ASSERT enabled would cause an assertion panic, helping users during development to track issues with devices.

/* A function that is called from network/shell/... */
static int some_function(const char *dev_name)
{
	const struct device *dev = device_get_binding(dev_name);

	if (dev == NULL) {
		return -ENODEV;
	}

	if (!DEVICE_API_IS(adc, dev)) {
		return -EINVAL;
	}

	/* do something with ADC device */
	return 0;
}

Detailed RFC

The following macro definitions are added:

/** @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 an
 *        API pointer is located in the iterable section.
 *
 * @param _class The device API class.
 * @param _dev The device instance pointer.
 *
 * @return true if the API pointer is located in the type's iterable section.
 */
#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))); \
	})

A python script scripts/build/gen_iter_sections.py is added (written by @bjarki-trackunit) to translate all __subsystem struct definitions to linker sections.

Requirements (for each API type)

Instances create the API struct using DEVICE_API (which forces const), for example:

static DEVICE_API(adc, api) = {
	.channel_setup = ads1112_channel_setup,
	.read = ads1112_read,
	.ref_internal = ADS1112_REF_INTERNAL,
};

The API calls evaluate the void *, for example:

static inline int z_impl_adc_read(const struct device *dev,
				  const struct adc_sequence *sequence)
{
	__ASSERT_NO_MSG(DEVICE_API_IS(adc, dev));

	return DEVICE_API_GET(adc, dev)->read(dev, sequence);
}

Dependencies

None that I can think of.

Concerns and Unresolved Questions

  • __ASSERT vs CHECKIF
  • CONFIG_CMAKE_LINKER_GENERATOR can't rely on the gen_iter_sections.py script to generate sections for iterable structs
  • All out-of-tree drivers need to migrate to use driver API macros
  • Should we create a post-linker script that validates if all device's api pointers are located in the correct section?
  • Naming; type vs class
  • Is the _driver_api a required suffix? And should we shorten/hide this part using the macros?

Tasks

After approval to go forward, we can start moving other drivers too.

@bjarki-andreasen
Copy link
Collaborator

bjarki-andreasen commented Apr 22, 2024

The other half of the issue is that we also need the actual device, the API on its own is not of much use :) The proposal I made here #58112 uses elf parsing to tie the API to a device, and generates header files from it. It's a bit complex, requiring multi stage building to work, building drivers before subsystem/app.

I still prefer the simplest (but a bit costly) generating additional arrays of dev + api in ROM like SENSOR_DEVICE_DT_DEFINE_...

@fabiobaltieri
Copy link
Member

fabiobaltieri commented Apr 22, 2024

The other half of the issue is that we also need the actual device

You could scan through the (existing) device iterable section and match all devices who's api pointer is within the boundaries of the API category you care about.

@bjarki-andreasen
Copy link
Collaborator

The other half of the issue is that we also need the actual device

You could scan through the (existing) device iterable section and match all devices who's api pointer is within the boundaries of the API category you care about.

Yes, but that's a runtime scan, ew :D

@fabiobaltieri
Copy link
Member

Yes, but that's a runtime scan, ew :D

Should be quick though.

@pdgendt
Copy link
Collaborator Author

pdgendt commented Apr 22, 2024

Yes, but that's a runtime scan, ew :D

Should be quick though.

That's why I think #67698 and this PR can solve different issues, and both can coexist.

@Laczen
Copy link
Collaborator

Laczen commented Apr 22, 2024

Wouldn't this method force the api to be included in ROM even if the device is not used?

@bjarki-andreasen
Copy link
Collaborator

Wouldn't this method force the api to be included in ROM even if the device is not used?

The APIs are only included if the drivers are included, so there should be no difference in ROM usage, only where in ROM the API structs are placed :)

drivers/adc/adc_emul.c Outdated Show resolved Hide resolved
include/zephyr/drivers/adc.h Outdated Show resolved Hide resolved
include/zephyr/drivers/adc.h Outdated Show resolved Hide resolved
@pdgendt pdgendt changed the title drivers: adc: Move API driver structs to an iterable section [RFC] drivers: adc: Move API driver structs to an iterable section Apr 23, 2024
@pdgendt pdgendt marked this pull request as ready for review April 23, 2024 09:22
Update the device driver API documentation with the new DEVICE_API
macro definitions and how to use them.

Signed-off-by: Pieter De Gendt <[email protected]>
@pdgendt
Copy link
Collaborator Author

pdgendt commented Nov 26, 2024

Update:

  • Change CHECKIF to __ASSERT_NO_MSG

@fabiobaltieri fabiobaltieri changed the title [RFC] drivers: Move API driver structs to an iterable section drivers: Move API driver structs to an iterable section Nov 26, 2024
@fabiobaltieri fabiobaltieri removed RFC Request For Comments: want input from the community DNM This PR should not be merged (Do Not Merge) labels Nov 26, 2024
@fabiobaltieri
Copy link
Member

Dropping the RFC and DNM tags since this has been commented and it appears like there's consensus that it can go in.

@pdgendt pdgendt removed the Architecture Review Discussion in the Architecture WG required label Nov 26, 2024
@pdgendt
Copy link
Collaborator Author

pdgendt commented Nov 26, 2024

Dropping the RFC and DNM tags since this has been commented and it appears like there's consensus that it can go in.

Also removed the arch meeting label as this prevents merging, and unassigned @gmarull

@kartben kartben merged commit d6b1de1 into zephyrproject-rtos:main Nov 27, 2024
36 of 39 checks passed
@pdgendt pdgendt deleted the linker-api branch November 27, 2024 07:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.