Skip to content

Commit d414c86

Browse files
committed
drivers: gpio: fix mis-use of slist API in callback processing
The iterator over registered callbacks failed to account for the possibility that the callback would remove itself from the list. If this occurred any remaining callbacks would no longer be reachable from the node. Switch to the slist iterator that is safe for self-removal. Note that the slist API remains unsafe for removal of subsequent nodes. Even with the corrected code removal of the next callback registration (cached in tmp) will result in it being called anyway, with the remaining unremoved registrations not being called. If the next callback were removed and re-registered on a different device, the callbacks would be invoked for the wrong device. Resolve this by a documentation change describing the conditions under which a change to callback registration from within a callback are permitted. Add a similar note regarding the effect of adding a callback. The current event invocation behavior for callbacks added within an event is explicitly left unspecified, though in the current slist implementation newly added callbacks will not be invoked until the next event. Closes #10186 Signed-off-by: Peter A. Bigot <[email protected]>
1 parent e0e2a12 commit d414c86

File tree

5 files changed

+84
-7
lines changed

5 files changed

+84
-7
lines changed

drivers/gpio/gpio_utils.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ static inline void _gpio_fire_callbacks(sys_slist_t *list,
4444
struct device *port,
4545
u32_t pins)
4646
{
47-
struct gpio_callback *cb;
47+
struct gpio_callback *cb, *tmp;
4848

49-
SYS_SLIST_FOR_EACH_CONTAINER(list, cb, node) {
49+
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(list, cb, tmp, node) {
5050
if (cb->pin_mask & pins) {
5151
__ASSERT(cb->handler, "No callback handler!");
5252
cb->handler(port, cb, pins);

include/gpio.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,10 @@ static inline void gpio_init_callback(struct gpio_callback *callback,
252252
* @param callback A valid Application's callback structure pointer.
253253
* @return 0 if successful, negative errno code on failure.
254254
*
255+
* @note Callbacks may be added to the device from within a callback
256+
* handler invocation, but whether they are invoked for the current
257+
* GPIO event is not specified.
258+
*
255259
* Note: enables to add as many callback as needed on the same port.
256260
*/
257261
static inline int gpio_add_callback(struct device *port,
@@ -271,6 +275,13 @@ static inline int gpio_add_callback(struct device *port,
271275
* @param callback A valid application's callback structure pointer.
272276
* @return 0 if successful, negative errno code on failure.
273277
*
278+
* @warning It is explicitly permitted, within a callback handler, to
279+
* remove the registration for the callback that is running, i.e. @p
280+
* callback. Attempts to remove other registrations on the same
281+
* device may result in undefined behavior, including failure to
282+
* invoke callbacks that remain registered and unintended invocation
283+
* of removed callbacks.
284+
*
274285
* Note: enables to remove as many callbacks as added through
275286
* gpio_add_callback().
276287
*/

tests/drivers/gpio/gpio_basic_api/src/main.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ void test_main(void)
5353
ztest_unit_test(test_gpio_callback_edge_low),
5454
ztest_unit_test(test_gpio_callback_level_high),
5555
ztest_unit_test(test_gpio_callback_add_remove),
56+
ztest_unit_test(test_gpio_callback_self_remove),
5657
ztest_unit_test(test_gpio_callback_enable_disable),
5758
ztest_unit_test(test_gpio_callback_level_low));
5859
ztest_run_test_suite(gpio_basic_test);

tests/drivers/gpio/gpio_basic_api/src/test_callback_manage.c

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,18 @@ static void callback_2(struct device *dev,
3030
TC_PRINT("%s triggered: %d\n", __func__, ++cb_cnt[1]);
3131
}
3232

33-
static void init_callback(struct device *dev)
33+
static void callback_remove_self(struct device *dev,
34+
struct gpio_callback *gpio_cb, u32_t pins)
35+
{
36+
struct drv_data *dd = CONTAINER_OF(gpio_cb, struct drv_data, gpio_cb);
37+
38+
TC_PRINT("%s triggered: %d\n", __func__, ++cb_cnt[1]);
39+
dd->aux = gpio_remove_callback(dev, gpio_cb);
40+
}
41+
42+
static void init_callback(struct device *dev,
43+
gpio_callback_handler_t handler_1,
44+
gpio_callback_handler_t handler_2)
3445
{
3546
gpio_pin_disable_callback(dev, PIN_IN);
3647
gpio_pin_disable_callback(dev, PIN_OUT);
@@ -44,10 +55,10 @@ static void init_callback(struct device *dev)
4455
GPIO_DIR_IN | GPIO_INT | GPIO_INT_EDGE | \
4556
GPIO_INT_ACTIVE_HIGH | GPIO_INT_DEBOUNCE);
4657

47-
gpio_init_callback(&cb_data[0].gpio_cb, callback_1, BIT(PIN_IN));
58+
gpio_init_callback(&cb_data[0].gpio_cb, handler_1, BIT(PIN_IN));
4859
gpio_add_callback(dev, &cb_data[0].gpio_cb);
4960

50-
gpio_init_callback(&cb_data[1].gpio_cb, callback_2, BIT(PIN_IN));
61+
gpio_init_callback(&cb_data[1].gpio_cb, handler_2, BIT(PIN_IN));
5162
gpio_add_callback(dev, &cb_data[1].gpio_cb);
5263
}
5364

@@ -73,7 +84,7 @@ static int test_callback_add_remove(void)
7384
struct device *dev = device_get_binding(DEV_NAME);
7485

7586
/* SetUp: initialize environment */
76-
init_callback(dev);
87+
init_callback(dev, callback_1, callback_2);
7788

7889
/* 3. enable callback, trigger PIN_IN interrupt by operate PIN_OUT */
7990
trigger_callback(dev, 1);
@@ -109,12 +120,58 @@ static int test_callback_add_remove(void)
109120
return TC_FAIL;
110121
}
111122

123+
static int test_callback_self_remove(void)
124+
{
125+
int res = TC_FAIL;
126+
struct device *dev = device_get_binding(DEV_NAME);
127+
128+
/* SetUp: initialize environment */
129+
init_callback(dev, callback_1, callback_remove_self);
130+
131+
gpio_pin_write(dev, PIN_OUT, 0);
132+
k_sleep(100);
133+
134+
cb_data[0].aux = INT_MAX;
135+
cb_data[1].aux = INT_MAX;
136+
137+
/* 3. enable callback, trigger PIN_IN interrupt by operate PIN_OUT */
138+
trigger_callback(dev, 1);
139+
140+
/*= checkpoint: check both callbacks are triggered =*/
141+
if (cb_cnt[0] != 1 || cb_cnt[1] != 1) {
142+
TC_ERROR("not trigger callback correctly\n");
143+
goto err_exit;
144+
}
145+
146+
/*= checkpoint: check remove executed is invoked =*/
147+
if (cb_data[0].aux != INT_MAX || cb_data[1].aux != 0) {
148+
TC_ERROR("not remove callback correctly\n");
149+
goto err_exit;
150+
}
151+
152+
/* 4 enable callback, trigger PIN_IN interrupt by operate PIN_OUT */
153+
trigger_callback(dev, 1);
154+
155+
/*= checkpoint: check only remaining callback is triggered =*/
156+
if (cb_cnt[0] != 1 || cb_cnt[1] != 0) {
157+
TC_ERROR("not trigger remaining callback correctly\n");
158+
goto err_exit;
159+
}
160+
161+
res = TC_PASS;
162+
163+
err_exit:
164+
gpio_remove_callback(dev, &cb_data[0].gpio_cb);
165+
gpio_remove_callback(dev, &cb_data[1].gpio_cb);
166+
return res;
167+
}
168+
112169
static int test_callback_enable_disable(void)
113170
{
114171
struct device *dev = device_get_binding(DEV_NAME);
115172

116173
/* SetUp: initialize environment */
117-
init_callback(dev);
174+
init_callback(dev, callback_1, callback_2);
118175

119176
/* 3. enable callback, trigger PIN_IN interrupt by operate PIN_OUT */
120177
trigger_callback(dev, 1);
@@ -155,6 +212,12 @@ void test_gpio_callback_add_remove(void)
155212
test_callback_add_remove() == TC_PASS, NULL);
156213
}
157214

215+
void test_gpio_callback_self_remove(void)
216+
{
217+
zassert_true(
218+
test_callback_self_remove() == TC_PASS, NULL);
219+
}
220+
158221
void test_gpio_callback_enable_disable(void)
159222
{
160223
zassert_true(

tests/drivers/gpio/gpio_basic_api/src/test_gpio.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ struct drv_data {
6161
struct gpio_callback gpio_cb;
6262
int mode;
6363
int index;
64+
int aux;
6465
};
6566

6667
void test_gpio_pin_read_write(void);
@@ -69,6 +70,7 @@ void test_gpio_callback_edge_low(void);
6970
void test_gpio_callback_level_high(void);
7071
void test_gpio_callback_level_low(void);
7172
void test_gpio_callback_add_remove(void);
73+
void test_gpio_callback_self_remove(void);
7274
void test_gpio_callback_enable_disable(void);
7375

7476
#endif /* __TEST_GPIO_H__ */

0 commit comments

Comments
 (0)