[PATCH] Proposal for new GPIO API and example implementation for STM32F4 BSP

oss at c-mauderer.de oss at c-mauderer.de
Thu Jun 23 16:30:50 UTC 2022


Hello Duc,

Am 23.06.22 um 13:36 schrieb Duc Doan:
> On Tue, 2022-06-21 at 17:23 +0200, oss at c-mauderer.de wrote:
>> OK. So every BSP that want's to use that API will have a different
>> rtems_gpio_config_t (or every other structure) that is defined in
>> (for
>> example) bsp.h? The application has to know the details of the
>> current
>> BSP and initialize the structure accordingly.
>>
>> That means that if I write an application that can run on an STM32 or
>> alternatively on some RISC-V based CPU the API will be different. Not
>> really portable.
>>
> 
> Yes, you are right: rtems_gpio_config_t was defined by each BSP. I have
> fixed by making rtems_gpio_t and rtems_gpio_config_t concrete structs
> that hold common information among architectures. They also have a
> pointer to a bsp-specific structure if required, like in the current
> API.
>     /**
>        * @brief Structure for a GPIO object. It holds information
>        *        like port number and pin number/pin mask.
>        *
>        */
>      typedef struct {
>          void *port;                 /* Pointer to the GPIO port */
>          uint32_t pin_mask;          /* The pin number or pin mask */
>          bool is_expander;           /* If the GPIO is an expander, set to
>     true.
>                                         Else false. */
>          uint32_t expander_id;       /* This field specifies which GPIO
>     expander is
>                                         in use. In case of using multiple
>     expanders,
>                                         this field is necessary to handle
>     each. */
>          void *bsp;                  /* Pointer to BSP-specific data */
>      } rtems_gpio_t;
>     
>     /**
>        * @brief Structure for configuration of a GPIO object.
>        */
>      typedef struct {
>          rtems_gpio_pin_mode mode;   /* Pin mode */
>          rtems_gpio_pull pull;       /* Pull resistor configuration */
>          void *bsp;                  /* Pointer to BSP-specific config */
>      } rtems_gpio_config_t;
> 
> Hopefully this makes the application code more portable. I have also
> updated the blink code:
> https://github.com/dtbpkmte/GSoC-2022-RTEMS-Sample-Apps/blob/main/RTEMS_Blink_API/src/init.c
> This time, for simple tasks like basic I/O, users won't need to care
> about the details of a BSP.
> 
> One thing I am not sure about is that do all architectures have ports
> and pins for GPIO? I am worried that my structure is still skewed
> towards STM32 because I don't have much knowledge about different types
> of microcontrollers.

Most controllers I've worked with use that organization but I'm not sure 
whether it's true for all controllers. Isn't the controller on the 
raspberry an example where there is only one big group of GPIO0 to 
GPIO<$BigNumber>?

But I think you can always organize pins in that way. Only make sure 
that you don't assume that every controller has a maximum of 32 pins. If 
there is only one GPIO controller on a system that has 400 pins, that 
shouldn't be a problem. The same is true if you have 400 controllers 
with one pin each. The logical structure works in both cases.

Note that not all controllers have to be the same type. See my notes 
below regarding the I2C GPIO chip.

> 
>> If you ask me: We have SYSINIT functions for this kind of
>> initializations. If something should be done at about bsp_start, the
>> user can register a function at RTEMS_SYSINIT_BSP_START.
> 
> Thank you for the suggestion. This is what I have and it seems to be
> working:
> 
>     RTEMS_SYSINIT_ITEM(
>         rtems_gpio_initialize,
>         RTEMS_SYSINIT_BSP_START,
>         RTEMS_SYSINIT_ORDER_LAST
>     );
> 
>>
>>
>> I think I haven't written clearly what I meant: Let's assume a I2C
>> chip
>> like the TCA9537 from TI (can be any other GPIO expander from any
>> other
>> vendor): You connect it to a I2C bus and control 4 GPIOs with it.
>> It's a
>> GPIO so a good GPIO API should be able to handle it.
>>
>> In that case the GPIO API would do some I2C transfers under the hood
>> if
>> you switch or read a GPIO. So what happens if some error happens
>> during
>> one of these transfers. If the only error option is UNSATISFIED, the
>> application can't distinguish the errors. It only knows something
>> didn't
>> work. It can't (for example) retry only on certain error cases like
>> if
>> the bus is busy.
>>
>> Please also note that the I2C GPIO expander is one example where a
>> BSP
>> would have two completely different GPIO controllers: A number of
>> integrated ones and a number of I2C ones. A generic API should be
>> able
>> to handle that case too.
> 
> I understand what you mean now. I have added that capability to by
> creating the field is_expander to select between integrated GPIO and
> expander. I also have a field called expander_id to select among
> multiple expanders. You can see those in the rtems_gpio_t struct above.
> The API function prototypes stay the same, but the BSP now need to
> implement 2 functions in private: one is the "*_default" function which
> controls the built-in GPIO and the other is the "*_ex" function which
> controls the expander. Here are the example read and write functions,
> which are in bsps/shared/dev/gpio/gpio.c:
> 
>     rtems_status_code rtems_gpio_write_pin(rtems_gpio_t *gpiox,
>     rtems_gpio_pin_state value) {
>         if (gpiox->is_expander) {
>             return rtems_gpio_write_pin_ex(gpiox, value);
>         } else {
>             return rtems_gpio_write_pin_default(gpiox, value);
>         }
>     }
>     
>     rtems_status_code rtems_gpio_read_pin(rtems_gpio_t *gpiox,
>     rtems_gpio_pin_state *value) {
>         if (gpiox->is_expander) {
>             return rtems_gpio_read_pin_ex(gpiox, value);
>         } else {
>             return rtems_gpio_read_pin_default(gpiox, value);
>         }
>     }
>>>
> Please give me more feedback. Thank you very much.

That solution is still not really flexible either. You now support one 
type of expander. But what if I have an I2C keyboard controller 
connected that has some GPIO pins to control LEDs and an SPI GPIO 
expander for some other stuff?

If you create an API don't think in these fixed structures. You have to 
think more in an object oriented way: Each controller is managed by one 
driver object. If you have a controller with three internal GPIOs you 
register three objects of the internal GPIO type. If you add an I2C 
expander, you register that object too.

 From an application point of view you somehow organize the object (some 
global object in the BSP; a file that you can open; some function that 
returns pointers to the object; ...) and then use device independent 
access functions.

The (thin) API layer is then only responsible for generic stuff like 
locking. Then it calls device specific function (for example via 
function pointer in the object) to let the device driver do the work.

Some examples for bigger APIs are termios, i2c, spi, ...

For example for termios: From a driver perspective (I picked the imxrt 
because I worked with that recently - you can take a look at every other 
BSP too) you create a number of handlers that you put in a structure:

 
https://git.rtems.org/rtems/tree/bsps/arm/imxrt/console/console.c?id=7141afbb0ea#n322

You then use that when you register your driver

 
https://git.rtems.org/rtems/tree/bsps/arm/imxrt/console/console.c?id=7141afbb0ea#n451

In the handlers you have some method to get your device specific context:

 
https://git.rtems.org/rtems/tree/bsps/arm/imxrt/console/console.c?id=7141afbb0ea#n103

For GPIO the really tricky part is to make the layer really thin to make 
it fast. Termios uses the typical file structure. I think for GPIO I 
would skip that and just use the objects directly. For example (in 
Pseudo-code - you have to put some more thought into that, define nice 
types and similar):


Application visible part:

struct rtems_gpio_ctrl {
   rtems_mutex mtx;
   const struct rtems_gpio_handler *handlers;
};

rtems_status_code rtems_gpio_set_pin(
     struct rtems_gpio *gpio_ctrl, unsigned pin
);

... More access functions ...


Driver visible part (additional to the application visible one):

struct rtems_gpio_handlers {
   some_function_pointer_type *init;
   another_function_pointer_tyupe *set_pin;
   ... More access functions ...
};


Example driver:

struct example_gpio_ctrl {
   struct rtems_gpio_ctrl base;
   volatile uint32_t *example_reg;
   ...
}

static rtems_status_t example_gpio_ctrl_set_pin(struct rtems_gpio_ctrl 
*base, unsigned pin)
{
   struct example_gpio_ctrl *ctx = get_ctx_from_base(base);
}

... more functions ...

struct rtems_gpio_handlers example_gpio_handlers = {
   .init = example_gpio_ctrl_init,
   .set_pin = example_gpio_set_pin,
   ...
}

struct example_gpio_ctrl[2] = {
    ... create instances here ...
};

void register(void) {
   /* The following will initialize the base structure and call the 
driver init afterwards. */
   rtems_gpio_register(
     &example_gpio_ctrl[0].base, example_gpio_handlers);
   rtems_gpio_register(
     &example_gpio_ctrl[1].base, example_gpio_handlers);
}


Application:

struct rtems_gpio_ctrl *ctrl = get_pointer_to_instance_from_somewhere();
rtems_gpio_set(ctrl, 5);


Again: The tricky part is to make that layer thin. Maybe I would even 
think about skipping something like the mutex in the base structure if 
it is not necessary for the API itself because some BSPs might don't 
need it.

Another tricky part can be how to handle pins or pin groups. At the 
moment I just used an "unsigned" for a single pin. That wouldn't be able 
to handle a pin group. Maybe a pin group needs it's own type or some 
kind of extensible type. For the usual controller with <= 32 bit a mask 
in a uint32_t would be great. For other controllers maybe a longer mask 
is necessary. That's a bit tricky.

Best regards

Christian

> 
> Best,
> 
> Duc Doan
> 
>>
> 


More information about the devel mailing list