Category Archives: programming tips

Mapping to device memory

1. mmap_device_io()

uintptr_t mmap_device_io( size_t len, uint64_t io );

  • maps len bytes of device I/O memory at io, and makes it accessiable via the in*() and out*() function.
  • returns a handle to the device’s I/O memory, or MAP_DEVICE_FAILED if an error occurs (errnois set).

Example:

uintptr_t vin_base;

ctx->vin_base = mmap_device_io(RCAR3_VIN_SIZE, RCAR3_VIN0_BASE); 

 if (ctx->vin_base == (uintptr_t)MAP_DEVICE_FAILED) {

                 ctx->vin_base = (uintptr_t)NULL; 

                 return errno;

   } 

Access the registers:

uint32_t reg_val;

reg_val = in32(vin_base + (off));

out32(vin_base + (off), (value));

2. mmap_device_memory()

void * mmap_device_memory( void * addr, size_t len,  int prot, int flags, uint64_t physical); 

  • maps len bytes of a device’s physical memory address into the caller’s address space at the location returned by mmap_device_memory().
  • Returns the address of the mapped-in object, or MAP_FAILED if an error occurs (errno is set).

example:

 uint32_t *ipu_regp;   

ipu_regp = (uint32_t*)mmap_device_memory(NULL, IPU_REGSIZE,                   PROT_READ|PROT_WRITE|PROT_NOCACHE, 0, ipu_regbase);

if (ipu_regp == MAP_FAILED) {

                  ipu_regptr = NULL;

                  return err; 

 }  

#define ipu_regptr(offset)  (uint32_t volatile *) (((unsigned char volatile *) ipu_regp) + offset)

#define IPU_CONF         ipu_regptr(IPU_CONF_OFFSET + 0x0)

uint32_t ipu_conf_val;

ipu_conf_val = *IPU_CONF;           

*IPU_CONF = ipu_conf_val;

Advertisements

Register knowledge

Access to the shared registers

  1. configuration registers, usually being modified via “read – modify – write”.
    • use mutex to protect the register access.
    • use irqspin lock, if the registers are being accessed by ISR handler.
    • NOTE: the if condition needs to be mutex/spinlock protected too, in the this example: if(!*IPU_CONF & (1 << 3)) *IPU_CONF |= (1 << 3);
  2.  status registers
    • “write 1 to clear” register
      • if the “write” operation is atomic, no protection is needed; otherwise, protect it.
      • however, if you perform a “read & write”, you need protect the entire ‘read-write” sequence.
    • “read to clear” register
      • protect doesn’t work in this case.

Register definition

  1. MAP_CONF(x) registers, each register contains one field, for map x only.
    • #define MAP_CONF(x)  (OFFSET + (x) * 4)
      • 0 — 0
      • 1– 4
      • 2– 8
  2. MAP_CONF(x) registers, each register contains two fields: bit 0 ~ 15 for map 0/2/4; bit 16 ~ 31 for map 1/3/5.
    • #define MAP_CONF(x)  (OFFSET + ((x) & ~0x1) * 2) or
    • #define MAP_CONF(x)  (OFFSET + ((x) / 2 ) * 4)
      • 0,1 — 0
      • 2,3– 4
      • 4,5—8
    • write to the correct bit fields
      • shift = (x & 1) * 16;
      • *MAP_CONF(x) |= val << shift;
  3. MAP_CONF(x) registers, each registers contains 3 fields: bit 0 ~ 10 for map 0/3/6; bit 11 ~ 20 for map 1/4/7, bit 21~30 for map 2/5/7.
    • #define MAP_CONF(x) (OFFSET + ((x)/3 * 4)
      • 0,1,2— 0
      • 3,4,5– 4
      • 6,7,8 — 8
    • write the correct bit fields
      • shift = (x%3) * 10;
      • *MAP_CONF(x) |= val << shift;
  4. An complicated example:
    • MAP_CONF(x) contains 6 fields, and defined as (OFFSET + ((x) / 2 ) * 4).
      • 30-26: mapping pointer for map #1 (or 3, 5, 7 when x increases) byte 2 —- 5
      • 25-21: mapping pointer for map #1 (or 3, 5, 7 when x increases) byte 1. —- 4
      • 16-20: mapping pointer for map #1 (or 3, 5, 7 when x increases) byte 0. —- 3
      • 14-10: mapping pointer for map #0 (or 2, 4, 6 when x increases) byte 2  —- 2
      • 9-5:     mapping pointer for map #0 (or 2, 4, 6 when x increases) byte 1. —- 1
      • 4-0:     mapping pointer for map #0 (or 2, 4, 6 when x increases) byte 0  —- 0
    • MAP_VAL(y) contains 4 fields, and defined as (OFFSET + ((y) / 2 ) * 4).
      • 28-24: offset #1 (or 3,5,7,..).
      • 23-16: mask #1
      • 12-8: offset #0 (or 2,4,6…)
      • 7-0:    mask #0
    • For a specified “map” value, e.g. 0, and 1, we want to set:
    • map   byte0         byte1         byte2
    • —————————————————
    • 0         7, 0xFF,  15, 0xFF,     23, 0xFF
    • 1         5, 0xFC,  11, 0xFC,     17, 0xFC
      • the register fields in MAP_CONF(0) will be set to the values, as marked as blue above.
      • for MAP_VAL(x), it will look like this
      •            MAP_VAL(0)      MAP_VAL(1)        MAP_VAL(2)
      • ————————————————————————————————
      • 28-24         15                      5                         17
      • 23-16          0xFF                 0xFC                  0xFC
      • 12-8            7                        23                      11
      • 7-0               0xFF                0xFF                  0xFC
    • the implementation of configure_map(map, offset_b0, mask_b0, offset_b1, mask_b1, offset_b2, mask_b2) would be:
      • shift = (map & 1) * 16;
      • pointer = map * 3;
      • *MAP_CONF(map) |=  ((pointer + 2) << 10 | (pointer +1 ) << 5 | (pointer) <<0 ) << shift;
      • // We need use “pointer”, “pointer + 1”, “pointer + 2” to find the offset of the associated MAP_VAL(y), and the shifts.
        • shift = (pointer & 1) * 16;
        • *MAP_VAL(pointer) |= ((offset_b0 << 8) | (mask_b0 << 0)) << shift;
        • shift = ((pointer +1) & 1) * 16;
        • *MAP_VAL(pointer+1) |= ((offset_b1 << 8) | (mask_b1 << 0)) << shift;
        • shift = ((pointer +2) & 1) * 16;
        • *MAP_VAL(pointer+2) |= ((offset_b2 << 8) | (mask_b2 << 0)) << shift;

two naive errors

Two pieces of fundamental knowledge:

  1. A local variable with the same name takes preference over the global variable, inside the function/block where the local variable is defined. The same applies to the function parameters.
  2. When program crashes with signo 11 (SIGSEGV), examine core dump by gdb, to locate where the crash occurs.

However, it took me a few hours before realizing it… I was so scared at signal handling,

case 1: The pointer to a context was defined as a local variable in main(). Now, we want to change the context in signal thread/signal hander, two methods:

  • Pass the pointer as an argument when call pthread_create(). // don’t work for signal handler; don’t work for the situation that that the pointer might change (context being destroyed and re-created).
  • Move the pointer declaration out of main().

so, I use the 2nd method. however, I made a mistake, leaving the old local variable still there. you can imagine, every time, when a signal is caught, the pointer is NULL.

I think, …It must be related to signal handling, maybe it is incorrect to share the pointer between signal handler and thread, this way..

 case 2:

 In signal handler of a customized app, it does something, then SIG_DFL(signo). The usual way is signal(signo, SIG_DFL). As the complier doesn’t complain, I think it is the other way to take the default action.

Will SIGBUS lead to a SIGSEGV? I didn’t find a proof while looking at the kernel code, then turned to a collegue.

he asked: what did GDB say?

I: Do you mean using GDB to trace the signal? How?

he said: At least, GDB can tell you where it crashes. where it crashes, does it say the “ip”?

 I realized I need treat it as a normal crash.. then it is so easy, as the system prints ” run fault pid xx tid xx code q ip 0″. it is obvious that the app was trying to access address 0, which is invalid.

the definition of SIG_DFL in signal.h,

#define SIG_DFL             ((void(*)(_SIG_ARGS))  0)

 

Use setjmp/longjmp for error handling

data to remember

For 60fps video, it takes 16.668333ms for each frame. 720×480, 28 ((1 / 16.668333 ) * 480) = 28 ) lines are transferred per ms. Practically, the lines are more than this number, as 16.668333 counts all the time, including the time for sync signals. What I saw is around 16ms (16.1, or 16.2, might be 16, or even a few us shorter than 16ms ).

programming tips: pre-filled pattern

Pre-fill a buffer with a pattern, and check how many lines have been changed, after a certain operation (e.g. transferring some image data from external interface into memory)
// the buffer is UYVY format, with each two bytes representing one pixel. The code writes 00000000 to the middle of each line

uint32_t *write_pos = (uint32_t *)(buf->pointer) + buf->stride /8;

int nlines = 0;

for(; nlines < buf->height; write_pos += buf->stride /4, nlines++) {

*write_pos = 0;

}

// Read each line from the top. If we only concern the bottom part, reset the counter “lines_untouched“, the value is changed. If we want to count every “hole”, no reset is needed.

uint32_t *read_pos = (uint32_t *)(buf->pointer) + buf->stride /8;

int nlines = 0;

int lines_untouched = 0;

for(; nlines < buf->height; read_pos += buf->stride /4, nlines++) {

if(*read_pos == 0) {

lines_untouched++;

}

else {

lines_untouched = 0;

}

}