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

[RFC] Multi interface devices #47655

Conversation

bjarki-andreasen
Copy link
Collaborator

@bjarki-andreasen bjarki-andreasen commented Jul 11, 2022

The goal of this proposal is to create a standardized design
and to set a precedent for how to create drivers and models
for multi-functional-devices, which expose one or more APIs
pr device.

The formal documentation will be created based on this RFC,
in its own pull request once the design details are in place.

Some devices require more than one interface to present
all of their functionalies. This can be a SPI to 4x UART
transceiver, which presents 4 distinct uart devices. A
struct device only provides one API, so 4 devices must
be created to allow for the application to use them.
However, the IC uses a single SPI to translate the
in and outbound UART data, so they must be synchronized
within a single device.

This proposal provides a standardized simple solution to
this problem, using only what is already provided by
the zephyr build system, in a transparent and intuitive
fasion.

Summarized, the solution is to create a parent device,
which then exposes the additional interfaces through
its child devices.

To ensure that it is obvious to a developer that the
device is a composite device (single device with
multiple interfaces), these rules must be followed:

  1. A composite device has the bus binding set to its
    name, it is placed in the folder which matches its
    primary function if it has one, otherwise it is placed
    in a folder named mfd
  2. A component device uses the on-bus binding to
    indicate that it belongs to the composite device, it
    is placed in the folder which matches its function.
  3. A component (child device) has the same name as
    the composite device, followed by - and the name
    of the component
  4. The composite bindings yaml file ends with a
    commented out section, which shows a detailed
    example of the composite device and its components
    in a fake device tree

Following these rules, it should be easy to spot a
composite device, and easy to figure out how to include
it in a device tree

The proposal also includes a draft implementation of the
bindings for the nxp,sc28c94 and bg96 composite devices,
alongside a draft driver for the nxp,sc28c94 to show how
the driver could be implemented.

Furthermore, the design descibed here is already in use by
the following driver:
https://github.com/zephyrproject-rtos/zephyr/blob/main/drivers/gpio/gpio_xlnx_axi.c
and in the following bindings:
https://github.com/zephyrproject-rtos/zephyr/blob/main/dts/bindings/adc/ti%2Clmp90100.yaml
https://github.com/zephyrproject-rtos/zephyr/blob/main/dts/bindings/gpio/ti%2Clmp90xxx-gpio.yaml

Note here that there is only one driver pr multi functional
device, which first instanciates all child devices, then itself,
the child devices are just used for storing cfgs, data and the
APIs.

The BG96 is a cellular modem with built in GNSS, which
requires two interfaces, and if cellular location is used,
three interfaces.

Two new functions have also been added to the device.h file
which allows for easily getting the child devices of a
composite device

Please come with any suggestions to improve this solution, or
propose a complimentary one for discussion :)

Signed-off-by: Bjarki Arge Andreasen bjarkix123@gmail.com

@bjarki-andreasen
Copy link
Collaborator Author

This is a list of issues that this solution should solve

  1. Must allow for a single device using multiple instances of the same API
  2. Should encapsulating properties where they are relevant (not as a huge blob in the parent device)
  3. Should disallow component without composite device present
  4. Should allow for including only some of the supported interfaces
  5. Must keep the device tree readable, it should be obvious that the child devices belong to the parent device
  6. Support all previous APIs (must pass a struct *device)
  7. Must be intuitive for new developers (KConfig and DTS compiler must be able to detect issues and inform/guide the user)

Feel free to add to this list, or suggest changes to it

@henrikbrixandersen henrikbrixandersen self-requested a review July 11, 2022 22:47
required: false
description: Username for network connection context

password:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

having a username/password dts feels wrong.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not possible to configure APN during runtime (at least yet), so it has to be here or in a KConfig, the reason it is here is to allow for multiple modem instances, which could use different APNs, I can imagine having two identical modems on different networks for redundancy for example

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A username/password may be needed to integrate with existing hardware, but we should design APIs to comply (support?) with each of the provisions in ETSI EN 303645: CYBER;
Cyber Security for Consumer Internet of Things:
Baseline Requirements (https://www.etsi.org/deliver/etsi_en/303600_303699/303645/02.01.01_60/en_303645v020101p.pdf)

@bjarki-andreasen bjarki-andreasen force-pushed the drivers-add-multi-interface-dev branch 2 times, most recently from 8a6eab6 to 4618c56 Compare July 12, 2022 08:20
@bjarki-andreasen bjarki-andreasen requested a review from galak July 12, 2022 08:20
@keith-zephyr
Copy link
Contributor

What about multi-function devices? For example, Nuvoton's TCPC (type-C port controller) is an I2C addressable device that includes the TCPC and a GPIO port expander. Would something like this fit into the model you're proposing?

@i2c1 {
    nct38xx@70 = {
        compatible = "nuvoton,nct38xx";
        reg = <0x70>;
        nct38xx_tcpc: nct38xx-tcpc = {
            compatible = "nuvoton,nct38xx-tcpc";
            /* ... */
        };
        nct38xx_gpio: nct38xx-gpio = {
            compatible = "nuvoton,nct38xx-gpio";
            /* ... */
        };
        

@bjarki-andreasen
Copy link
Collaborator Author

What about multi-function devices? For example, Nuvoton's TCPC (type-C port controller) is an I2C addressable device that includes the TCPC and a GPIO port expander. Would something like this fit into the model you're proposing?

@i2c1 {
    nct38xx@70 = {
        compatible = "nuvoton,nct38xx";
        reg = <0x70>;
        nct38xx_tcpc: nct38xx-tcpc = {
            compatible = "nuvoton,nct38xx-tcpc";
            /* ... */
        };
        nct38xx_gpio: nct38xx-gpio = {
            compatible = "nuvoton,nct38xx-gpio";
            /* ... */
        };
        

Yes :)

@jfischer-no
Copy link
Collaborator

Some devices require more than one interface to present
all of their functionalies. This can be a SPI to 4x UART
transceiver, which presents 4 distinct uart devices. A
struct device only provides one API, so 4 devices must
be created to allow for the application to use them.
However, the IC uses a single SPI to translate the
in and outbound UART data, so they must be synchronized
within a single device.

Actually driver like one for sc28c94 would provide maximal four UART devices per driver instance, described, configured and enabled in device tree. Obviously also that "a single SPI to translate the in and outbound UART data, so they must be synchronized within a single device" is task of the driver.

This proposal provides a standardized simple solution to
this problem, using only what is already provided by
the zephyr build system, in a transparent and intuitive
fasion.

What exactly is the problem? How to implement driver for external UART controller? We have drivers for external GPIO controllers, pca9555 and sn74hc595, perhaps these can serve as an example.

For BG96, there are tree independent UART interfaces:
"UART1 interface supports 9600bps,... It is used for data transmission and AT command communication.
UART2 interface supports 115200bps baud rate, and is used for module debugging and log output.
UART3 interface supports 115200bps baud rate, and is used for outputting GNSS data and NMEA
sentences."

Usually a GNNS driver would have a specific UART as parent node, or a GNNS service would access UART directly (how the application determines UART is its own problem, e.g. using /chosen node). The same for LTE modem.
For both services/drivers GNNS and LTE moden, what UART controller is used, UART controller on a SoC or external connected through SPI or I2C(e.g. SC16IS7xx), or mix of them, must not be relevant.

@MaureenHelm MaureenHelm self-requested a review July 12, 2022 15:50
@bjarki-andreasen
Copy link
Collaborator Author

bjarki-andreasen commented Jul 12, 2022

Some devices require more than one interface to present
all of their functionalies. This can be a SPI to 4x UART
transceiver, which presents 4 distinct uart devices. A
struct device only provides one API, so 4 devices must
be created to allow for the application to use them.
However, the IC uses a single SPI to translate the
in and outbound UART data, so they must be synchronized
within a single device.

Actually driver like one for sc28c94 would provide maximal four UART devices per driver instance, described, configured and enabled in device tree. Obviously also that "a single SPI to translate the in and outbound UART data, so they must be synchronized within a single device" is task of the driver.

The sc28c94 only has exactly four uarts, you could also use an id like uart@0, uart@1 etc. However, in this example, i chose to go the more descriptive route, informing the user that only 4 distinct UARTs exist. Not sure what the issue with the driver is here, the parent device has the driver, which uses the device tree to find its up to 4 child devices (UARTs) as shown in the draft driver included in this PR

This proposal provides a standardized simple solution to
this problem, using only what is already provided by
the zephyr build system, in a transparent and intuitive
fasion.

What exactly is the problem? How to implement driver for external UART controller? We have drivers for external GPIO controllers, pca9555 and sn74hc595, perhaps these can serve as an example.

The pin controllers and I2C bus APIs are designed for addressable child devices which all are interacted with identically. Multi-functional-devices are not busses, their multiple functions are not necessarily identical, and are therefore not addressable. Take the UART for example, the API functions only take a device pointer, not an address, so for the sc28c94, you can not choose which of them to send or receive from, contrary to I2C, which takes a pointer to the parent, and then the address of the child. The pin cntrl takes a pin number, which is like an address.

For BG96, there are tree independent UART interfaces: "UART1 interface supports 9600bps,... It is used for data transmission and AT command communication. UART2 interface supports 115200bps baud rate, and is used for module debugging and log output. UART3 interface supports 115200bps baud rate, and is used for outputting GNSS data and NMEA sentences."

If you look to the example of the BG96, the nmea-uart = &uart1; ties the specific uart to a device, the BG96 parent is placed on the "main" uart, which is used for AT commands. This is the same way clocks, GPIOs etc. are referenced for things like UARTs through params like clocks = <&clock1>;

Usually a GNNS driver would have a specific UART as parent node, or a GNNS service would access UART directly (how the application determines UART is its own problem, e.g. using /chosen node). The same for LTE modem. For both services/drivers GNNS and LTE moden, what UART controller is used, UART controller on a SoC or external connected through SPI or I2C(e.g. SC16IS7xx), or mix of them, must not be relevant.

Yes, for many projects, for modems, the UART is exposed, and the drivers are placed in the application. This is not a portable solution however. The point of this PR is to allow for drivers to handle the AT commands and NMEA frame parsing, exposing APIs like location, socket offloading, GPIO, file systems etc. decoupling the application from the specific modem. The main issue is that setup of the modems, like position update rates, power modes etc. are also handled through the UART, which should be part of the driver. The application should not know if it has to send "$PMTK225,42F" or "$PAIR00339" to turn of the modem, and which pins to toggle, the app should just get the parsed lat or long, and use the PM to turn it on or off.
As you can imagine, this raises the need for multiple APIs pr device, since modems provide way more functionality than a single API can provide, unless we make APIs for each modem, therefore, multiple APIs for multi function devices is the way to go :)

Copy link
Member

@henrikbrixandersen henrikbrixandersen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed on the Architecture WG meeting, I don't see this proposed standardization on multi-function devices (MFDs) really bringing in anything new.

We already support MFDs in-tree using a similar approach, but without the limitations proposed here (see e.g. https://github.com/zephyrproject-rtos/zephyr/blob/main/dts/bindings/adc/ti%2Clmp90100.yaml and https://github.com/zephyrproject-rtos/zephyr/blob/main/dts/bindings/gpio/ti%2Clmp90xxx-gpio.yaml, there are others too).

I don't see any reason for restricting DTS bindings for the same MFD to be located in the same subfolder. I'd much rather have them in their respective device class dts bindings directory, as this makes it easier to get an overview of e.g. all our GPIO controller bindings.

What we really need to consider is whether the current Zephyr device driver model, which enforces a one-to-one relationship between a devicetree node and a struct device along with a one-to-one relationship between a struct device and an API struct, is sufficient to support MFDs going forward. This limitation causes us to having to reflect the division of MFD support into various drivers in the devicetree (as also shown here). This causes a MFD devicetree for use in Zephyr having to deviate from the devicetree for the same MFD used in the Linux kernel, as the Linux kernel does not have this restriction (one devicenode can support multiple device classes/APIs).

@bjarki-andreasen
Copy link
Collaborator Author

bjarki-andreasen commented Jul 14, 2022

As discussed on the Architecture WG meeting, I don't see this proposed standardization on multi-function devices (MFDs) really bringing in anything new.

We already support MFDs in-tree using a similar approach, but without the limitations proposed here (see e.g. https://github.com/zephyrproject-rtos/zephyr/blob/main/dts/bindings/adc/ti%2Clmp90100.yaml and https://github.com/zephyrproject-rtos/zephyr/blob/main/dts/bindings/gpio/ti%2Clmp90xxx-gpio.yaml, there are others too).

I don't see any reason for restricting DTS bindings for the same MFD to be located in the same subfolder. I'd much rather have them in their respective device class dts bindings directory, as this makes it easier to get an overview of e.g. all our GPIO controller bindings.

What we really need to consider is whether the current Zephyr device driver model, which enforces a one-to-one relationship between a devicetree node and a struct device along with a one-to-one relationship between a struct device and an API struct, is sufficient to support MFDs going forward. This limitation causes us to having to reflect the division of MFD support into various drivers in the devicetree (as also shown here). This causes a MFD devicetree for use in Zephyr having to deviate from the devicetree for the same MFD used in the Linux kernel, as the Linux kernel does not have this restriction (one devicenode can support multiple device classes/APIs).

The new thing being added is the standardization itself, a page in the Documentation (and two helper macros to the device.h), describing best practices for creating MFDs using the currently available features of the Zephyr project. It is supposed to bring clarity to developers wishing to create MFDs, so they don't all have to go through the same discussions, designs etc. as I have done. If this documentation existed, I would not have needed to bring it up, and we could have started writing the driver for the BG96 immediately.

The further discussion regarding changing the 1-1 binding is largely irrelevant to adding this documentation, since it is not currently possible and it is not required to create MFDs as you demonstrated in your example (and I in mine). It is an important discussion, and something that would be nice to have, but we can't wait until that becomes a reality (if it does).

I agree that the rule to place parent and child nodes in the same folder was a bad idea, and i have replaced it with simply placing it on a bus with the name of the parent like in your example.

@bjarki-andreasen bjarki-andreasen force-pushed the drivers-add-multi-interface-dev branch from 4618c56 to 21984dc Compare July 14, 2022 09:18
@henrikbrixandersen
Copy link
Member

The new thing being added is the standardization itself, a page in the Documentation (and two helper macros to the device.h),

I don't see any documentation being added by this RFC? The two helper macros seem fine, but they can easily be implemented where needed by using the existing DT macros and as such do not solve the issue of implementing MFD drivers.

The nxp_sc28c94.c should likely just be yet another serial driver under drivers/serial/uart_sc28c94.c or similar.

The idea of instantiating all devices for a MFD in the same driver is already used in-tree, please see https://github.com/zephyrproject-rtos/zephyr/blob/main/drivers/gpio/gpio_xlnx_axi.c for an example.

@bjarki-andreasen
Copy link
Collaborator Author

bjarki-andreasen commented Jul 14, 2022

The new thing being added is the standardization itself, a page in the Documentation (and two helper macros to the device.h),

I don't see any documentation being added by this RFC? The two helper macros seem fine, but they can easily be implemented where needed by using the existing DT macros and as such do not solve the issue of implementing MFD drivers.

The nxp_sc28c94.c should likely just be yet another serial driver under drivers/serial/uart_sc28c94.c or similar.

The idea of instantiating all devices for a MFD in the same driver is already used in-tree, please see https://github.com/zephyrproject-rtos/zephyr/blob/main/drivers/gpio/gpio_xlnx_axi.c for an example.

Cool, so the proposed solution is already used in Zephyr, nearly as described in this RFC, and the solution is scalable to fit all of the usecases I have listed during the discussions, and no even better solution has been proposed. Absolutely perfect.

Having the helper functions in the device.h will help link to the MFD documentation, which is primarily where it will be used. Otherwise we must create a new file or use another existing file for the macros, IMO this is a worse solution. The worst solution would be to repeat this macro in every MFD driver. With the standardization of the design, the macros will be used extensively, helping with readability and understanding, since the macros in general are not easy to read, and have little to no description in the drivers.

This RFC does not contain any formal documentation, as it is meant for discussion on the driver level, the formal documentation will be based on this RFC, and be in its own pull request.

I forgot to move the draft driver to the serial folder, that has been updated.

Some devices require more than one interface to present
all of their functionalies. This can be a SPI to 4x UART
transceiver, which presents 4 distinct uart devices. A
struct device only provides one API, so 4 devices must
be created to allow for the application to use them.
However, the IC uses a single SPI to translate the
in and outbound UART data, so they must be synchronized
within a single device.

This proposal provides a standardized simple solution to
this problem, using only what is already provided by
the zephyr build system, in a transparent and intuitive
fasion.

Summarized, the solution is to create a parent device,
which then exposes the additional interfaces through
its child devices.

To ensure that it is obvious to a developer that the
device is a composite device (single device with
multiple interfaces), these rules must be followed:

1. A composite device has the bus binding set to its
   name, it is placed in the folder which matches its
   primary function if it has one, otherwise it is placed
   in a folder named mfd

2. A component device uses the on-bus binding to
   indicate that it belongs to the composite device, it
   is placed in the folder which matches its function.

3. A component (child device) has the same name as
   the composite device, followed by - and the name
   of the component

4. The composite bindings yaml file ends with a
   commented out section, which shows a detailed
   example of the composite device and its components
   in a fake device tree

Following these rules, it should be easy to spot a
composite device, and easy to figure out how to include
it in a device tree

The proposal also includes a draft implementation of the
bindings for the nxp,sc28c94 and bg96 composite devices,
alongside a draft driver for the nxp,sc28c94 to show how
the driver could be implemented.

The BG96 is a cellular modem with built in GNSS, which
requires two interfaces, and if cellular location is used,
three interfaces.

Two new functions have also been added to the device.h file
which allows for easily getting the child devices of a
composite device

Please come with any suggestions to improve this solution, or
propose a complimentary one for discussion :)

Signed-off-by: Bjarki Arge Andreasen <bjarkix123@gmail.com>
@bjarki-andreasen bjarki-andreasen force-pushed the drivers-add-multi-interface-dev branch from 21984dc to 509140a Compare July 14, 2022 12:07
@MaureenHelm MaureenHelm added the dev-review To be discussed in dev-review meeting label Jul 14, 2022
@galak
Copy link
Collaborator

galak commented Jul 14, 2022

I think the general rules look good. Have some small tweaks in some cases, but will make those comments once you create a PR with a doc update that includes them.

Copy link
Contributor

@mbolivar-nordic mbolivar-nordic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quick note about the password property without commenting yet on the rest

Comment on lines +39 to +47
username:
type: string
required: false
description: Username for network connection context

password:
type: string
required: false
description: Password for network connection context
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without commenting on the meat of the PR: a friendly but unambiguous no way to these properties :). Not now, not ever, not on my watch :). CC @d3zd3z

  • While this isn't strictly speaking a security hole since the password property will not automatically be stored in the firmware, it is just too tempting a target. Someone somewhere will save into ROM "just for now"; I can't in good conscience encourage this antipattern.
  • IMO this is a pain to manage from a provisioning standpoint. Are we really going to expect every single device to get its own firmware build just to pass in an overlay for the unique password (the password is unique per device, right? 🙂 )
  • This is also a pain from a flexibility standpoint. This might work for you, but other people are going to want to configure their software differently, e.g. getting the password from a special piece of on-chip flash with anti-readout protections, or from an EEPROM, or ...

TL;DR this is the sort of software configuration that doesn't belong in DT, and it is a security smell to boot.

Copy link
Collaborator Author

@bjarki-andreasen bjarki-andreasen Jul 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is currently done using KConfig, which to my knowledge has the exact same issues as you mention here :) The username and password are like WIFI passwords, tied to the mobile network provider. All units using the same provider will use the same username/password, if required. Note that the username and password are largely no longer used, and most APNs and their accompanying passwords are public information https://www.hw-group.com/support/international-apn-settings-for-mobile-broadband-network-operators, so i doubt it is a security risk to store them outside of EEPROM or similar.

Usually, mass manufactured devices have soldered on ESIM chips which have a fixed APN/Username/Password, so it can be tied directly with the device through firmware.

Overall, this is just a parameter, like baudrate, I can not see what would differentiate this property from any other property directly tied to an instance of a device.

With that said, i do agree that it should be possible to change during runtime, which would remove it from KConfig and the device tree, which would also open up the possibility of creating routers or similar, which require the ability to change SIM card and APN during runtime, but for now, this is how it is done.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is currently done using KConfig, which to my knowledge has the exact same issues as you mention here :)

The presence of bad behavior elsewhere is not a justification for introducing it into a devicetree binding.

The username and password are like WIFI passwords, tied to the mobile network provider. All units using the same provider will use the same username/password, if required.

Then it doesn't seem like this is per-instance modem configuration, but rather per-mobile-network information, which also argues against having them here.

most APNs and their accompanying passwords are public information https://www.hw-group.com/support/international-apn-settings-for-mobile-broadband-network-operators

OK, thanks for explaining that. That does indeed change the security aspect of this in most cases, I guess. However, "most" is not "all".

Usually, mass manufactured devices have soldered on ESIM chips which have a fixed APN/Username/Password, so it can be tied directly with the device through firmware.

Right, which is exactly my point about flexibility, which is basically that putting this in DT doesn't really make sense if people are going to want to do it in other ways, and in fact that seems to be the case here, which is not surprising.

which require the ability to change SIM card and APN during runtime,

In general, devicetree describes the hardware and its boot time configuration. Just because something can change at runtime is not -- in general -- a reason to remove its boot time value from the DT. However again I still don't think this should go here.

but for now, this is how it is done.

My -1 remains, sorry

Copy link
Collaborator Author

@bjarki-andreasen bjarki-andreasen Jul 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, can you propose a better solution to the issue? Keep in mind that two modems will use two different SIM cards potentially from different network providers for redundancy.

@MaureenHelm MaureenHelm removed the dev-review To be discussed in dev-review meeting label Jul 21, 2022
@ycsin ycsin self-requested a review July 27, 2022 00:21
@github-actions
Copy link

This pull request has been marked as stale because it has been open (more than) 60 days with no activity. Remove the stale label or add a comment saying that you would like to have the label removed otherwise this pull request will automatically be closed in 14 days. Note, that you can always re-open a closed pull request at any time.

@github-actions github-actions bot added the Stale label Sep 25, 2022
@github-actions github-actions bot closed this Oct 9, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants