Description
Introduction
Currently, only one API can be mapped to each instance of a struct device. This proposal provides two solutions which together allow for multiple APIs pr device instance, and the creation of devices which consist of multiple sub devices.
Problem description
Some devices provide multiple functions, which require multiple APIs pr single device instance. Some devices consist of multiple internal devices, which all require their own handles and APIs. There is no general design guideline for creating such devices, and creating devices with multiple APIs pr instance is not currently possible. This is preventing the creation of proper drivers for cellular and GNSS modems which do provide many varying features which can not be efficiently covered by a single API, since the functionalities are ever expanding and vary highly between devices.
Proposed change
The first solution is simply a design guideline for devices which consist of multiple sub devices. The change includes adding a documentation page describing this guideline, and adding some macros to help keep the driver design consistent. The specific docs and macros are contained in this PR #48932
bus {
parent {
child1 {
};
child2 {
};
child3 {
};
};
};
The second solution creates one struct device for each API implemented by a single device instance. The struct devices share all data except for the API pointer. Their names reflect the API they implement, and they are referenced using a function similar to DEVICE_DT_GET(node_id), called DEVICE_DT_API_GET(node_id, api_type), which returns the struct device which implements the specified API type if it has been defined by a driver, using DEVICE_DT_API_DEFINE(api_type, node_id, api_ptr)
struct api_foo;
struct api_bar;
DEVICE_DT_DEFINE(node_id, ... NULL)
DEVICE_DT_API_DEFINE(foo, node_id, &api_foo)
DEVICE_DT_API_DEFINE(bar, node_id, &api_bar)
struct device *foo_dev = DEVICE_DT_API_GET(foo, node_id);
struct device *bar_dev = DEVICE_DT_API_GET(bar, node_id);
The API struct devices are stored in ROM, grouped by API type, allowing for iterating through all devices which support a specific API type, which is handy for the shell for example.
Solution 2 requires no changes to the existing API headers, adds no overhead to the usage of API wrappers, while allowing for multiple APIs pr device instance
int ret = foo_var_get(&foo_dev);
ret = bar_dev_set(&bar_dev);
Detailed RFC
The first solution requires little extra explaining, since it is literally a docs page :)
The second solution requires updating the linker files to add sections for the API struct devices, and an update to the device.h file to include the new macros for defining and getting struct devices. Adding convenience macros to the driver API headers like LOCATION_DEVICE_DT_API_DEFINE(node_id, api_ptr) should also be done, but is not required.
struct api_loc;
DEVICE_DT_DEFINE(node_id, ... NULL)
LOCATION_DEVICE_DT_API_DEFINE(node_id, &api_loc)
Proposed change (Detailed)
See #48817 for example implementation of an API using solution 2
- Add documentation and macros for solution 1
- Add linker file which will contain API iterable sections
- Update device.h to include new macros for solution 2
- Add convenience macros to driver API headers
- Start transition period from old solution to these solutions
- Update in-tree drivers to use DEVICE_DT_API_DEFINE for APIs, and set DEVICE_DT_DEFINE api_ptr to NULL
- Transition period ends
- Remove api_ptr from DEVICE_DT_DEFINE
- Use api ptr in struct device from DEVICE_DT_DEFINE as init function
- update linker and device.c to use struct devices with init instead of an additional list as is does now
- Success
Dependencies
The only dependency which we can not control is the out-of-tree drivers which must be updated in the transition period.
Concerns and Unresolved Questions
Alternatives
Come forth with your alternatives here :)
Metadata
Metadata
Assignees
Type
Projects
Status