Wiki > Main > PortingApplication (compare)
Difference: PortingApplication (r3 vs. r2)
Work in progress
This page tries to describe steps and requirements for porting Rockbox as an application (RaaA?), as well as common pitfalls.
Rockbox is historically an operating system for embedded systems. Yet, it has many audio and playback related features that you cannot find on a single media player application. But it lacks features of OSes shipped on devices (such as telephony or networking) which makes it illogical to run Rockbox as an OS on those devices. Additionally, Rockbox as an OS needs to be tailored for every specific device. Therefore it's desirable to run Rockbox as an application, within a hosted environment.
As part of SummerOfCode2010, Rockbox has been successfully ported to two platforms, SDL and Android.
Platform can mean an specific OS or a (cross platform) user space library. Rockbox is ported to the platforms then, not to the devices.
Speaking of platforms. We currently differentiate between platform ports and application ports. A platform port ports the existing application to a new platform. The application port has a defined feature set and should be similar across all platforms it has been ported too. A new application port is needed where the feature set differs significantly from existing application ports.
In practice: if apps/ code doesn't notice the difference between two platform ports, then it should be a single application port. But if apps/ notices it, because the one platform supports FM radio (which makes themes behave differently) and the other doesn't, then there should be two separate application ports.
This differentiation could possibly change in the future, it hasn't been set in stone at the time of writing.
This document will largely cover platform ports.
Because Rockbox has always been an OS, OS for embedded systems, it has virtually no dependencies. But to reach the best degree of integration and to reduce Rockbox' memory footprint the goal is to use supporting OS/library routines where possible.
There's a set of drivers mandatory for each port, I'll refer to them in the OS terms.
Functions marked with extern are already implemented in Rockbox, but you need to call them somehow from the platform drivers.
Rockbox draws into a flat memory region, the LCD framebuffer. It then uses lcd_update() and lcd_update_rect() to display the framebuffer. The following bits are interesting for the lcd driver.
typedef unsigned short fb_data; #define LCD_FBWIDTH ((LCD_WIDTH+7)/8) #define LCD_FBHEIGHT LCD_HEIGHT fb_data lcd_framebuffer[LCD_FBHEIGHT][LCD_FBWIDTH]; extern void lcd_init_device(void); extern void lcd_update(void); extern void lcd_update_rect(int x, int y, int width, int height);
The above shows the RGB565 (so, 16bit) case (i.e. each pixel has 5 bits of red, 6 bits of green and 5 bits of blue). Currently there's no driver for more bits, so you need to write one if you want to use it. But that'd be a lot of effort, because you'd also need to properly convert the compiled-in bitmaps. So it's recommended you stick to the 16bit driver, then all compiled-in bitmaps will also work as is.
What the driver needs to implement is the 3 functions shown.
It is important that lcd_update[_rect] schedules the update completely, otherwise you might see artifacts on the display.
Example: The Android port creates an RockboxFramebuffer? object in lcd_init_device. The object holds a back buffer. The OS updates from this back buffer. The back buffer is needed because you cannot determine when Android does the actual update. lcd_update() also emits a postInvalidate() call which notifies the OS that we're ready to draw.
Rockbox reads the button state in each tick. The input driver is responsible for mapping the states to appropriate buttons (as used in apps/keymap-*.c). Rockbox currently has no complete keyboard support, so don't waste time on trying to implement it. Instead, focus on the touchscreen and button interface.
The Touchscreen interface can be used for touchscreen input, obviously. But it can also re-purposed to mouse navigation.
With button interface you can send arbitrary buttons to the core. You need to have them defined (so they can be bitwise OR'ed) in the button-target.h file and you need to give them a function in the apps/keymap-*.c file.
extern void button_init_device(void); int button_read_device(int *data); extern int button_read_device touchscreen_to_pixels (int x, int y, int *data);
You need to implement the above functions. button_init_device() is much like the aforementioned lcd_init_device() functions, i.e. you use it to initialize the driver and connect it with the host.
button_read_device() is what's being Example: The Android port called periodically, every tick. It should be fast to execute. It only has some functions which the data parameter if OS calls whenever you implement the touchscreen interface. It's expected to return the appropriate bit for each pressed button (e.g. BUTTON_UP|BUTTON_LEFT), or BUTTON_NONE if nothing was pressed. You should return the value of touchscreen_to_pixels() if you received a button touchscreen press with is detected. Those function write the touchscreen interface. Pass through the data parameter if you call state into variables. button_read_device() only reads that state, calls touchscreen_to_pixels() it. It'll handle the current touchscreen mode for you and return returns the correct result. press on its own.
int touchscreen_to_pixels(int x, int y, int *data);
Rockbox uses "tick tasks" for a variety of purposes. tick tasks are small C functions that are called in a fixed intervall HZ times per second (HZ is usually 100).
extern bool timer_register(int reg_prio, void (*unregister_callback)(void),
extern void call_tick_tasks(void);
The first three functions need to be stubbed, for now, since it's only used in (some) plugins which the application doesn't build right now.
The last however, tick_start(), is important. This function needs to set up a mechanism which enables the tick tasks to be called. In most cases this means setting up a timer task, which runs periodically at a HZ rate.
The timer task function needs to call call_tick_tasks().
Threads Example: The SDL port creates a SDL_Timer, sets it to run each each interval_in_ms, and calls call_tick_tasks() from it.
Rockbox continuously writes the decoded audio data into the pcm buffer, which acts as a ring buffer. Since that can wrap, the pcm driver needs to ask for a chunk raw pcm data. Then, that chunk is to be fed to host, who doesn't need to do any more than writing it to the hardware.
Rockbox usually decodes at 44100Hz, and the result is 16bit signed interleaved pcm data.
void pcm_play_lock(void); void pcm_play_unlock(void); void pcm_dma_apply_settings(void); void pcm_play_dma_start(const void *addr, size_t size); void pcm_play_dma_stop(void); void pcm_play_dma_pause(bool pause); size_t pcm_get_bytes_waiting(void); const void * pcm_play_dma_get_peak_buffer(int *count); void pcm_play_dma_init(void); void pcm_postinit(void); extern void pcm_play_get_more_callback(void **start, size_t *size);
These are quite a lot of functions, but it's not actually complicated.
Now, if the current pcm chunk has been played, you want to get a new one for the host so it can continue playing. This is where pcm_play_get_more_callback() comes into play. This is where you get a new chunk from. It shall be called after each chunk and thus establishes a continuous pcm stream. Make the host call it so that it can fetch new data automatically on its own.
Rockbox relies on cooperative multi-tasking. This is very tricky, because host's usually don't offer that. Preemptive multi-tasking seems to be generally accepted and chosen over cooperative multi-tasking.
One has 4(5) choices basically:
It's not very useful to list all the functions now. One basically needs to implement all of firmware/thread.c (unless it can be re-used).
One particular function worthwhile to mention, though, is core_sleep(). If you don't use the host's thread, then this function shall notify the host that "Rockbox doesn't do anything right now", i.e. that all threads are asleep. If this is not implemented, then the scheduler in thread.c will busy wait for the next thread to be runnable, which wastes a lot CPU time for nothing. If you use the host's thread then the host should be able to figure it out on its own.
I don't want to recite much information here, because it's largely covered by PortingHowTo.
However, at the time of writing, there are only two platform ports which have the same feature set. Because of that, they are not treated differently throughout the Rockbox code (except the target tree code). They share a single firmware/export/config/application.h and a single entry in tools/configure. There's effectively only a single application port; but the application is ported to different platforms.
This application port should be re-used if possible. This reduces the amount of work needed to add a new platform port to the target tree platform drivers (including conditional compilation of it, in firmware/SOURCES) and to logic to pick the right build tools in tools/configure.
However, if it needs a new application port, then refer to PortingHowTo.
-- ThomasMartitz - 2010-08-15
r3 - 15 Aug 2010 - 21:58:59 - ThomasMartitzRevision r3 - 15 Aug 2010 - 21:58 - ThomasMartitz
Revision r2 - 15 Aug 2010 - 19:47 - ThomasMartitz
Copyright © by the contributing authors.