Wikia

CHDK Wiki

CHDK Coding Guidelines

Talk0
565pages on
this wiki
Revision as of 23:28, October 16, 2010 by ReyalP (Talk | contribs)

THIS IS A WORK IN PROGRESS

Introduction

This page documents best practices and common pitfalls specific to CHDK code and the ARM platform. The aim is to provide developers who are new to CHDK with the information they need to write safe, correct code.

Avoiding physical damage to the camera

If your code directly manipulates hardware or certain propcases, there is a very real danger that bad code could result in permanent physical damage to the camera. For example, commanding lens hardware when the lens is retracted. If your code interacts with hardware, think very carefully about when the operation should be allowed, and abort or panic if the requirements are not met. High level functions in the Canon generally perform sanity checking, but you should not assume that all functions do.

Memory allocation

Available Memory

Most cameras have a total of between 300KB and 1MB available to malloc()/umalloc() after CHDK is loaded. If you need to manipulate large amounts of data, consider working in chunks or hijacking some other part of memory. Keep in mind that the Canon firmware needs to be able to allocate memory too. See CHDK/Camera RAM memory usage for more information.

General recommendations for memory use

  • Use auto (local) variables for things that are a few hundred bytes or smaller.
Notes
  • The total stack size in most CHDK code is 8KB. If you use large amounts of autos or stack parameters, consider how deeply it is likely to call or be called from.
  • The structure of the GUI code is such that it is frequently impossible to use stack memory. Local variables in a menu or dialog callback will not be valid after the callback returns.
  • Use static and global variables only where required.
  • Use dynamic memory (malloc()/free()) for large data structures that do not have to exist for the life of the program.
Notes
  • malloc() memory does have overhead, so allocating lots of very small data structures is inefficient.
  • memory fragmentation is a concern.
  • Use function calls rather than copy/pasting similar code, avoid large macros or inlines unless you really need them. Small, simple functions are extremely cheap. Don't unroll loops unnecessarily.
  • Consider loading large amounts of constant data from disk, rather than hard coding it.

Memory APIs and CPU caching

malloc() / free() operate just like they do in C. umalloc() / ufree() work on addresses for which the CPU cache and write buffer is not enabled. Memory that is directly read/written by hardware should usually be uncached. Other memory should generally be cached.

Notes

  • Uncached memory should not be used in processing intensive operations, since this will be much slower.
  • Both cached and uncached memory come from the same pool, they are simply referred to with different addresses. In other words, either one counts against the same 300K-1M mentioned above.
  • Do not use free() on memory allocated with umalloc or ufree() on memory allocated with malloc()

TODO MANIPULATING ADDRESSES TO REFER TO CACHED/UNACHED MEMORY. ALSO ENABLE/DISABLE/FLUSH

Disk IO

Several file IO APIs are available in CHDK. Each has specific requirements and limitations. Failing to respect these can result in invalid data being read or written.

Low level io with open() read() and write()

These functions correspond roughly to the unix syscalls of the same name. They operate on file handles, which are numbered starting from zero. Invalid filehandles are < 0. In CHDK, read() and write() must be used only with uncached memory. This is typically obtained using umalloc.

Notes

  • The uncached requirement means you should not use read() and write() with normal C variables. e.g. int x; read(fd,&x,sizeof(x)); is unsafe.
  • Not all cameras behave the same way. Just because it appears to work on yours doesn't mean it's OK.
  • Because uncached memory access is slow, if you are loading data that will be used frequently or for extensive calculations, you should copy it to normal memory. Alternately, use the stdio functions described below.
  • Internally (in the sig and stubs files) the functions that make up this API are referred to with a capital letter, i.e. _Open, _Read, _Write. The wrappers cause these to be used when you call the lower case variants in CHDK. The low level lower case variants (_open etc) should not be used.

(almost) ANSI stdio with fopen() et al.

The Fut API provides higher level, buffered io which corresponds roughly to C stdio. CHDK wraps this to provide the normal stdio functions in a way that is mostly compatible with a normal ANSI libc. This API uses FILE * and does not require the user to provide uncached memory.

Notes

  • Each open file handle allocates a little over 32KB of memory. This memory is freed when the file is closed, but if more memory is allocated (but not freed) between the open and close, memory fragmentation will result. Example
    U = used memory, F = free memory.
    At the start, you can allocate up to 4 blocks:
    FFFF
    Open file, using one block:
    UFFF
    Allocate another block:
    UUFF
    Close the file. Now you have 3 blocks free, but the largest contiguous amount you can allocate is 2 blocks:
    FUFF
  • The FILE * used by Fut funtions is not compatible with other functions that might take a FILE *.
  • Some fields of the FILE structure have been reverse engineered, but you should avoid using them directly unless there is no way to get equivalent functionality from the camera APIs.

Raw DISK IO

TODO

Other functions

There several other IO APIs found in the firmware (for example, the original vxworks stdio and the lower case open/read/write) however these generally do not work reliably and should not be used.

Calling functions from firmware assembly

Calling original ROM functions

When calling the original camera ROM from CHDK inline assembler, it is important to understand the B and BL instructions. From "ARM Architecture Reference Manual" A4.1.5 B, BL

Specifies the address to branch to. The branch target address is calculated by:

  1. Sign-extending the 24-bit signed (two's complement) immediate to 30 bits.
  2. Shifting the result left two bits to form a 32-bit value.
  3. Adding this to the contents of the PC, which contains the address of the branch instruction plus 8 bytes.

1) means that you cannot B or BL directly from a RAM address to a ROM address, since the offset is more than 24 bits. While the assembler may silently accept BL 0x<constant> or BL =0x<constant>, it will treat the value as a 24 bit PC relative offset, truncating or otherwise discarding any additional bits. This will NOT produce a jump to the specified address.

In platform/sub code, this is handled by the build process. C files mentioned in STUBS_AUTO_DEPS are scanned for B/BL sub_<address> and an appropriate stub is added to stubs_auto.S

From other code you may simulate a B instruction with

LDR PC, =<address>

or a BL instruction with

MOV LR, PC
LDR PC, =<address>

Note that MOV instruction has special behavior when PC is the source, such that the two instruction sequence above produces the correct return address.

Functions mentioned in stubs_entry.S or stubs_entry_2.S may also be called directly by name using B/BL.

Calling CHDK code

It is frequently necessary to call a C (or self written asm) function from disassembled firmware code. It is extremely important that you understand the arm calling convention and preserve any required registers.

Example

    MOV     R0, R4 ; this will be the first argument to the following BL call
 
    BL      sub_FFD483A4 ; call some function in Canon ROM
    BL      capt_seq_hook_raw_here ; call our function here
 
    MOV     R2, R4 ; load up the registers for the next call
    MOV     R1, #1
    BL      sub_FFD43578 ; call another function in Canon ROM
; ...

In this code, the call to the C function capt_seq_hook_raw_here is added to assembly code obtained by disassembling the canon firmware.

In the arm calling convention, a C function gets it's first four arguments in R0-R3, and is not required to preserve them. It is required to preserve other registers. Because of this, GCC will generate code for capt_seq_hook_raw_here which leaves these registers in an undetermined state after the call returns.

In the above case, because the call to the new function is added immediately after an existing call, we know that the firmware already expects R1-R3 to be undefined, so the fact that our function might clobber them is unimportant.

We do have to worry about R0, because it is also used for the return value. Given that the firmware loads R2 and R1, but not R0 before calling the final function (sub_FFD43578), we can assume that the return value of the preceding function (sub_FFD483A4) is expected to be the first argument to this call. If our call to capt_seq_hook_raw_here changes R0, problems could result. One easy way to avoid this is to define your function to take one argument and return it unchanged, like this:

int capt_seq_hook_raw_here(int save_R0) {
// ... your code goes here
    return save_R0;
}

This ensures that GCC saves the value of R0, since it will take whatever value is already in R0 as it's first argument, and return it using R0.

Now consider if we put our call a few lines later:

    MOV     R0, R4 ; this will be the first argument to the following BL call
 
    BL      sub_FFD483A4 ; call some function in Canon ROM
 
    MOV     R2, R4 ; load up the registers for the next call
    MOV     R1, #1
    BL      capt_seq_hook_raw_here ; call our function here
    BL      sub_FFD43578 ; call another function in Canon ROM
; ...

This is clearly wrong, because R2 and R1 were set up for the final BL, but now our call is going to (potentially) stomp on them.

The best practice is to insert your call immediately after an existing call, preserving R0. If you want to insert calls into arbitrary assembly code, you have to preserve all registers and possibly the CPU status word.

You can carefully analyze the firmware code to decide exactly what needs to be saved, but keep in mind others might modify your code without noticing these details.

Naked functions

Many functions in CHDK use __attribute__((naked)). It is important to understand what this means. From the GCC manual

naked

Use this attribute on the ARM, AVR, IP2K and SPU ports to indicate that the specified function does not need prologue/epilogue sequences generated by the compiler. It is up to the programmer to provide these sequences. The only statements that can be safely included in naked functions are asm statements that do not have operands. All other statements, including declarations of local variables, if statements, and so forth, should be avoided. Naked functions should be used to implement the body of an assembly function, while allowing the compiler to construct the requisite function declaration for the assembler.

(emphasis added)

Some CHDK functions violate these rules. Other than pure asm functions, the common idiom is

void __attribute__((naked,noinline)) some_func()
{
 asm volatile("STMFD   SP!, {R0-R12,LR}\n");
... some C code...
 asm volatile("LDMFD   SP!, {R0-R12,PC}\n");
}

The intent is to save all registers, to allow C code to be called from arbitrary asm. This is wrong, and should be eliminated from the CHDK code. However, it appears to work with the current toolchains if some rules are followed:

  • No local variables. The compiler will not allocate stack space for them. Statics are probably OK.
  • No arguments. As above.
  • No return statements. The stack will not be set up correctly.

Any function using this should be as simple as possible. A much safer way to do this would be putting the register save/restore in the caller, possibly using macros.

Integral data types

On CHDK platforms, long and int are both a 32 bit integer. Pointers are also 32 bits. Short is a 16 bit integer, and chars are 8 bits.

  • In general, use int or unsigned for variables and function parameters. The arm architecture is such that using shorts or chars is less efficient than using an int.
  • long is identical to int. Using the long keyword only serves to add confusion. CHDK is not expected to run on systems where int or long are anything other than 32 bits.
  • For arrays, use the smallest type that has the requisite number of bits.
  • For structures, alignment considerations limit the value of using chars and shorts.
TODO WHAT IS THE DEFAULT STRUCTURE PACKING ?
  • Avoid using floats or doubles, since there is no floating point hardware. Floating point emulation does work in CHDK code. There is potential for issues passing floats or doubles between Canon ROM and CHDK code. It is not clear if this has been tested, since very little of the canon ROM uses FP.

Multi tasking and intertask communication

TODO

Optimization

See Optimizing ARM sourcecode.

Advertisement | Your ad here

Around Wikia's network

Random Wiki