Hello Duc,

Am 23.06.22 um 13:36 schrieb Duc Doan:
On Tue, 2022-06-21 at 17:23 +0200, o...@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



_______________________________________________
devel mailing list
devel@rtems.org
http://lists.rtems.org/mailman/listinfo/devel

Reply via email to