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.
Because Rockbox has always an OS, 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.
- Standard C library: Every platform should come with a C library. But sometimes, platforms only have a reduced and limited C library (e.g. Android's bionic). If the host's C library is insufficient, it is possible to partly or completely fall back to the C library that comes with Rockbox (in firmware/libc). Rockbox also has only a very small subset implemented, but it has everything it needs. Other libraries, like libm on Unix are not needed.
- Memory allocation: Rockbox doesn't use malloc() at all. All memory the core needs is allocated statically (the max. size can be adjusted tools/configure). The default is 8MB.
- File I/O: Rockbox only has a FAT(32) driver, which exposes a Unix-like API (file descriptor based, creat/open/close). So, you are (a) limited to fat32, (b) the platform offers a Unix-like File I/O API (see uisimulator/common/io.c), or (c) you can wrap around the platforms native API in a platform specific I/O driver. Of course, (a) is very much discouraged, (b) and (c) enable you to read/write any file system.
- Loading codecs(and plugins): The application ports currently use libdl for codecs. You need a compatible API (again, see uisimulator/common/io.c) or you implement loading them in a native way. Loading them in the classic manner (loading the binary blob into memory and jump to its main funcion) is probably not possible, due to CPU cache problems.
There's a set of drivers mandatory for each port, I'll refer to them in the OS terms.
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
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.
- lcd_init_device() is the initializing functions, which is called early at startup. You can use it to set up static data, or create objects or something, and generally connect to the host's update mechanism.
- lcd_update() draws the complete framebuffer, full screen. It's expected to either draw directly or copy it to a back buffer from which the platform can update.
- lcd_update_rect() draws a fraction of the framebuffer. It's meant to safe resources because it only updates what's needed, but you can call lcd_update() in it if you need to.
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);
extern int button_read_device(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 called periodically, every tick. It should be fast to execute. It only has the data parameter if 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 press with the touchscreen interface. Pass through the data parameter if you call it. It'll handle the current touchscreen mode for you and return the correct press on its own.
int touchscreen_to_pixels(int x, int y, int *data);
Kernel and Timer driver
Copyright © by the contributing authors.