Wikia

CHDK Wiki

Changes: Adding support for a new camera

Edit

Back to page

(Basics)
(Major revision: essentially replaced old article with my own.)
Line 2: Line 2:
 
|| __TOC__
 
|| __TOC__
 
|}
 
|}
The original article was copied from [https://tools.assembla.com/chdk/wiki/HDK/Adding%20support%20for%20new%20camera here].
+
The original article was copied from [https://tools.assembla.com/chdk/wiki/HDK/Adding%20support%20for%20new%20camera here] and later merged/replaced by the one from [http://www.mweerden.net/chdk_porting.html mweerden].
   
===Introduction===
 
   
This manual assumes that
+
==Introduction==
* firmware dump
 
* basic ARM assembler knowledge
 
* decent ARM reversengineering tool
 
* C knowledge
 
* chdk building environment
 
   
are already available/known/setup correctly. Making above things to work/etc. is not part of this document.
+
This article is mainly intended for new porters to get started. Various other useful articles can be found [[For_Developers|here]].
   
It is recommended that you use the sources from the trunk, as they contain fewer bells and whistles, and thus there are fewer things that can go wrong.
+
Feel free to make changes when you encounter something incorrect, incomplete or unclear.
   
===Source tree===
 
{|
 
|- valign="top"
 
||{{File|/bin}} ||- results ready for usage
 
|- valign="top"
 
||{{File|/core}} ||- glue that makes it all work together
 
|- valign="top"
 
||{{File|/CHDK}} ||- Files intended for CHDK directory on the SD card
 
|- valign="top"
 
||{{File|/doc}} ||- some docs...
 
|- valign="top"
 
||{{File|/include}} ||- headers
 
|- valign="top"
 
||   {{File|camera.h}} ||- Camera-dependent settings
 
|- valign="top"
 
||{{File|/lib}} ||- various libraries. BASIC handling, some math and standard C functions, font, other "separate" stuff goes here
 
|- valign="top"
 
||{{File|/loader<br>&nbsp;&nbsp;+&nbsp;<platform>}} ||- initial code ran by camera. Setups various things.
 
|- valign="top"
 
||{{File|/platform<br>&nbsp;&nbsp;+&nbsp;<platform><br>&nbsp;&nbsp;+&nbsp;<platformsub>}} ||- camera-/firmware- dependent code
 
|- valign="top"
 
||{{File|&nbsp;&nbsp;+&nbsp;generic}} ||- generic code #included by platform code
 
|- valign="top"
 
||{{File|/script}} ||- BASIC scripts
 
|- valign="top"
 
||{{File|/tools}} ||- tools to make life easier :)
 
|- valign="top"
 
||{{File|/buildconf.inc}} ||- build optional components settings
 
|}
 
   
Source is build in the following order: {{File|tools}}, {{File|lib}}, {{File|platform}}, {{File|core}}, {{File|loader}}. The result of '{{File|core}}' is {{File|main.bin}}. It contains injected code and code to startup camera in special way.
+
==Requirements==
   
'{{File|loader}} is a, well, loader for the '{{File|core}}'. It handles camera's software reset, loading '{{File|core}}' to specific address and finally running it.
+
Besides some knowledge of assembly and C, you will need to following things when making a CHDK port:
   
Files that are not included into source tree are
+
* A dump of the Canon firmware ([[Porting_the_CHDK#Q._How_can_I_get_a_firmware_dump.3F|details]])
* {{File|/tools/sig_ref_<X>.bin}} (see description below)
+
* Tools to disassemble the above firmware (e.g. [[Loading_dump_to_IDA|IDA]] or [[GPL_Tools|others]])
* {{File|/platform/<NAME>/sub/<VER>/PRIMARY.BIN}} - fw dump for camera NAME version VER
+
* CHDK source + ARM compilation tools (see links [[For_Developers|here]])
  +
* Some addresses for LEDs (discussed [[Firmware_Dumping|here]])
   
Take a look at {{File|Makefiles}}, some of them must be modified.
 
   
  +
==The workings of the CHDK==
   
===Tools===
+
To understand how the CHDK works we first must look at the Canon firmware itself. On startup, the firmware roughly executes the following:
   
* {{File|pakwif}} - utility to build FIR - fw update files<br>
+
* If there is a memory card with a <tt>DISKBOOT.BIN</tt> file in the root: load, decode and run it.
* {{File|hexdmplt}} - utility to dump files
+
* Otherwise, start the normal booting process, which includes starting various tasks such as:
* {{File|gensig.sh}} - [[Signature finder]] to simplify search of known functions in fw dumps. File {{File|signatures.h}} is generated from data from the set of
+
** Logging task
* {{File|sig_ref_#.txt}} and {{File|sig_ref_#.bin}}. {{File|Txt}} contains function names and virtual offsets and {{File|bin}} is a corresponding fw dump.
+
** Keyboard task
  +
** Image-capturing task
  +
** File-system task
   
  +
The CHDK adds another task and slightly adapts some of the existing task to make things work. It does this mimicking the normal boot process, substituting different behaviour where needed. This includes adding hooks to the general task creation of the firmware, which allows it to start its own versions of certain tasks.
   
===Basics===
+
All of this is possible due to the <tt>DISKBOOT.BIN</tt> execution. Put the CHDK code in <tt>DISKBOOT.BIN</tt>, and the firmware will execute it automatically. Note that this requires a FAT12 or FAT16 partition ([[Bootable_SD_card|details]]). Also note that it is also possible to do the same by using a <tt>PS.FIR</tt>/<tt>PS.FI2</tt> file and manually choosing to do a "firmware update".
   
So, the goal is to add things that are missing in {{File|/loader}} and {{File|/platform}} directories. The good starting point is copying of loader and platform stuff from another camera that may be very alike to the one your are trying to port to.
+
The details of what happens when <tt>DISKBOOT.BIN</tt> is executed are as follows.
   
{{File|makefile.inc}} in {{File|platform/sub}} dir should be edited, it contains these parameters:
+
* The core CHDK code is copied to a suitable location in memory.
{|
+
* The camera is restarted with the CHDK code as new entry point (based on reset code from the firmware).
|- valign="top"
+
* The CHDK adaptation of the boot process is run. It has the following differences from the firmware boot process:
||{{Var|<nowiki>PLATFORMID=12345</nowiki>}} ||- decimal ModelID so valid FIRs were built, refer to [[P-ID %28Table%29]].
+
** It adds task creation hooks.
|- valign="top"
+
** It starts the main CHDK task.
||{{Var|<nowiki>MEMBASEADDR=0x1900</nowiki>}} ||- address where wif or diskboot code will be loaded by camera. As code is mostly position dependent it's important to know. Used by loader.
+
** Changes some details to ensure correct startup
|- valign="top"
+
* At the end of the above process, all (normal) tasks are created. Via the added hooks, the CHDK substitutes some slightly modified tasks.
||{{Var|<nowiki>RESTARTSTART=0x50000</nowiki>}} ||- address of a free region. {{Proc|memmove()}} to setup core in memory and reset code lives here. Obviously it must not intersect with whats already loaded in RAM.<br>Usually:<br>{{Var|MEMBASEADDR}} - ({{Var|MEMBASEADDR}}+{{Var|FIRSIZE}}) and<br>{{Var|MEMISOSTART}} - ({{Var|MEMISOSTART}}+{{Var|MEMISOSIZE}})
 
|- valign="top"
 
||{{Var|<nowiki>ROMBASEADDR=0xffc00000</nowiki>}} ||- base address of fw. Used by function signature finder. For A-series it is 0xFFC00000, for S-, SD-, and G- series - 0xFF810000.
 
|- valign="top"
 
||{{Var|<nowiki>MEMISOSTART=0xABCDE</nowiki>}} ||- these will be adjusted later. Depends on particular firmware.
 
|- valign="top"
 
|}
 
   
Corresponding fw dump '''must''' be placed into {{File|PRIMARY.BIN}} in {{File|platform/sub/<VER>}} directory.
 
   
Edit {{File|Makefile}} and disable generation of Thumb code.
+
==Making a port==
   
Optional: edit {{File|platform\generic\main.c}} and disable function call link ''capt_seq_task()'', ''mykbd_task()'' and movie_record_task() as long as {{File|platform\sub}} is not fully modified for '''your''' platform.
+
To make a port for a new camera one has to search the firmware for code to base the adapted boot process and tasks on. Also, various camera-specific details and functions that the CDHK uses need to be implemented. The latter mostly entails taking already existing code of a port of a similar camera (i.e. your reference port) and finding the right constants for that code in the firmware. You typically find the origin of values/code in the reference port's firmware and try to use that to find something similar in your own. Finding a good reference port might not be trivial, so you might want to consult the CHDK experts on that one.
   
Many recent cameras require the DISKBOOT.BIN to be encoded. On these cameras, you need to define NEED_ENCODED_DISKBOOT in the platformsub makefile.inc
+
The easiest approach is probably to take all the code of an already existing port as basis and adapt it file by file. You'll need copies of the <tt>loader/&lt;camera&gt;</tt> and <tt>platform/&lt;camera&gt;</tt> directories, add(/copy) defines in <tt>include/camera.h</tt> and add the firmware dump to <tt>platform/&lt;camera&gt;/sub/&lt;version&gt;</tt> (N.B.: change the directory in <tt>sub</tt> to the right name for your firmware version).
   
===Memory layout===
+
Next you make sure that you can compile the CHDK image. Add defines for <tt>PLATFORM</tt> and <tt>PLATFORMSUB</tt> to <tt>makefile.inc</tt> (commenting out the ones that are enabled by default) and run <tt>make fir</tt>. There might be errors due to undefined functions. For now, you can get rid of them by adding some dummy values to <tt>platform/&lt;camera&gt;/sub/&lt;version&gt;/stubs_entry_2.S</tt> (e.g. <tt>NHSTUB(name,0x12345678)</tt> for function <tt>_name</tt>; note the underscore which is not in the <tt>NHSTUB</tt>).
   
Example memory layout while CHDK is running:
+
Now we can start adapting the code. During this process one can add debug statements that blink LEDs to indicate what the camera is doing. For example, you can use something like the following code (with 0x12345678 the address of the LED and N a reasonably large number such as 0x1000000):
{| cellspacing=0
 
|- valign="top" bgcolor="#f3f3f3"
 
||{{Var|0x00000000-0x00001000}} ||&nbsp;&nbsp;- interrupt related stuff.
 
|- valign="top" bgcolor="#fff3ff"
 
||{{Var|0x00001000-0x00001900}} ||&nbsp;&nbsp;- kernel stack.
 
|- valign="top" bgcolor="#f3f3ff"
 
||{{Var|0x00001900-0x00012340}} ||&nbsp;&nbsp;- ''.data''
 
|- valign="top" bgcolor="#f3f3ff"
 
||{{Var|0x00012340-0x000ABCD0}} ||&nbsp;&nbsp;- ''.bss''
 
|- valign="top" bgcolor="#fffff3"
 
||{{Var|0x000ABCD0-0x000CBCD0}} ||&nbsp;&nbsp;- 'core' lives here. '''<- thing we change'''
 
|- valign="top" bgcolor="#f0f0ff"
 
||{{Var|0x000CBCD0-0x00200000}} ||&nbsp;&nbsp;- VxWorks Primary memory pool.
 
|- valign="top" bgcolor="#f0f8ff"
 
||{{Var|0x00200000-0x02000000}} ||&nbsp;&nbsp;- "Extended" RAM. Various buffers, etc.
 
|- valign="top" bgcolor="#f0f0f0"
 
||{{Var|0x10000000-0x1fffffff}} ||&nbsp;&nbsp;- same as {{Var|0x0-0x0fffffff}} but with no caching
 
|- valign="top" bgcolor="#fff0f0"
 
| colspan=2 |<center>...mmio regions...</center>
 
|- valign="top" bgcolor="#ffe0e0"
 
||{{Var|0xffc00000-0xffffffff}} ||&nbsp;&nbsp;- Firmware flash
 
|}
 
   
  +
int i;
  +
  +
*((volatile long *) 0x12345678) = 0x46; // Turn on LED
  +
for (i=0; i&lt;N; i++) // Wait a while
  +
{
  +
asm volatile ( "nop\n" );
  +
}
  +
*((volatile long *) 0x12345678) = 0x44; // Turn of LED
  +
for (i=0; i&lt;N; i++) // Wait a while
  +
{
  +
asm volatile ( "nop\n" );
  +
}
   
===Platform/Sub===
+
Note that you cannot safely insert this code everywhere, so it is probably best to make a function (no inline!) for this and use that instead. Also note that in some cases you might want to add a <tt>while (1) ;</tt> to prevent not yet adapted code from running.
   
{|
+
As we all know, it is good practice to document the code. For the CHDK port this mostly means that you should note where (and, in non-trivial cases, how) you have found certain values or pieces of code in the firmware. Also make sure it is clear which portions of assembly are straight from the firmware and which are changed, added or removed. Besides being useful when debugging, this documentation really helps people who are going to use your port as basis.
|- valign="top"
 
||{{File|stubs_min.S}} ||- usually data pointers to make keyboard driver work. '{{Var|physw_status}} is where kbd state is kept. Address is inside {{Proc|PhySw}} task depending on kbd architecture.
 
|- valign="top"
 
||{{File|stubs_entry.S}} ||- entry points found by {{File|finsig}}. I.e. <u>generated</u>.
 
|- valign="top"
 
||{{File|stubs_entry_2.S}} ||- functions addresses 'fixups'. Here go functions that were mistakenly identified by {{File|finsig}} or completely missed.
 
|- valign="top"
 
||{{File|stubs_auto.S}} ||- file <u>generated</u> from {{File|boot.c}}. Entry points for original fw functions referenced by {{File|boot.c}} assembler inlines.
 
|- valign="top"
 
||{{File|lib.c}} ||- various fw-dependent stuff. Pointers, sizes, etc.
 
|- valign="top"
 
||{{File|boot.c}} ||- FW startup and basic hook installation code.
 
|}
 
   
  +
We only cover the most important details here. Files or functionality that is not mentioned are typically straightforward to adapt (e.g. <tt>Makefile</tt>) or similar to other files/functionality discussed here. When you think you have done "everything", just go over each file to see if you have missed anything.
   
====File boot.c====
+
====<tt>platform/&lt;camera&gt;/sub/&lt;version&gt;/makefile.inc</tt>====
   
Most functions are copied and have straight equivalent inside of FW at early initialization stages but ours are with minor modifications. :)
+
This is probably the first file you want to adjust. At first the most important values are:
   
{{Proc|void boot()}} - setup ''.data'' and ''.bss''
+
* <tt>PLATFORMOS</tt>: either vxworks or dryos (it's DryOS if "gaonisoy" is at the start of the firmware dump)
  +
* <tt>MEMBASEADDR</tt>: typically 0x1900 (for DryOS: the smallest address used in the loop above the occurrence of MEMISOSTART)
  +
* <tt>RESTARTSTART</tt>: should point to a piece of memory that can be used to copy the restart code to. A safe bet is usually <tt>MEMBASEADDR</tt> plus the size of <tt>DISKBOOT.BIN</tt> (take it a bit bigger to accommodate changes in <tt>DISKBOOT.BIN</tt>), but be sure it does not cause overlap with <tt>MEMISOSTART</tt> (see <tt>loader/&lt;camera&gt;/main.c</tt>)
  +
* <tt>ROMBASEADDR</tt>: should point to the start of the firmware (usually 0xFF810000 or 0xFFC0000, related to size of firmware)
  +
* <tt>MEMISOSTART</tt>: points to the start of the memory pool used by the firmware (you can find this address at the end of the first piece of code in the firmware for DryOS)
  +
* <tt>NEED_ENCODED_DISKBOOT</tt>: for new cameras this it seems a safe bet to say "yes"
   
{{Proc|void h_usrInit()}} - Nothing interesting here. Just don't forget to fix the call to {{Proc|h_usrKernelInit()}}.
+
The other values are only required to make <tt>PS.FIR</tt> (VxWorks) or <tt>PS.FI2</tt> (DryOS) files.
   
{{Proc|void h_usrKernelInit()}} - fix '''R0''' ({{Var|h_usrRoot}}) and {{Color|#900|(IMPORTANT!)}} '''R2''' ({{Var|pMemPoolStart}}) parameters of {{Proc|kernelInit()}} call like that:
+
====<tt>loader/&lt;camera&gt;/entry.S</tt>====
"LDR R0, =h_usrRoot\n"
 
"MOV R1, #0x4000\n"
 
// "LDR R2, 0xA0000\n" // old
 
"LDR R2, =new_sa\n" // new
 
"LDR R2, [R2]\n" // new
 
"STR R12, [SP]\n"
 
"STR R4, [SP,#4]\n"
 
"BL sub_FFEC9DA8\n" {{Proc|kernelInit()}}
 
Now return to {{File|makefile.inc}} and modify {{Var|MEMISOSTART}} so it pointed to *original* {{Var|pMemPoolStart}}, i.e. {{Var|0xA0000}} in this example.
 
   
{{Proc|h_usrRoot()}} - two modifications: add calls to {{Proc|taskCreateHookAdd}} and {{Proc|taskDeleteHookAdd}} to install hooks on task creation (further CHDK startup/initialization depends on these) and {{Proc|drv_self_hide()}} to "hide" {{File|A/DISKBOOT.BIN}} file. Later is required to diskboot successfully - without it camera will stuck in permanent diskboot :)
+
The code in this file is the very first thing that gets called when <tt>DISKBOOT.BIN</tt> (or <tt>PS.FI2</tt>) is run. It typically just consists of the following code that calls the function in <tt>loader/&lt;camera&gt;/main.c</tt>:
   
  +
mov sp, #&lt;MEMBASEADDR&gt;
  +
mov r11, #0
  +
b my_restart
   
====File stubs_entry.S====
+
Some cameras seem to need some additional code before this piece for the automatic
  +
boot to work. See, for example, the SX10 port for the code snippet.
   
Yes, this is a generated file but it must be reviewed and every questionable entry point must be checked and fixed if needed by corresponding line in {{File|stubs_entry_2.S}}.
+
As a test you can already add some debug code to turn on a LED here, but you can also just add the code to the beginning of <tt>my_restart()</tt> in <tt>main.c</tt>. If this doesn't work, you should make sure you have the right numbers in <tt>platform/&lt;camera&gt;/sub/&lt;version&gt;/makefile.inc</tt>.
   
Some functions are not required by CHDK to function properly but are used to get some valuable information by hand or are left there for historical reasons.
+
====<tt>loader/&lt;camera&gt;/main.c</tt>====
   
= Links =
+
Here we implement <tt>my_restart()</tt>. The standard code copies the reset code from the <tt>resetcode</tt> subdirectory to a safe place (at RESTARTSTART) and then calls that code. The reason it is copied is because when copying the CHDK core to its destination, which happens in the reset code, there might be situations where the destination and source (containing the executing reset code) overlap. Overwriting code that is running is not what we want.
*[[For_Developers|For Developers]]
 
*[[Modifying_the_CHDK_Sources|Modifying the CHDK Sources]]
 
*[http://chdk.setepontos.com/index.php/topic,898.msg8773.html#msg8773 IXUS75/SD750 1.01a - Port (Forum)]
 
*[http://chdk.setepontos.com/index.php/topic,1132.msg10255.html#msg10255 Porting chdk to the G9 (Forum)]
 
*[http://chdk.setepontos.com/index.php/topic,234.0.html DryOS - some success (Forum)]
 
   
  +
Note that the call to the reset code is a bit of a hack. Instead of directly calling the function <tt>copy_and_restart()</tt> we actually jump to the entry point of the reset-code binary. That is, we jump to the code in <tt>loader/&lt;camera&gt;/resetcode/entry.S</tt>. The arguments we pass in the C code are "silently" passed on to the actual <tt>copy_and_restart()</tt> function.
  +
  +
====<tt>loader/&lt;camera&gt;/resetcode/entry.S</tt>====
  +
  +
Similar as the <tt>entry.S</tt> before, but this one calls the <tt>copy_and_restart()</tt> from <tt>loader/&lt;camera&gt;/resetcode/main.c</tt>. Note the "silent" passing of arguments mentioned earlier (<tt>../main.c</tt>).
  +
  +
====<tt>loader/&lt;camera&gt;/resetcode/main.c</tt>====
  +
  +
Here we implement the function <tt>copy_and_restart()</tt>. As arguments it takes the destination, source and size of the CHDK core. It first copies the core to the destination and then executes the slightly adapted reset code. You can typically locate this reset code in the firmware by using your reference port. Once you have found it, you must copy it (or make sure it is the same as the reference) and make sure you change the jump at the end to jump to the start of the core you just copied).
  +
  +
Note that this last jump is to the start of the core which means it will execute the code in <tt>core/entry.S</tt> next. This code will call the <tt>startup()</tt> function in <tt>platform/&lt;camera&gt;/main.c</tt>, which in turn will call the adapted boot code.
  +
  +
====<tt>platform/&lt;camera&gt;/sub/&lt;version&gt;/boot.c</tt>====
  +
  +
In this file the adapted boot process is located. For testing purposes you probably want to make sure that you have the standard <tt>startup()</tt> in <tt>platform/&lt;camera&gt;/main.c</tt>; this function basically just calls the <tt>boot()</tt> function of <tt>boot.c</tt>.
  +
  +
Besides the boot process itself, this file also contains the task-creation hooks, a function to start the main CHDK ("spy")task and one or two modified task (<tt>init_file_modules_task()</tt> and, if relevant for your camera, <tt>JogDial_task_my()</tt>). At first it is probably best to comment out the code of the task-creation hooks and <tt>CreateTask_spytask()</tt>. This way you can focus on getting the boot process working and uncomment the tasks as you finish them.
  +
  +
The boot code is taken from the start of the firmware and the successive functions that are called from it. There a a number of changes that are made:
  +
  +
* The task-creation hooks are added.
  +
* Power-button detection is improved; this makes sure that you only have to hold the power button for about a second to start in shooting mode. (Due to the more complex booting the firmware no longer correctly detects whether the power-button was pressed to start the camera.)
  +
* MEMISOSTART is replaced; the CHDK core is now load at that address so we must adjust the memory pool accordingly.
  +
* The CHDK task must be created.
  +
  +
In essence you can just take the code from the firmware and copy the changes as made in your reference port. Once you have got the boot code ready, the camera should boot normally again. (Only difference is probably that a short press on the power-button gives you the review mode now.)
  +
  +
First thing to do now is to get the CHDK task to run. So uncomment the previously commented code and check that you get the CHDK boot screen now. It will take a few seconds because it is waiting for something that has to be done at a later stage. The relevant code of the CHDK task is in <tt>core/main.c</tt>. Also, you might need to fix some of the references in <tt>stubs_entry_2.S</tt> and <tt>stubs_min.S</tt> as well as the <tt>vid_*()</tt> functions in <tt>platform/&lt;camera&gt;/sub/&lt;version&gt;/lib.c</tt> and <tt>debug_led()</tt> in <tt>platform/&lt;camera&gt;/lib.c</tt>.
  +
  +
At this point you can probably skip the tasks in this file. It is more useful to first get the keyboard to work properly for CHDK. (See <tt>platform/&lt;camera&gt;/kbd.c</tt>.) Don't forget to uncomment the <tt>if</tt> statement with <tt>mykbd_task</tt>!
  +
  +
Following the general keyboard task, you can add the JogDial_task (if your camera has a jog dial). Here you will have to add a piece of code (see, for example, the SX10 port). The purpose of this code is to be able to stop the firmware from reacting to the jog dial while one is in CHDK ALT mode.
  +
  +
Finally, you must adapt <tt>init_file_modules_task()</tt>. Here an extra piece of code has to be added as well. This code is to support the autoboot feature on memory cards that are bigger than 4GB (by using two partitions). Also, after the call that takes care of this, we add a statement that signals the CHDK task it can start. This is done afterwards to make sure that this task uses the right partition.
  +
  +
====<tt>platform/&lt;camera&gt;/sub/&lt;version&gt;/stubs_entry_2.S</tt>====
  +
  +
The CHDK uses various functions that are readily available in the firmware. In order to do so the addresses to these function need to be known. The build system tries to guess ([[Signature_finder|details]]) these addresses from the firmware dump (which are written to <tt>platform/&lt;camera&gt;/sub/&lt;version&gt;/stubs_entry.S</tt>), but not in all cases does it find the right addresses. For this reason it is possible to add corrections to <tt>stubs_entry_2.S</tt>. These corrections should be of the form <tt>NHSTUB(name,address)</tt>. Note that the macro <tt>NSTUB</tt>, as used in <tt>stubs_entry.S</tt>, should not be used; using <tt>NHSTUB</tt> ensures that the incorrect addresses found automatically are overridden.
  +
  +
There might also be some functions that are completely missed by the automatic detection. These will generate compilation errors that mention function names that start with an underscore. These should also be manually added to <tt>stubs_entry_2.S</tt>, but without the underscore (<tt>NHSTUB</tt> takes care of that).
  +
  +
====<tt>platform/&lt;camera&gt;/kbd.c</tt>====
  +
  +
To get CHDK to detect button presses (and USB remote triggers), one needs to specify the bits that represent these. These bits are located in the <tt>physw_status</tt> array (defined in <tt>platform/&lt;camera&gt;/sub/&lt;version&gt;/stubs_min.S</tt>). To figure out which bit is what, one can use the debug On-Screen Display (OSD) of CHDK. As the buttons probably don't work yet, you can force it to be always visible by uncommenting the commented call to <tt>gui_draw_debug_vals_osd()</tt> in <tt>core/gui.c</tt> and removing the <tt>if</tt> at the beginning of that function. Also make sure that in this function you display the physw_status array (one line for each of the three elements). With these changes you should be able to find each bit that corresponds to a specific button. Note that for the USB-power bit you can simple plug in a (connected) USB cable.
  +
  +
The information obtained in this way should be put in <tt>keymap[]</tt> (except for the USB-power bit). Each entry is of the form <tt>{ idx, KEY_NAME, mask }</tt> where <tt>idx</tt> is the index to <tt>physw_status</tt> and <tt>mask</tt> selects the bit (or bits) for the <tt>KEY_NAME</tt> button. That is, <tt>(physw_status[idx] & mask)</tt> should be true if, and only if, button <tt>KEY_NAME</tt> is not pressed (not, because the bits are 0 if the button ''is'' pressed). The table should be ended with <tt>{ 0, 0, 0 }</tt>.
  +
  +
For the USB power you should define the macros <tt>USB_MASK</tt> and <tt>USB_REG</tt> (where the latter is the index for <tt>physw_status</tt>). Be sure to check the use of <tt>USB_REG</tt> as some code simple has the value hard coded instead of using the macro.
  +
  +
Next you can define <tt>alt_mode_key_mask</tt> and the <tt>KEY_MASK?</tt> macros. For <tt>alt_mode_key_mask</tt> you should take the mask from <tt>keymap[]</tt> that corresponds to the button that should be used to enable the CHDK "ALT" mode. The macros should be the ''or'' of all the masks of key that correspond to the specific index (i.e. for <tt>KEY_MASK0</tt> you should take the masks of keys that are represented in <tt>physw_status[0]</tt>).
  +
  +
Besides these defines, you should also set <tt>SD_READONLY_FLAG</tt>. This is needed because autoboot only works if your memory card is locked and saving pictures only works if it is unlocked. Because the card lock is not a "true" lock, CHDK can fake that the card is unlocked by unsetting this flag. Note that there typically is no define to indicate the index of <tt>physw_status</tt> that is used, so make sure that the code you have uses the right index. Also note that you can get this bit with the debug OSD, but perhaps it is easier to find it in the firmware (see, for example, the comments in the IXUS 870 port).
  +
  +
Finally, if your camera has a jog dial, <tt>kbd.c</tt> should implement the functions <tt>Get_JogDial()</tt> and <tt>get_jogdial_direction()</tt>. Don't forget to check that turning the dial in, for example, the CHDK menu feels intuitive.
  +
  +
With this file complete, you should be able to actually use the CHDK. Of course only as far as the functionality that you have enabled at this point allows you.
  +
  +
====<tt>platform/&lt;camera&gt;/sub/&lt;version&gt;/capt_seq.c</tt>====
  +
  +
In <tt>capt_seq.c</tt> we have two modified tasks: <tt>capt_seq_task()</tt> and <tt>exp_drv_task()</tt>. These are adapted to support saving RAW files, using a remote (USB) trigger and changing the exposure settings (including the addition of very long exposure times).
  +
  +
====<tt>platform/&lt;camera&gt;/sub/&lt;version&gt;/movie_rec.c</tt>====
  +
  +
Here we have just on task. An adapted <tt>movie_record_task()</tt> to allow optical zooming during recording as well as changing the movie quality.
  +
  +
====<tt>platform/&lt;camera&gt;/lib.c</tt>====
  +
  +
The function <tt>ubasic_set_led()</tt> is a bit of an odd one as it has no clearly defined behaviour (due to the fact that different cameras have different number and kinds of leds). The best guess seems to be to just try and approximate the behaviour as described in the documentation of the UBASIC <tt>set_led</tt> function.
  +
  +
====<tt>platform/&lt;camera&gt;/main.c</tt>====
  +
  +
In <tt>main.c</tt> there are essentially three main things to adjust. First of all we have <tt>modemap[]</tt> (and <tt>mode_get()</tt>). The way to populate this table is the same as with <tt>key_map[]</tt> in <tt>kbd.c</tt>. Use the debug OSD with "Props" to show item <tt>PROPCASE_SHOOTING_MODE</tt> (see <tt>include/propcase1.h</tt> or <tt>include/propcase2.h</tt>, depending on your camera, for the right number) and change modes to see what value is shown. You might need to extend the enumeration in <tt>include/platform.h</tt> if your camera has new kinds of modes.
  +
  +
Secondly, there is <tt>fl_tbl[]</tt> (with <tt>CF_EFL</tt> and <tt>zoom_points</tt>). Depending on the number of different zoom steps your camera supports, there are two implementations. One has a table with ''all'' possible focal lengths (one for each zoom step; see, for example, the IXUS 980 port) while the other has just a small number of focal lengths and calculates the others through interpolation (see, for example, the SX10 port). To get the values for <tt>fl_tbl[]</tt> make a picture for each zoom step you put in the table and check the EXIF data for the reported focal length. The entries are in micrometers (i.e. mm*1000). To calculate the value for <tt>CF_EFL</tt> see the comments in the IXUS 980 port. Note that this value is typically multiplied by 1000 or 10000 to allow for accurate calculations using just integers (this factor is eliminated in <tt>get_effective_focal_length()</tt>).
  +
  +
To conclude this file, we have the <tt>get_vbatt_min()</tt> and <tt>get_vbatt_max()</tt> functions. Get the values by observing the voltages that CHDK reports over time (you'll have to change the display of percentage to voltage in the CHDK menu). You might want to take numbers that deviate a bit from the actually observed minimum and maximum, as the decrease over time is not linear and is likely to be higher at the limits.
  +
  +
====<tt>platform/&lt;camera&gt;/shooting.c</tt>====
  +
  +
To fill <tt>aperture_sizes_table[]</tt> you can use the same method as for filling <tt>fl_tbl[]</tt> in <tt>main.c</tt>. In case your camera doesn't have an actual diaphragm, you just take pictures at the available zoom steps. Twice if your camera has an ND filter (once with filter and once without; either use the ND-filter option from the CHDK menu, if you have the right values in <tt>platform/&lt;camera&gt;/sub/&lt;version&gt;/stubs_entry(_2).S</tt>, or find specific modes that make sure the filter is used or not). The entries are of the form <tt>{ idx, av, string }</tt> with <tt>idx</tt> starting from 9, <tt>av</tt> the values of <tt>PROPCASE_AV</tt> and <tt>string</tt> a string representation of the aperture (the value of which you can get from the EXIF data).
  +
  +
To fill <tt>iso_table[]</tt> you check <tt>PROPCASE_ISO_MODE</tt> for the different ISO values the camera allows you to select. Note that <tt>Auto</tt> gets index 0 and <tt>HI</tt> gets index -1.
  +
  +
For <tt>shutter_speeds_table[]</tt> you essentially have to try various exposure times and see if there is a difference with others. If not, you know that the used exposure time wasn't really supported.
  +
  +
Finally, you'll have to find the right values for <tt>PARAM_FILE_COUNTER</tt> and <tt>PARAM_EXPOSURE_COUNTER</tt> by using the debug OSD. You can easily detect them by taking a picture and noting which value changes. Note that <tt>PARAM_FILE_COUNTER</tt> is a big number as it actually contains multiple counters (see its usage in <tt>platform/generic/shooting.c</tt>; taking a picture will add 16 to the number as a whole.
  +
  +
====<tt>core/kbd.h</tt>====
  +
  +
Add your camera to the right definition of nTxtbl. This table should correspond with the zoom-step indices used for <tt>fl_tbl[]</tt> in <tt>platform/&lt;camera&gt;/shooting.c</tt>.
  +
  +
  +
===Getting your first version out===
  +
  +
If all went well, you should have a reasonably decent initial port by now. Post it on the [http://chdk.setepontos.com/ forum], add a link to the wiki page for your camera and keep your fingers crossed.
 
[[Category:Development]]
 
[[Category:Development]]

Revision as of 06:42, May 9, 2009

The original article was copied from here and later merged/replaced by the one from mweerden.


Introduction

This article is mainly intended for new porters to get started. Various other useful articles can be found here.

Feel free to make changes when you encounter something incorrect, incomplete or unclear.


Requirements

Besides some knowledge of assembly and C, you will need to following things when making a CHDK port:

  • A dump of the Canon firmware (details)
  • Tools to disassemble the above firmware (e.g. IDA or others)
  • CHDK source + ARM compilation tools (see links here)
  • Some addresses for LEDs (discussed here)


The workings of the CHDK

To understand how the CHDK works we first must look at the Canon firmware itself. On startup, the firmware roughly executes the following:

  • If there is a memory card with a DISKBOOT.BIN file in the root: load, decode and run it.
  • Otherwise, start the normal booting process, which includes starting various tasks such as:
    • Logging task
    • Keyboard task
    • Image-capturing task
    • File-system task

The CHDK adds another task and slightly adapts some of the existing task to make things work. It does this mimicking the normal boot process, substituting different behaviour where needed. This includes adding hooks to the general task creation of the firmware, which allows it to start its own versions of certain tasks.

All of this is possible due to the DISKBOOT.BIN execution. Put the CHDK code in DISKBOOT.BIN, and the firmware will execute it automatically. Note that this requires a FAT12 or FAT16 partition (details). Also note that it is also possible to do the same by using a PS.FIR/PS.FI2 file and manually choosing to do a "firmware update".

The details of what happens when DISKBOOT.BIN is executed are as follows.

  • The core CHDK code is copied to a suitable location in memory.
  • The camera is restarted with the CHDK code as new entry point (based on reset code from the firmware).
  • The CHDK adaptation of the boot process is run. It has the following differences from the firmware boot process:
    • It adds task creation hooks.
    • It starts the main CHDK task.
    • Changes some details to ensure correct startup
  • At the end of the above process, all (normal) tasks are created. Via the added hooks, the CHDK substitutes some slightly modified tasks.


Making a port

To make a port for a new camera one has to search the firmware for code to base the adapted boot process and tasks on. Also, various camera-specific details and functions that the CDHK uses need to be implemented. The latter mostly entails taking already existing code of a port of a similar camera (i.e. your reference port) and finding the right constants for that code in the firmware. You typically find the origin of values/code in the reference port's firmware and try to use that to find something similar in your own. Finding a good reference port might not be trivial, so you might want to consult the CHDK experts on that one.

The easiest approach is probably to take all the code of an already existing port as basis and adapt it file by file. You'll need copies of the loader/<camera> and platform/<camera> directories, add(/copy) defines in include/camera.h and add the firmware dump to platform/<camera>/sub/<version> (N.B.: change the directory in sub to the right name for your firmware version).

Next you make sure that you can compile the CHDK image. Add defines for PLATFORM and PLATFORMSUB to makefile.inc (commenting out the ones that are enabled by default) and run make fir. There might be errors due to undefined functions. For now, you can get rid of them by adding some dummy values to platform/<camera>/sub/<version>/stubs_entry_2.S (e.g. NHSTUB(name,0x12345678) for function _name; note the underscore which is not in the NHSTUB).

Now we can start adapting the code. During this process one can add debug statements that blink LEDs to indicate what the camera is doing. For example, you can use something like the following code (with 0x12345678 the address of the LED and N a reasonably large number such as 0x1000000):

int i;

*((volatile long *) 0x12345678) = 0x46; // Turn on LED
for (i=0; i<N; i++) // Wait a while
{
  asm volatile ( "nop\n" );
}
*((volatile long *) 0x12345678) = 0x44; // Turn of LED
for (i=0; i<N; i++) // Wait a while
{
  asm volatile ( "nop\n" );
}

Note that you cannot safely insert this code everywhere, so it is probably best to make a function (no inline!) for this and use that instead. Also note that in some cases you might want to add a while (1) ; to prevent not yet adapted code from running.

As we all know, it is good practice to document the code. For the CHDK port this mostly means that you should note where (and, in non-trivial cases, how) you have found certain values or pieces of code in the firmware. Also make sure it is clear which portions of assembly are straight from the firmware and which are changed, added or removed. Besides being useful when debugging, this documentation really helps people who are going to use your port as basis.

We only cover the most important details here. Files or functionality that is not mentioned are typically straightforward to adapt (e.g. Makefile) or similar to other files/functionality discussed here. When you think you have done "everything", just go over each file to see if you have missed anything.

platform/<camera>/sub/<version>/makefile.inc

This is probably the first file you want to adjust. At first the most important values are:

  • PLATFORMOS: either vxworks or dryos (it's DryOS if "gaonisoy" is at the start of the firmware dump)
  • MEMBASEADDR: typically 0x1900 (for DryOS: the smallest address used in the loop above the occurrence of MEMISOSTART)
  • RESTARTSTART: should point to a piece of memory that can be used to copy the restart code to. A safe bet is usually MEMBASEADDR plus the size of DISKBOOT.BIN (take it a bit bigger to accommodate changes in DISKBOOT.BIN), but be sure it does not cause overlap with MEMISOSTART (see loader/<camera>/main.c)
  • ROMBASEADDR: should point to the start of the firmware (usually 0xFF810000 or 0xFFC0000, related to size of firmware)
  • MEMISOSTART: points to the start of the memory pool used by the firmware (you can find this address at the end of the first piece of code in the firmware for DryOS)
  • NEED_ENCODED_DISKBOOT: for new cameras this it seems a safe bet to say "yes"

The other values are only required to make PS.FIR (VxWorks) or PS.FI2 (DryOS) files.

loader/<camera>/entry.S

The code in this file is the very first thing that gets called when DISKBOOT.BIN (or PS.FI2) is run. It typically just consists of the following code that calls the function in loader/<camera>/main.c:

mov sp, #<MEMBASEADDR>
mov r11, #0
b   my_restart

Some cameras seem to need some additional code before this piece for the automatic boot to work. See, for example, the SX10 port for the code snippet.

As a test you can already add some debug code to turn on a LED here, but you can also just add the code to the beginning of my_restart() in main.c. If this doesn't work, you should make sure you have the right numbers in platform/<camera>/sub/<version>/makefile.inc.

loader/<camera>/main.c

Here we implement my_restart(). The standard code copies the reset code from the resetcode subdirectory to a safe place (at RESTARTSTART) and then calls that code. The reason it is copied is because when copying the CHDK core to its destination, which happens in the reset code, there might be situations where the destination and source (containing the executing reset code) overlap. Overwriting code that is running is not what we want.

Note that the call to the reset code is a bit of a hack. Instead of directly calling the function copy_and_restart() we actually jump to the entry point of the reset-code binary. That is, we jump to the code in loader/<camera>/resetcode/entry.S. The arguments we pass in the C code are "silently" passed on to the actual copy_and_restart() function.

loader/<camera>/resetcode/entry.S

Similar as the entry.S before, but this one calls the copy_and_restart() from loader/<camera>/resetcode/main.c. Note the "silent" passing of arguments mentioned earlier (../main.c).

loader/<camera>/resetcode/main.c

Here we implement the function copy_and_restart(). As arguments it takes the destination, source and size of the CHDK core. It first copies the core to the destination and then executes the slightly adapted reset code. You can typically locate this reset code in the firmware by using your reference port. Once you have found it, you must copy it (or make sure it is the same as the reference) and make sure you change the jump at the end to jump to the start of the core you just copied).

Note that this last jump is to the start of the core which means it will execute the code in core/entry.S next. This code will call the startup() function in platform/<camera>/main.c, which in turn will call the adapted boot code.

platform/<camera>/sub/<version>/boot.c

In this file the adapted boot process is located. For testing purposes you probably want to make sure that you have the standard startup() in platform/<camera>/main.c; this function basically just calls the boot() function of boot.c.

Besides the boot process itself, this file also contains the task-creation hooks, a function to start the main CHDK ("spy")task and one or two modified task (init_file_modules_task() and, if relevant for your camera, JogDial_task_my()). At first it is probably best to comment out the code of the task-creation hooks and CreateTask_spytask(). This way you can focus on getting the boot process working and uncomment the tasks as you finish them.

The boot code is taken from the start of the firmware and the successive functions that are called from it. There a a number of changes that are made:

  • The task-creation hooks are added.
  • Power-button detection is improved; this makes sure that you only have to hold the power button for about a second to start in shooting mode. (Due to the more complex booting the firmware no longer correctly detects whether the power-button was pressed to start the camera.)
  • MEMISOSTART is replaced; the CHDK core is now load at that address so we must adjust the memory pool accordingly.
  • The CHDK task must be created.

In essence you can just take the code from the firmware and copy the changes as made in your reference port. Once you have got the boot code ready, the camera should boot normally again. (Only difference is probably that a short press on the power-button gives you the review mode now.)

First thing to do now is to get the CHDK task to run. So uncomment the previously commented code and check that you get the CHDK boot screen now. It will take a few seconds because it is waiting for something that has to be done at a later stage. The relevant code of the CHDK task is in core/main.c. Also, you might need to fix some of the references in stubs_entry_2.S and stubs_min.S as well as the vid_*() functions in platform/<camera>/sub/<version>/lib.c and debug_led() in platform/<camera>/lib.c.

At this point you can probably skip the tasks in this file. It is more useful to first get the keyboard to work properly for CHDK. (See platform/<camera>/kbd.c.) Don't forget to uncomment the if statement with mykbd_task!

Following the general keyboard task, you can add the JogDial_task (if your camera has a jog dial). Here you will have to add a piece of code (see, for example, the SX10 port). The purpose of this code is to be able to stop the firmware from reacting to the jog dial while one is in CHDK ALT mode.

Finally, you must adapt init_file_modules_task(). Here an extra piece of code has to be added as well. This code is to support the autoboot feature on memory cards that are bigger than 4GB (by using two partitions). Also, after the call that takes care of this, we add a statement that signals the CHDK task it can start. This is done afterwards to make sure that this task uses the right partition.

platform/<camera>/sub/<version>/stubs_entry_2.S

The CHDK uses various functions that are readily available in the firmware. In order to do so the addresses to these function need to be known. The build system tries to guess (details) these addresses from the firmware dump (which are written to platform/<camera>/sub/<version>/stubs_entry.S), but not in all cases does it find the right addresses. For this reason it is possible to add corrections to stubs_entry_2.S. These corrections should be of the form NHSTUB(name,address). Note that the macro NSTUB, as used in stubs_entry.S, should not be used; using NHSTUB ensures that the incorrect addresses found automatically are overridden.

There might also be some functions that are completely missed by the automatic detection. These will generate compilation errors that mention function names that start with an underscore. These should also be manually added to stubs_entry_2.S, but without the underscore (NHSTUB takes care of that).

platform/<camera>/kbd.c

To get CHDK to detect button presses (and USB remote triggers), one needs to specify the bits that represent these. These bits are located in the physw_status array (defined in platform/<camera>/sub/<version>/stubs_min.S). To figure out which bit is what, one can use the debug On-Screen Display (OSD) of CHDK. As the buttons probably don't work yet, you can force it to be always visible by uncommenting the commented call to gui_draw_debug_vals_osd() in core/gui.c and removing the if at the beginning of that function. Also make sure that in this function you display the physw_status array (one line for each of the three elements). With these changes you should be able to find each bit that corresponds to a specific button. Note that for the USB-power bit you can simple plug in a (connected) USB cable.

The information obtained in this way should be put in keymap[] (except for the USB-power bit). Each entry is of the form { idx, KEY_NAME, mask } where idx is the index to physw_status and mask selects the bit (or bits) for the KEY_NAME button. That is, (physw_status[idx] & mask) should be true if, and only if, button KEY_NAME is not pressed (not, because the bits are 0 if the button is pressed). The table should be ended with { 0, 0, 0 }.

For the USB power you should define the macros USB_MASK and USB_REG (where the latter is the index for physw_status). Be sure to check the use of USB_REG as some code simple has the value hard coded instead of using the macro.

Next you can define alt_mode_key_mask and the KEY_MASK? macros. For alt_mode_key_mask you should take the mask from keymap[] that corresponds to the button that should be used to enable the CHDK "ALT" mode. The macros should be the or of all the masks of key that correspond to the specific index (i.e. for KEY_MASK0 you should take the masks of keys that are represented in physw_status[0]).

Besides these defines, you should also set SD_READONLY_FLAG. This is needed because autoboot only works if your memory card is locked and saving pictures only works if it is unlocked. Because the card lock is not a "true" lock, CHDK can fake that the card is unlocked by unsetting this flag. Note that there typically is no define to indicate the index of physw_status that is used, so make sure that the code you have uses the right index. Also note that you can get this bit with the debug OSD, but perhaps it is easier to find it in the firmware (see, for example, the comments in the IXUS 870 port).

Finally, if your camera has a jog dial, kbd.c should implement the functions Get_JogDial() and get_jogdial_direction(). Don't forget to check that turning the dial in, for example, the CHDK menu feels intuitive.

With this file complete, you should be able to actually use the CHDK. Of course only as far as the functionality that you have enabled at this point allows you.

platform/<camera>/sub/<version>/capt_seq.c

In capt_seq.c we have two modified tasks: capt_seq_task() and exp_drv_task(). These are adapted to support saving RAW files, using a remote (USB) trigger and changing the exposure settings (including the addition of very long exposure times).

platform/<camera>/sub/<version>/movie_rec.c

Here we have just on task. An adapted movie_record_task() to allow optical zooming during recording as well as changing the movie quality.

platform/<camera>/lib.c

The function ubasic_set_led() is a bit of an odd one as it has no clearly defined behaviour (due to the fact that different cameras have different number and kinds of leds). The best guess seems to be to just try and approximate the behaviour as described in the documentation of the UBASIC set_led function.

platform/<camera>/main.c

In main.c there are essentially three main things to adjust. First of all we have modemap[] (and mode_get()). The way to populate this table is the same as with key_map[] in kbd.c. Use the debug OSD with "Props" to show item PROPCASE_SHOOTING_MODE (see include/propcase1.h or include/propcase2.h, depending on your camera, for the right number) and change modes to see what value is shown. You might need to extend the enumeration in include/platform.h if your camera has new kinds of modes.

Secondly, there is fl_tbl[] (with CF_EFL and zoom_points). Depending on the number of different zoom steps your camera supports, there are two implementations. One has a table with all possible focal lengths (one for each zoom step; see, for example, the IXUS 980 port) while the other has just a small number of focal lengths and calculates the others through interpolation (see, for example, the SX10 port). To get the values for fl_tbl[] make a picture for each zoom step you put in the table and check the EXIF data for the reported focal length. The entries are in micrometers (i.e. mm*1000). To calculate the value for CF_EFL see the comments in the IXUS 980 port. Note that this value is typically multiplied by 1000 or 10000 to allow for accurate calculations using just integers (this factor is eliminated in get_effective_focal_length()).

To conclude this file, we have the get_vbatt_min() and get_vbatt_max() functions. Get the values by observing the voltages that CHDK reports over time (you'll have to change the display of percentage to voltage in the CHDK menu). You might want to take numbers that deviate a bit from the actually observed minimum and maximum, as the decrease over time is not linear and is likely to be higher at the limits.

platform/<camera>/shooting.c

To fill aperture_sizes_table[] you can use the same method as for filling fl_tbl[] in main.c. In case your camera doesn't have an actual diaphragm, you just take pictures at the available zoom steps. Twice if your camera has an ND filter (once with filter and once without; either use the ND-filter option from the CHDK menu, if you have the right values in platform/<camera>/sub/<version>/stubs_entry(_2).S, or find specific modes that make sure the filter is used or not). The entries are of the form { idx, av, string } with idx starting from 9, av the values of PROPCASE_AV and string a string representation of the aperture (the value of which you can get from the EXIF data).

To fill iso_table[] you check PROPCASE_ISO_MODE for the different ISO values the camera allows you to select. Note that Auto gets index 0 and HI gets index -1.

For shutter_speeds_table[] you essentially have to try various exposure times and see if there is a difference with others. If not, you know that the used exposure time wasn't really supported.

Finally, you'll have to find the right values for PARAM_FILE_COUNTER and PARAM_EXPOSURE_COUNTER by using the debug OSD. You can easily detect them by taking a picture and noting which value changes. Note that PARAM_FILE_COUNTER is a big number as it actually contains multiple counters (see its usage in platform/generic/shooting.c; taking a picture will add 16 to the number as a whole.

core/kbd.h

Add your camera to the right definition of nTxtbl. This table should correspond with the zoom-step indices used for fl_tbl[] in platform/<camera>/shooting.c.


Getting your first version out

If all went well, you should have a reasonably decent initial port by now. Post it on the forum, add a link to the wiki page for your camera and keep your fingers crossed.

Around Wikia's network

Random Wiki