Yes, I think all of them return value, what should I do in this case?
Also it seems lvfaceEnd and aewbSuspend take values, but not quite sure, but might be decompilation error in Ghidra, how to make sure?
I used "(void *)" blindly, I saw a1ex used it in sd_uhs for SD_ReConfiguration function, I just reused it and didn't notice problems . .
Here's the definition in that case:
static int (*SD_ReConfiguration)() = 0;
That is, SD_ReConfiguration is a function pointer to a function that takes no arguments (I think? I didn't check what the implied arguments are if you leave that empty), and returns an int. It is initialised to untyped 0, which is probably not ideal (does it give a compile error?).
Taking values is okay, the concern is potential conflict between the calling convention you declare (perhaps implicitly) in ML code, and the calling convention used by the external function. ARM has a standard calling convention:
https://stackoverflow.com/questions/261419/what-registers-to-save-in-the-arm-c-calling-convention. Compilers will normally generate code according to those rules, but they don't have to, and it is possible to override the compiler with e.g. pragmas. Certainly, some Canon code doesn't use this convention.
Let's imagine you have some generated ML code that wants to call a Canon function you've named "do_stuff()", and you've told ML uses standard ARM calling convention. This means the compiler will generate assembly which assumes the following rules are true (and other rules, this is a specific example):
r4-r8 are callee-save registers
r9 might be a callee-save register or not (on some variants of AAPCS it is a special register)
r10-r11 are callee-save registers
"Callee-save" means the caller expects those registers will not be changed by the function call. This why if you look at the disassembly
in most cases you'll see them being saved at the start of a function and restored at the end.
Therefore, the compiler will assume that the values in some variables, etc, will never be changed before and after the call. But if the Canon function is unusual, those can change. If you don't tell ML this is one of those functions, this kind of thing can happen:
some_magiclantern_variable = 0; // the compiler *might* choose to put this in r4
some_canon_function(); // in C, this function cannot change your variable...
if (some_magiclantern_variable != 0)
// this should never happen, let's crash!
assert(-1); // but in asm, it certainly can, by changing the content of r4 and returning, and you assert here
In that code you'd expect the assert to never trigger. But it can if conditions are right. There are other edge cases. Some functions don't return. Some functions expect the return address is in register you're passing to it. Some expect they can return to your saved return address. This doesn't hurt you very often, but if it does it's really confusing.
So, you need to make sure that what you tell ML about Canon functions is correct. Most Canon functions use standard ARM calling convention, which should work with the defaults. Some do not and you should confirm this before using them. You can then instruct the compiler to do the right thing, e.g. in dryos.h:
extern void __attribute__((noreturn)) cstart(void);