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: rework GPIO interrupt configuration #17102

Closed
pabigot opened this issue Jun 26, 2019 · 36 comments
Closed

RFC: rework GPIO interrupt configuration #17102

pabigot opened this issue Jun 26, 2019 · 36 comments
Assignees
Labels
Enhancement Changes/Updates/Additions to existing features

Comments

@pabigot
Copy link
Collaborator

pabigot commented Jun 26, 2019

The existing GPIO API has a somewhat baroque approach to configuring interrupts and callbacks, the motivation for which is unclear:

  • The gpio_callback structure provides the handler to use in the callback, and specifies the pins for which the callback applies;
  • The gpio_{add,remove}_callback() API adds or removes gpio_callback objects from registry maintained in the driver but does not automatically enable what's been added;
  • The gpio_{en,dis}able_callback API operates on pins in isolation of whether any callback handlers happen to be registered for them.

The combination seems to allow for multiple drivers/application functions to request notification of interrupts occurring on a pin, but if somebody takes advantage of that any one of them can enable or disable the callback for all them.

This comment on work to clean up the GPIO API on proposes a change that moves the pin's interrupt-related configuration (viz. edge/level, high/low) from being performed through GPIO configuration into being provided in the callback structure.

So if the API may be changed, two questions:

  • Is there any motivation for allowing multiple consumers of interrupts on a single pin-in-driver? Yes, a soft-reset feature
  • Taking into account the answer, does it make sense to change the API so that configuring a pin for interrupt and enabling it are not two separate operations?

Tentatively my answers would be "no", and "yes", which could be accomplished by using something like gpio_enable_interrupt to handle the registration, configuration, and enable operations and gpio_disable_interrupt to disable, return the GPIO to its non-interrupt configuration, and remove the callback registration.

It would be nice to have this along with the other improvements of #16648, although it's not directly related to device tree flags so probably belongs in a coordinated PR.

I would like to have consensus on the value and approach before anybody's asked to spend time on this.

@pabigot pabigot added the Enhancement Changes/Updates/Additions to existing features label Jun 26, 2019
@pabigot
Copy link
Collaborator Author

pabigot commented Jun 26, 2019

@b0661 @anangl FYI.

If we can agree on value and details I'd be willing to help in the implementation, unless it falls naturally within other work in progress.

@pabigot
Copy link
Collaborator Author

pabigot commented Jun 27, 2019

Reworded description to remove my temporary confusion that a GPIO driver might control multiple ports. It'd be nice if that was true, but it's not.

@tbursztyka
Copy link
Collaborator

if somebody takes advantage of that any one of them can enable or disable the callback for all them.

And with your proposal, if somebody takes advantage of enabling interrupt on all pins for itself, no other system could register a callback anymore. Same problem then. This issue of "taking advantage to is a non-issue as there is no way you can really fix it.

Tentatively my answers would be "no", and "yes"

You are assuming wrong.

Let's say a soft reset for peripherals that would be triggered via gpio pin, meaning each peripheral drivers would trigger a soft reset based on that pin event. That's a valid use case. You would have multiple listeners for the same pin. And it is just an example.

And for the second question, configuring and enabling are 2 distinct things. You could have them in one go, or separate as it is right now: in the end it is just a matter of taste whether you prefer a function doing 1 thing or 2. It's been chosen to have functions doing one thing only, fine.

In general, as long as there is no real life issues with an API, so anything besides assumptions, let's not change it. It is a costly move for zephyr and its users. Better be sure a change is worthy to be made before actually doing it.

@pabigot
Copy link
Collaborator Author

pabigot commented Jun 27, 2019

I'm not assuming anything: I'm raising a question to try to understand why things are the way they are. You've presented a reasonable use case for multiple listeners. That needs to be traded against the risks of having those listeners apply inconsistent configurations.

Enabling and disabling each have multiple steps, one of which is registration. Whether and how to fragment the steps is a design decision. There is a cost to leaving a callback registered when it's disabled.

My overall point is that we've discovered reasons to separate a pin's GPIO functionality from its interrupt functionality, so the API may be reworked anyway. Now is the time to revisit past decisions, see if they're still relevant, and ensure that any changes handle both existing and new use cases.

@b0661
Copy link
Collaborator

b0661 commented Jun 28, 2019

In general, as long as there is no real life issues with an API, so anything besides assumptions, let's not change it.

@tbursztyka there is a real life issue for me. Currently the combined interrupt/ pin configuration muxes the pin to the GPIO function. In my case the GPIO pin is used as a HW CS (muxed to the SPI) for my board acting as a SPI slave. I still need an interrupt when the SPI chip select is asserted but I can not do a software based chip select handling for the SPI function. With the current API I can either have HW CS or the interrupt. This forces me to create an OOT GPIO driver with a changed API.

@tbursztyka
Copy link
Collaborator

@b0661 I don't see you issue being fixed by what's being proposed here (thus why these changes are not needed: they don't fix anything). Aren't you working on a complete rehearsal of the gpio/pin sub-sybstem in zephyr (pinctrl etc...)?

@pabigot
Copy link
Collaborator Author

pabigot commented Jun 28, 2019

@tbursztyka

This comment on work to clean up the GPIO API on proposes a change that moves the pin's interrupt-related configuration (viz. edge/level, high/low) from being performed through GPIO configuration into being provided in the callback structure.

So if the API may be changed, two questions:

Any changes related to @b0661's need may or may not be part of that PR, which primarily deals with GPIO device tree bindings. So I opened this to discuss what other interrupt configuration features might be worth considering.

I understand your perspective and will take it into consideration if I'm involved in any related concrete proposals.

@b0661
Copy link
Collaborator

b0661 commented Jun 28, 2019

@tbursztyka

Aren't you working on a complete rehearsal of the gpio/pin sub-sybstem in zephyr (pinctrl etc...)?

I´m working on a pinctrl subsystem that relates to pin configuration. Interrupt configuration is not part of it and the pinctrl subsystem is blocked anyway by the missing of appropriate code generation (or the decision on that). So it is not in scope of this discussion.

@anangl
Copy link
Member

anangl commented Jun 28, 2019

I somehow didn't realize earlier that it was possible to bind more than one callback structure to a given GPIO pin. And I don't think it would be reasonable to change this - @tbursztyka already provided a use case for such possibility. I can also imagine a need of having a common callback for a group of pins and separate specialized callbacks for individual pins from this group, and these callbacks added and removed from different modules (of course this can be also achieved in other ways but here we've got an API that already provides means for it).
Taking the above into account, moving the interrupt configuration to the callback structure doesn't seem to make sense. But I still think that it would be good to separate the configuration of the pin and configuration of the interrupt it can trigger, and as @b0661 argues there's an actual need for this. Maybe we could then move the interrupt related configuration flags to the parameter passed to gpio_enable_callback?
And actually, the function enabling a pin interrupt should be rather named gpio_enable_interrupt - this is what it actually does, and for some cases (like waking up a device) there's no need to have a callback called, the interrupt itself is enough. If we decide to modify the API, we could also consider such renaming.

@pabigot
Copy link
Collaborator Author

pabigot commented Jun 28, 2019

I'm still concerned about multiple devices changing the configuration of a pin without coordination and no way to detect or diagnose this situation.

What if we do put the interrupt configuration and some coordination flags in the structure that holds the callback, keep the distinct register and enable steps, and pass a reference to the (registered) callback structure in the enable operation?

Then, based on other flags in the callback-specific structure, we could validate the enable and configure operation. For example, we might have a "control" vs "observe" flag in the callback structure, where "observe" never changes the configuration but just ensures that the callback is invoked whenever the interrupt is generated according to the configuration set by the device that "controls" the pin. This handles the multiple consumer case without making them all responsible for configuration. (We could also have a "shared control" which allows them all to control with some defined semantics about compatible pin configuration and how to handle mixed enabled/disabled states, in case the multiple consumers can't rely on a single controller).

I too have a case where a single pin has two roles, one an active-low signal where I want to detect on falling edge, and one where it's a square wave and I want to detect on rising and falling edge. Changing the mode involves an I2C command to a peripheral. I could handle that with two registered callbacks, with only one of them enabled (and so in control) at any time.

@b0661
Copy link
Collaborator

b0661 commented Jun 28, 2019

What if we do put the interrupt configuration and some coordination flags in the structure that holds the callback, keep the distinct register and enable steps, and pass a reference to the (registered) callback structure in the enable operation?

LGTM. On every enable and disable operation the total (all currently enabled callbacks) interrupt configuration can be evaluated. Any enable request that would require an interrupt configuration that would jeopardize the current interrupt configuration can be rejected.

First idea of a possible interface. The callback configuration is split from the callback management to allow for constant configuration data to save ram memory.

/**
 * @brief GPIO callback configuration structure
 *
 * Used to configure a callback.
 */
struct gpio_callback_configuration {

	/** Actual callback function being called when relevant. */
	gpio_callback_handler_t handler;

	/**  A mask of trigger modes of pins the callback is interested in.
	 * If pin bit is set then edge mode is requested otherwise level mode.
	 */
	u32_t pin_trigger_mask;

	/** A mask of pins the callback is interested in.
	 * Callback is called if the pin rises to high or is on high level
	 * depending on the value of the pin_trigger_mask.
	 */
	u32_t pin_high_rising_mask;

	/** A mask of pins the callback is interested in.
	 * Callback is called if the pin falls to low or is on low level
	 * depending on the value of the pin_trigger_mask.
	 */
	u32_t pin_low_falling_mask;
}

/**
 * @brief GPIO callback structure
 *
 * Used to register a callback in the driver instance callback list.
 * As many callbacks as needed can be added as long as each of them
 * are unique pointers of struct gpio_callback.
 * Beware such structure should not be allocated on stack.
 *
 * Note: To help setting it, see gpio_init_callback() below
 */
struct gpio_callback {
	/** This is meant to be used in the driver and the user should not
	 * mess with it (see drivers/gpio/gpio_utils.h)
	 */
	sys_snode_t node;

	/** This is meant to be used in the driver and the user should not
	 * mess with it.
	 */
	bool enabled;

	/** The callback and interrupt configuration.
	 */
	const struct gpio_callback_configuration *config;
};

/**
 * @cond INTERNAL_HIDDEN
 *
 * GPIO driver API definition and system call entry points
 *
 * (Internal use only.)
 */
typedef int (*gpio_config_t)(struct device *port, int access_op,
			     u32_t pin, int flags);
typedef int (*gpio_write_t)(struct device *port, int access_op,
			    u32_t pin, u32_t value);
typedef int (*gpio_read_t)(struct device *port, int access_op,
			   u32_t pin, u32_t *value);
typedef int (*gpio_manage_callback_t)(struct device *port,
				      struct gpio_callback *callback,
				      bool set);
typedef int (*gpio_enable_callback_t)(struct device *port,
				      struct gpio_callback *callback);
typedef int (*gpio_disable_callback_t)(struct device *port,
				       struct gpio_callback *callback);
typedef u32_t (*gpio_api_get_pending_int)(struct device *dev);

struct gpio_driver_api {
	gpio_config_t config;
	gpio_write_t write;
	gpio_read_t read;
	gpio_manage_callback_t manage_callback;
	gpio_enable_callback_t enable_callback;
	gpio_disable_callback_t disable_callback;
	gpio_api_get_pending_int get_pending_int;
};

@tbursztyka
Copy link
Collaborator

There would be no need to separate the callback and the configuration in 2 structs. Actually it's weird to see the handler being in the config part and not the callback. Just just the new attributes in gpio_callback.

@pabigot
Copy link
Collaborator Author

pabigot commented Jul 2, 2019

There would be no need to separate the callback and the configuration in 2 structs.

I agree; I don't think the configuration will be big enough to justify splitting it to save RAM. I'd also like to see a list of the configuration attributes that would move from GPIO configuration to interrupt configuration. I suspect that using a single word (instead of two masks) to define the behavior is more flexible, and would allow sharing flags (like logic vs line level, debounce, pull, ....) that might be common for both pin roles.

@b0661
Copy link
Collaborator

b0661 commented Jul 2, 2019

Actually it's weird to see the handler being in the config part and not the callback. Just just the new attributes in gpio_callback.

Actually the gpio_callback struct is pure management data that could go away if the driver allocates it by itself and not forces the user to provide it. Currently it is also constraint to not be stack allocated.

I'd also like to see a list of the configuration attributes that would move from GPIO configuration to interrupt configuration

I don´t understand why interrupt configuration should be mixed with pin characteristic configuration. These are two distinct functions.

I suspect that using a single word (instead of two masks) to define the behavior is more flexible, and would allow sharing flags (like logic vs line level, debounce, pull, ....) that might be common for both pin roles.

This would mix interrupt configuration with pin configuration. The masks are purely for interrupt configuration and allow easy elaboration of the requested interrupt configuration for all selected pins of a gpio.

@mnkp
Copy link
Member

mnkp commented Aug 3, 2019

I've updated #16648 to partially address issued discussed here. In particular I've added gpio_pin_interrupt_configure() function which allows to configure interrupts independently from the main GPIO pin configuration. As a result interrupt can be configured on pins controlled by a peripheral different than the GPIO module (as a workaround until we have a proper pinmux driver). I've also added gpio_pin_interrupt_enable(), gpio_pin_interrupt_disable() functions and deprecated gpio_pin_enable_callback(), gpio_pin_disable_callback().

Since interrupt configuration can be quite complex: interrupt can be triggered on edge or level, logical or physical level I propose not to configure interrupts within a callback configuration but rather require user to do it via a dedicated gpio_pin_interrupt_configure() function. The gpio_add_callback() will still be able to configure the callback to be triggered on the rising or falling edge, however the required hardware level configuration should have already been done by gpio_pin_interrupt_configure(). The meaning of the pin_high_rising_mask parameter proposed by @b0661

	/** A mask of pins the callback is interested in.
	 * Callback is called if the pin rises to high or is on high level
	 * depending on the value of the pin_trigger_mask.
	 */
	u32_t pin_high_rising_mask;

will be more of a filter. Execute the callback on the rising edge only even if the underlying interrupt may be configured to be triggered on both edges. Nevertheless, in case gpio_add_callback() requires a callback to be triggered on the rising edge but the underlying pin has interrupt configured on the falling edge only the gpio_add_callback() should return an error.

Such approach will be much easier to implement and understand. Having gpio_add_callback() actually configure the hardware interrupt will be complex with potentially high code size penalty.

@b0661
Copy link
Collaborator

b0661 commented Aug 6, 2019

Moved from #16648

You are creating 6 functions to manage the callback on interrupt.

	int (*pin_interrupt_configure)(struct device *port, u32_t pin,
				       unsigned int flags);
	int (*pin_interrupt_enable)(struct device *port, u32_t pin);
	int (*pin_interrupt_disable)(struct device *port, u32_t pin);
	int (*manage_callback)(struct device *port, struct gpio_callback *cb,
			       bool set);
	int (*enable_callback)(struct device *port, int access_op, u32_t pin);
	int (*disable_callback)(struct device *port, int access_op, u32_t pin);

I think the same functionality can be achieved with a more minimal API:

struct gpio_callback {
	/** Actual callback function being called when relevant. */
	gpio_callback_handler_t handler;
	/** A mask of pins the callback is interested in.
	 * Callback is called if one of the pins is triggered according to pin_trigger_mode.
	 */
	u32_t pin_mask;
	/** Trigger mode of the pins the callback is interested in.  */
	u8_t pin_trigger_mode;
};
int (*enable_callback)(struct device *port, const struct gpio_callback *cb);
int (*disable_callback)(struct device *port, const struct gpio_callback *cb);
  • pin_interrupt_configure -> enable_callback
  • pin_interrupt_enable -> enable_callback
  • pin_interrupt_disable -> disable_callback
  • manage_callback -> enable_callback
  • enable_callback -> enable_callback
  • disable_callback -> disable_callback

Having gpio_add_callback() actually configure the hardware interrupt will be complex with potentially high code size penalty.

Complexity of the driver stays the same as all the functionality is anyway needed.

Benefit is that you may add several callbacks to a pin change and that the driver always knows about all callbacks (and the trigger_modes) for the pins. Misconfiguration or impossible configuration due to another callback requesting an incompatible trigger_mode can easily be detected. cb is a pointer to const to enable the callback struct data in Flash/ROM.

@mnkp
Copy link
Member

mnkp commented Aug 6, 2019

You are creating 6 functions to manage the callback on interrupt.

As I have mentioned in my last comment the gpio_pin_interrupt_enable(), gpio_pin_interrupt_disable() functions were added but gpio_pin_enable_callback(), gpio_pin_disable_callback() were deprecated. This is ultimately a rename operation as proposed by @anangl and discussed in this issue. The purpose of the gpio_add_callback(), gpio_pin_interrupt_enable() functions is very different. The former, an expensive one, is used to register a new callback to be triggered on the pin interrupt. The latter, a cheap one, is used to enable a pin interrupt. There may be more callbacks associated with one pin. Having a possibility to easily enable/disable pin interrupts is important as they can be used to wake up SoC from a sleep state. Executing callbacks is not the only task a pin interrupt can initiate.

Regarding the proposed API:

struct gpio_callback {
	/** Actual callback function being called when relevant. */
	gpio_callback_handler_t handler;
	/** A mask of pins the callback is interested in.
	 * Callback is called if one of the pins is triggered according to pin_trigger_mode.
	 */
	u32_t pin_mask;
	/** Trigger mode of the pins the callback is interested in.  */
	u8_t pin_trigger_mode;
};
int (*enable_callback)(struct device *port, const struct gpio_callback *cb);
int (*disable_callback)(struct device *port, const struct gpio_callback *cb);

As I already argued in my last comment that approach will result in a complex driver code that will also not be very transparent to the user trying to debug it. Let's take the following use case as an example where user always adds and removes callbacks on the same pin:

  • add/enable callback A active on the rising edge
  • add/enable callback B active on the falling edge
  • add/enable callback C active on both edges
  • remove/disable callback C
    That's not a trivial task if (*enable_callback), (*disable_callback) need to manage physical configuration of the interrupt. It becomes trivial if the physical configuration of the pin interrupt is managed by the gpio_pin_interrupt_configure() function.

Again, we may want to configure and enable pin interrupt to use it as a wake up source, not necessarily to execute a callback.

Having gpio_pin_interrupt_configure(), gpio_pin_interrupt_enable(), gpio_pin_interrupt_disable(), gpio_add_callback(), gpio_remove_callback() to manage GPIO pin interrupts - which pretty much means keeping the current design - gives more control to the user and is much simpler to implement.

@b0661
Copy link
Collaborator

b0661 commented Aug 6, 2019

That's not a trivial task if (*enable_callback), (*disable_callback) need to manage physical configuration of the interrupt. It becomes trivial if the physical configuration of the pin interrupt is managed by the gpio_pin_interrupt_configure() function

You are just moving the complexity from the driver developer to the application developer.

I attached my OOT GPIO driver. You may have a look at gpio_xxx_config_callback in gpio_xxx.c to judge the complexity. gpio_xxx.zip

driver code that will also not be very transparent to the user trying to debug

User (application developer) should not need to debug the driver code. With all the active callback struct data available to the driver the driver code can check any attempt to do an invalid interrupt configuration (see again gpio_xxx_config_callback in gpio_xxx.c).

Again, we may want to configure and enable pin interrupt to use it as a wake up source, not necessarily to execute a callback

Assign a NULL callback and you are done. Enabling an interrupt just for the purpose of getting an interrupt is not really related to GPIO.

The former, an expensive one, is used to register a new callback to be triggered on the pin interrupt. The latter, a cheap one, is used to enable a pin interrupt.

This assumes there is a common need to have "fast" interrupt enable/disable to prevent the interrupt code execution all together only for a certain time. Otherwise that can be done by a flag in the callback. Anyway on shared interrupts this will only prevent interrupt execution if there is no other callback related to the shared interrupt.

@mnkp
Copy link
Member

mnkp commented Aug 6, 2019

You are just moving the complexity from the driver developer to the application developer.

Not at all. We are talking about a very different approach to configuring interrupts. In the scenario involving gpio_pin_interrupt_configure() function application developer needs to decide beforehand if the callbacks will trigger on rising, falling or both edges. And then registers callbacks which are compatible with this configuration. In case of your proposal interrupt configuration should happen dynamically as callbacks are added or removed. This dynamic part is what drives the complexity.

Looking at the code in gpio_xxx.c, in function gpio_xxx_config_callback the part of code which needs to decide if the interrupt configuration of the new callback is compatible with the existing one looks as follows

/* check whether trigger mode is compatible to trigger mode already used */
for (int i = 0; i < ARRAY_SIZE(gpio_xxx_callbacks); i++) {
	if (i == callback_slot) {
		/* We do not check the slot that we will change/ add */
		continue;
	}
	if ((gpio_xxx_callbacks[i].dev == dev) &&
	    ((gpio_xxx_callbacks[i].callback->pin_mask & callback->pin_mask) != 0)) {
		/* callbacks are interested in the same pin */
		if (gpio_xxx_callbacks[i].callback->pin_trigger_mode != callback->pin_trigger_mode) {
			/* incompatible trigger mode */
			ret = -EINVAL;
			goto unlock_callbacks_mutex;
		}
	}
}

If the new configuration is not identical to the existing one you simply return -EINVAL. This means the scenario I mentioned

  • add/enable callback A active on the rising edge
  • add/enable callback B active on the falling edge
  • add/enable callback C active on both edges
  • remove/disable callback C

is not supported by your code. It is if we stick with gpio_pin_interrupt_configure() function.

Again, we may want to configure and enable pin interrupt to use it as a wake up source, not necessarily to execute a callback

Assign a NULL callback and you are done. Enabling an interrupt just for the purpose of getting an interrupt is not really related to GPIO.

It is very much related to GPIO. Assigning NULL pointer to configure interrupt without a callback works, it is also quite clumsy.

Possibility of using GPIO pin interrupt as a wake up source is an important use case that we cannot overlook. We do need a possibility to easily enable and disable pin interrupt without the need to run complex interrupt configure functions.

@pabigot
Copy link
Collaborator Author

pabigot commented Aug 6, 2019

@mnkp Your proposed sequence use case:

  • add/enable callback A active on the rising edge
  • add/enable callback B active on the falling edge
  • add/enable callback C active on both edges
  • remove/disable callback C

is interesting and I'd like to explore it.

First: for this to be validated as a use case we must assume that A, B, and C are from independent modules that cannot easily coordinate with each other: if they were all from related modules this could be handled by a single callback that demultiplexes to the appropriate condition-specific reactions. Agreed?

On the surface, this might derive requirements for these features:

  • A function that configures a pin for the conditions under which an hardware interrupt will be generated, but does not enable the interrupt;
  • A function that enables or disables the hardware interrupt on a pin that has been configured for interrupt use;
  • A registry associating callbacks (a function pointer and the conditions under which it is to be called) with pins;
  • The ability to enable and disable the individual callbacks that have been registered.

Second: is that the breakdown you envision, or are some of these combined (e.g. any registered callback is enabled, and disabling it unregisters it)?

This approach is complex. The conditions for enabled callbacks together form the set of conditions for the interrupt. So changing the set of callbacks can change the interrupt conditions. This may be inconsistent with the expectation that there is one function that configures the interrupt. What part of the system is responsible for ensuring the two are compatible, given that configuration and registration changes may be interleaved from uncoordinated modules?

I would like to see an outline of the data structures and functions that you propose, with clearly documented behavior. A possible outline would be:

  • how are interrupt conditions described (GPIO flags?)
  • what's in the callback structure (function pointer? interrupt conditions? opaque user-defined parameter?)
  • what's the relationship between registering a callback and enabling that callback?
  • what's the behavior when callback registration changes the set of conditions required for notification that the callback is needed?
  • under what conditions is it safe/permitted to change interrupt configuration or enable state, or to callback registration or enable state? For example, can one change the set of enabled (or registered) callbacks from within a callback? Can I reconfigure the interrupt from rising to falling edge and update the callback conditions? How do I do that given that the line conditions may change while I'm updating the configuration?

The PR does not have this complete; for example you've stated that gpio_add_callback() could select rising/falling edge conditions, but the PR does not seem to have the pieces necessary to do that.

If you wish you could try to do that in the PR, but I think details would be lost. I'd rather see it at a more abstract level, as we've been doing in #15611 for the pin/port output operations.

@b0661
Copy link
Collaborator

b0661 commented Aug 7, 2019

@mnkp

You are just moving the complexity from the driver developer to the application developer.

Not at all. We are talking about a very different approach to configuring interrupts. In the scenario involving gpio_pin_interrupt_configure() function application developer needs to decide beforehand if the callbacks will trigger on rising, falling or both edges. And then registers callbacks which are compatible with this configuration. In case of your proposal interrupt configuration should happen dynamically as callbacks are added or removed. This dynamic part is what drives the complexity

In my scenario callbacks have an expectation about the condition they are called. Thats why the callback struct also includes the pin mask and the pin trigger mode. In the scenario involving gpio_pin_interrupt_configure() callbacks have to be informed about the condition they are called and check by themselve whether this is an expected condition. In my scenario you always get the pin interrupt configuration the set of active callbacks needs - or you get an error return on callback enable. In the scenario involving gpio_pin_interrupt_configure() the application developer has to ensure to do a interrupt reconfiguration that is compatible to all callbacks currently registered and enabled.

The decision is just how much abstraction does an application developer want or need from a GPIO driver. From my perspective I just want my callback to make my application work. I don´t want to fiddle with interrupt reconfiguration and management of callbacks possibly enabled by some other threads.

This means the scenario I mentioned ... is not supported by your code.

You are right. I just wanted to show the total complexity of the code. You may replace the check by

if ((gpio_xxx_callbacks[i].callback->pin_trigger_mode | callback->pin_trigger_mode) >= GPIO_INT_HIGH_ACTIVE ) {

and it should be supported !-). I think this not a change in complexity.

We do need a possibility to easily enable and disable pin interrupt without the need to run complex interrupt configure functions.

I´m not against enable/disable pin interrupt functions. It´s just if all application code would do a enable callback, enable interrupt, ... disable interrupt, disable callback sequence it is just not worth to introduce it with some potential error in doing the sequence wrong (e.g. forgetting about the disable interrupt).
But as I do not know all application code let´s go for more flexibility and freedom and have the enable/disable interrupt functions.

Possibility of using GPIO pin interrupt as a wake up source is an important use case that we cannot overlook.

Yes. What I still do not understand is what shall the GPIO ISR do if the interrupt fires but there is no callback?

@pabigot
Copy link
Collaborator Author

pabigot commented Aug 7, 2019

I want to continue step back from solution details, and revisit an original proposal: The GPIO interrupt infrastructure only needs to support a single callback on any pin.

So far we have two use cases for multiple callback capability:

  • From @tbursztyka A soft reset: press a button and many modules need to react;
  • From @mnkp a sequence that registers three callbacks with different conditions.

Why is fanning out notifications the responsibility of the generic GPIO driver interrupt API?

A soft reset could easily be implemented by having a service monitor the interrupt and distribute change notifications to whatever wants them.

I have no idea what use case motivates three independent callbacks with different conditions.

Multiple callbacks on one pin introduces significant complexity, because there is no longer a single source defining whether the interrupt is enabled and what conditions must produce a hardware notification. It also prevents constant-time enabling/disabling within the interrupt handler.

Please justify why we need this capability.

@pabigot
Copy link
Collaborator Author

pabigot commented Aug 7, 2019

Possibility of using GPIO pin interrupt as a wake up source is an important use case that we cannot overlook.

Yes. What I still do not understand is what shall the GPIO ISR do if the interrupt fires but there is no callback?

That one's easy: nothing. Presumably there are other side effects, like a latched register, that can be checked on boot to determine the cause of a wakeup.

I have no problem with indicating "nothing" by providing a null function pointer in the callback configuration when the interrupt is configured. I do have a problem with recording fact-of an interrupt and expecting the infrastructure to invoke the callback when it's registered, if somebody's proposing that.

This is another use case that needs to be explored at a conceptual level.

@b0661
Copy link
Collaborator

b0661 commented Aug 7, 2019

Multiple callbacks on one pin introduces significant complexity, because there is no longer a single source defining whether the interrupt is enabled and what conditions must produce a hardware notification.

At least for the interrupt enable this scenario can not be avoided as e.g. in STM32 the interrupt is shared between several pins. So even if you have one callback per pin you may have multiple callbacks per interrupt with all the downsides like managing interrupt enable and non constant-time enabling/disabling.

@pabigot
Copy link
Collaborator Author

pabigot commented Aug 7, 2019

Multiple callbacks per interrupt doesn't cause a problem; you can in theory have an array of callback pointers indexed by pin, so the per-pin cost is constant once a triggering pin has been identified. Even if you use a sys_dlist it's bounded by the number of enabled pins.

The question remains: why do we need to support multiple callbacks on a single pin?

@mnkp
Copy link
Member

mnkp commented Aug 7, 2019

Before I answer individual questions I have some more input related to gpio_pin_enable_callback(), gpio_pin_disable_callback() functions. We've already established that despite the function name their API doesn't allow to enable/disable individual callbacks but rather to enable/disable pin interrupt to which a callback is attached. That's also how the function is mostly implemented at the moment by the gpio drivers. Therefore @anangl proposed (and I concurred) to rename the functions to gpio_pin_interrupt_enable(), gpio_pin_interrupt_disable().

I looked a bit more into the issue. The NXP gpio_mcux.c driver will not easily support gpio_pin_interrupt_enable(), gpio_pin_interrupt_disable() functions. In case of this hardware interrupt configuration on a pin is done by setting a 4 bit wide bit-field. Value 0 means pin interrupt is disabled, any other value encodes the interrupt type, edge/level, rising/falling, etc. If we call gpio_pin_interrupt_disable() and write 0 to the bit-field the interrupt configuration is lost. The gpio_pin_interrupt_enable() will not be able to restore previous configuration, we would need to call gpio_pin_interrupt_configure(). To work around the limitation and ensure the gpio_pin_interrupt_enable() can restore interrupt configuration the driver would need to cache it in RAM. 4-bits per pin times 32 pins per port gives 8 bytes of RAM per GPIO port.

Given this complication and taking into account that there may be more drivers like gpio_mcux.c it may be better to simply deprecate gpio_pin_enable_callback(), gpio_pin_disable_callback() functions without replacing them. Interrupt configuration, enabling and disabling the interrupt will happen exclusively via gpio_pin_interrupt_configure(). Then we would also have gpio_add_callback(), gpio_remove_callback() to add and remove callbacks and that's all.

@b0661
Copy link
Collaborator

b0661 commented Aug 7, 2019

Then we would also have gpio_add_callback(), gpio_remove_callback() to add and remove callbacks and that's all.

Is a callback related to the pin, a pin mask or the GPIO port?

@pabigot
Copy link
Collaborator Author

pabigot commented Aug 7, 2019

it may be better to simply deprecate gpio_pin_enable_callback(), gpio_pin_disable_callback() functions without replacing them. Interrupt configuration, enabling and disabling the interrupt will happen exclusively via gpio_pin_interrupt_configure(). Then we would also have gpio_add_callback(), gpio_remove_callback() to add and remove callbacks and that's all.

Or specify that the pin configuration is provided in the callback structure, and that there's only one of those per pin, in which case gpio_pin_interrupt_{en,dis}able() can be made to work very easily. Answering some of the higher-level questions like "what's in the callback structure" and

Is a callback related to the pin, a pin mask or the GPIO port?

would make it a lot easier to understand what you're proposing, whether it works for us, and what needs to be provided to use it and implement it effectively.

@mnkp
Copy link
Member

mnkp commented Aug 7, 2019

Is a callback related to the pin, a pin mask or the GPIO port?

would make it a lot easier to understand what you're proposing, whether it works for us, and what needs to be provided to use it and implement it effectively.

My intention at the moment is not to change much comparing to what we have. We can deprecate gpio_pin_enable_callback(), gpio_pin_disable_callback() functions since they don't do what they are supposed to do. Configure interrupts not in gpio_pin_configure() but a dedicated gpio_pin_interrupt_configure() function. And then we can add

	/** Trigger mode of the pins the callback is interested in.  */
	u8_t pin_trigger_mode;

as proposed by @b0661 with the difference that pin_trigger_mode will not influence the underlying pin interrupt configuration but merely encode on which interrupt edges the callback should be executed, i.e. pin_trigger_mode will function more like a filter. I mentioned that in my previous comment.

@mnkp
Copy link
Member

mnkp commented Aug 7, 2019

In general I'm in favor of a simple API that does few things but does it well. The more advanced features should still be easy to support or supported but not necessarily included by default. If we had an API that supports a single callback per interrupt but user could enable multiple callbacks per interrupt via a Kconfig option that would be fine with me.

@b0661
Copy link
Collaborator

b0661 commented Aug 7, 2019

Configure interrupts not in gpio_pin_configure() but a dedicated gpio_pin_interrupt_configure() function. And then we can add .... pin_trigger_mode ... (to the callback struct)

I still think the pin_mask in the callback struct is also valuable. Adding the same callback to several pins (e.g. for bit banging on a multiple line connection) requires to call gpio_pin_interrupt_configure() for every pin. If the pins happen to be on the same shared interrupt the callback is inserted multiple times in the callback list. Using the pin_mask may make that more convenient (only one gpio_interrupt_configuration) and save some entries in the callback list of shared interrupts. Furthermore the management information about the pin the callback is interested in the callback list is now in the callback struct and does not occupy driver memory and does not have to be in RAM.

@pabigot
Copy link
Collaborator Author

pabigot commented Aug 7, 2019

If we had an API that supports a single callback per interrupt but user could enable multiple callbacks per interrupt via a Kconfig option that would be fine with me.

So the drivers would still have to implement all the complexity of multiple callbacks per pin[*], but it'll only be used rarely for cases that nobody's justified as worth the effort.

I don't think that's a good plan.

[*] I assume you meant that rather than per interrupt. We know there can be multiple callbacks per invocation of an interrupt handler that's shared among multiple pins.

@mnkp
Copy link
Member

mnkp commented Aug 7, 2019

Configure interrupts not in gpio_pin_configure() but a dedicated gpio_pin_interrupt_configure() function. And then we can add .... pin_trigger_mode ... (to the callback struct)

I still think the pin_mask in the callback struct is also valuable.

I never mentioned we should remove it.

@pabigot
Copy link
Collaborator Author

pabigot commented Dec 22, 2019

I was just scanning this to understand exactly why we're deprecating the pin enable/disable callback operations in the new GPIO API.

Removing the calls and replacing them with enable/disable interrupt is part of what happens in #20017. However, this is not a functional replacement for the existing infrastructure, which technically allows multiple callbacks to be associated with a single interrupt pin and controlled individually.

That in turn exposes the concern that, to my knowledge, we do not have a task to document for release notes all the API changes that are incorporated into the topic branch and what they should be replaced by, nor do we have a roll-up list of all tasks that need to be completed before the topic branch is merged to master and placed firmly on the road to a release. #18530 converts drivers, #20017 converts in-tree users; those should be on that roll-up list.

@carlescufi IMO we should get started on this in the next API telecon.

@carlescufi
Copy link
Member

@pabigot good point, here's a start: #21789

@pabigot
Copy link
Collaborator Author

pabigot commented Feb 11, 2020

Some of this done in the GPIO rework, there's no strong motivation for dealing with the rest at this time. Possibly worth revisiting when things other than GPIO require configuring interrupt callbacks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Enhancement Changes/Updates/Additions to existing features
Projects
None yet
Development

No branches or pull requests

6 participants