cancel
Showing results for 
Search instead for 
Did you mean: 

Is it guaranteed that the original pointer is passed from the interrupt handlers to the callbacks when using HAL?

marcell46
Associate

I would like to implement a C++ wrapper around the STM32 HAL. I have a class called I2C_Handler, which manages the usual I2C HAL structs and functions. The only problem that I am having now are the callbacks, because the callbacks functions called by the HAL get e.g. a hi2c pointer parameter.

void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c);

What I do is that I define the IRQ handler in a way to call the interrupt handler function of my I2C_Handler object. Then in the class member interrupt handler, I call the HAL interrupt handler with a pointer to the class member hi2c I2C_HandleTypeDef struct. And in the end, the HAL interrupt handler will call the appropriate HAL callback. The problem is that in the callback function, I lost my C++ object context, I only have a I2C_HandleTypeDef* to work with, and I want to get my object back.

I know that there are alternative solutions to what I will present, but I am not looking for any other solution, I just want to know about this one.

Now, the C++ standard states that "A pointer to an object of standard-layout class type can be reinterpret_cast to pointer to its first non-static non-bitfield data member (if it has non-static data members) or otherwise any of its base class subobjects (if it has any), and vice versa."

Which means, that I could have a struct defined like this:

typedef struct {
    I2C_HandleTypeDef* hi2c_ptr;
    I2C_Handler* obj_ptr;
} I2C_HandleTypeDefContainer;

And in the interrupt handler of my class, I could do the following:

void I2C_Handler::handle_ev_it() {
    static_assert(std::is_standard_layout<I2C_HandleTypeDefContainer>::value);
    I2C_HandleTypeDefContainer container;
    container.hi2c_ptr = &(this->hi2c);
    container.obj_ptr = this;
    HAL_I2C_EV_IRQHandler(reinterpret_cast<I2C_HandleTypeDef*>(&container));
}

And then I could define the callbacks as follows:

void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c) {
    static_assert(std::is_standard_layout<I2C_HandleTypeDefContainer>::value);
    I2C_Handler* i2c_handler = reinterpret_cast<I2C_HandleTypeDefContainer*>(hi2c)->obj_ptr;
    /* do stuff with the object */
}

This is well-defined standard C++ ONLY IF the HAL_I2C_EV_IRQHandler calls the HAL_I2C_MasterTxCpltCallback with the same pointer that I passed to it.

I looked into the source code, and in my case, it is the same pointer. And to be honest, I cannot even think of a good reason why the callback would not be called with my original pointer. But theoretically it is possible. They could create a local struct in the interrupt handler for some reason and pass a pointer to that struct to the callback. And at this point my code is undefined behavior.

I did not find any note about this question in the HAL documentation. But if it is not guaranteed that it will be the same pointer, then this code is not portable without looking into the HAL.

Therefore my question: is it guaranteed that in the official HAL the callbacks are called with the same pointer that was passed to the interrupt handler? And I mean officially guaranteed, I am sure that they probably never do otherwise, but probably is not good enough.

3 REPLIES 3

> Therefore my question: is it guaranteed that in the official HAL the callbacks are called with the same pointer that was passed to the interrupt handler?

What does the Cube/HAL documentation say?

JW

I did not find that they say anything about this explicitly. There are function descriptions in the documentation, but it does not go into detail about how a function calls other functions.

It's just a pointer, how could it conceivably be changed without breaking everything?

Who'd write software that way?

Run Lint, MISRA or similar tools against the code.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..