|
|
Log in / Subscribe / Register

GPIO in the kernel: an introduction

GPIO in the kernel: an introduction

Posted Jan 19, 2013 0:33 UTC (Sat) by jimparis (guest, #38647)
In reply to: GPIO in the kernel: an introduction by etienne
Parent article: GPIO in the kernel: an introduction

Also those quality specialists tell you to use the macro:
#define IOWRITE32BYTE(addr, val) *((volatile unsigned *)(addr)) = (val)
(Hint: try to write the same addr twice with a newer compiler).

The volatile should ensure that both writes occur in that case.

Anyway on newer architecture, those are memory mapped and IHMO it is a lot cleaner to use C to declare them:
volatile struct my_IO_s {
enum { healthy, fail } power_state : 1;
unsigned power_active : 1;
} * const my_IO = (volatile struct my_IO_s *)0xDC002000;
Instead of pages and pages of #define.

I agree, and I do the same in embedded development, but it's not perfect. The three biggest problems:

  • You lose control over the access size. It can vary between compilers, compiler versions, architectures, and even ABIs for particular architectures. For example, see GCC bug #23623
  • They don't map well to unusual registers. Consider an interrupt flag register which behaves like:
        read 0: interrupt has not occurred
        read 1: interrupt has occurred
        write 0: no action
        write 1: clear interrupt flag
    
    To clear a flag, you can't just do:
        my_IO->timer_interrupt = 1;
    
    because this can become
        tmp = *my_IO_addr | (1 << TIMER_INTERRUPT_SHIFT);
        *my_IO_addr = tmp;
    
    Instead, you'd need to use something like:
        *my_IO = (struct my_IO_s) {
            .timer_interrupt = 1
        };
    
    which is not much better than the equivalent:
        *my_IO_addr = (1 << TIMER_INTERRUPT_SHIFT);
    
  • Even for a more normal register like a GPIO port, it's quite difficult to set two bits simultaneously with a single write. You can't do:
        *my_IO_addr |= (1 << PIN0_SHIFT) | (1 << PIN1_SHIFT);
    
    without something like:
       struct my_IO_s tmp = *my_IO;
       tmp.pin0 = 1;
       tmp.pin1 = 1;
       *my_IO = tmp;
    
Really it's one of the more annoying parts of C when doing embedded development on a constrained system, as it's hard to avoid without adding some layer of performance-killing indirection (like Arduino does).


to post comments

GPIO in the kernel: an introduction

Posted Jan 21, 2013 11:36 UTC (Mon) by etienne (guest, #25256) [Link]

>> #define IOWRITE32BITS(addr, val) *((volatile unsigned *)(addr)) = (val)
> The volatile should ensure that both writes occur in that case.

Last time I tried few days ago it did not work, you have to do:
#define IOWRITE32BITS(addr, val) do { \
volatile unsigned *ptr = (volatile unsigned *)(addr); \
*ptr = (val); \
} while (0)

> You lose control over the access size

Yes, I would also like to have bitfields accessed like they are declared, i.e. "char abit : 2;" accessed as a byte and "unsigned abit :2;" accessed as a 32 bits, because it is usual to have devices which cannot do short word access (lots of system on chip).

> [... using "(struct my_IO_s) {}" extension ...]

My system is more complex than Arduino, I have arrays of structures containing other arrays in these I/O areas, and all those #define blessed by software-quality people drive me mad.
#define FPGA1_FILTER_2_PARAMETER_4_ACTIVE 1
How to do a loop for each filters?
In short, in C or C++, unlike BASIC, the linker is made to manage addresses, the compiler manages offsets into these addresses; the C pre-processor has nothing to do there.
IHMO even GPIO in the kernel shall be identified with an address, not a #define.


Copyright © 2026, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds